Error와 Exception
프로그램이 비정상적으로 종료되게 하는 원인
Error
수습할 수 없는 심각한 오류
- 컴파일 에러: 컴파일시 발생하는 에러
- 런타임 에러: 실행시 발생하는 에러
Exception (예외)
예외 처리를 통해 수습할 수 있는 덜 심각한 오류
예외 처리란?
package bubu;
public class Exception1 {
public static void main(String[] args) {
ExceptionObj1 exobj = new ExceptionObj1();
int value = exobj.divide(10, 0);
System.out.println(value);
}
}
class ExceptionObj1{
public int divide(int i, int k){
int value = 0;
value = i / k;
return value;
}
}
예외 처리하기 (try~catch)
try {
코드1
코드2
.................
} catch (Exception클래스명1 변수명1) {
Exception을 처리하는 코드
} catch (Exception클래스명2 변수명2) {
Exception을 처리하는 코드
}
package bubu;
public class Exception1 {
public static void main(String[] args) {
ExceptionObj1 exobj = new ExceptionObj1();
int value = exobj.divide(10, 0);
System.out.println(value);
}
}
class ExceptionObj1{
public int divide(int i, int k){
int value = 0;
try{
value = i / k;
} catch (ArithmeticException e){
System.out.println("0으로 나눌 수 없어요.");
System.out.println(e.toString());
}
return value;
}
}
위 코드의 문제점
- 0으로 나눴을 때 값이 안 나오는 게 맞는데, 엉뚱하게 0이 출력된다.
- 사용자가 원하지 않는 메서드를 출력한다.(divide 메서드)
예외 떠넘기기(throws)
리턴타입 method명 (아규먼트 리스트) throws Exception클래스명1, Exception클래스명2 ....{
코드1
코드2
.............
}
package bubu;
public class Exception1 {
public static void main(String[] args) {
ExceptionObj2 exobj = new ExceptionObj2();
//exception이 이쪽으로 넘어오면 try~catch문으로 처리해야 한다.
try {
int value = exobj.divide(10, 0);
System.out.println(value);
} catch (ArithmeticException e){
System.out.println("0으로 나눌 수 없어요.");
}
}
}
class ExceptionObj2{
public int divide(int i, int k) throws ArithmeticException{ //메서드를 호출한 쪽으로 exception을 떠넘김
int value = 0;
value = i / k;
return value;
}
}
RuntimeException과 Checked Exception
Exception의 직계자손 두 가지
- RuntimeException = UncheckedException
예외 처리를 하지 않아도 컴파일 됨 (실행 시 발생하는 예외)
ex) ArithmeticException, ArrayIndexOutOfBoundsException - IOException = CheckedException
예외 처리를 했는지 컴파일러가 체크해서 예외 처리를 하지 않았으면 컴파일 오류를 발생시킴
→ 예외 처리를 강제함
ex) FileNotFoundException
package studyException;
import java.io.IOException;
public class E {
//UncheckedException (예외 처리 안해도 됨)
void throwArithmeticException(){
throw new ArithmeticException();
}
//CheckedException (예외 처리 필수)
void throwIOException1(){
try {
throw new IOException();
} catch(IOException e){
e.printStackTrace();
}
}
void throwIOException2() throws IOException{
throw new IOException();
}
}
IOException 예시
package bubu;
import java.io.FileInputStream;
public class Exception4 {
public static void main(String[] args) {
//컴파일 오류 발생
FileInputStream fis = new FileInputStream("Exception4.java");
}
}
try ~ catch로 예외처리를 해주자.
package bubu;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class Exception4 {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("Exception4.java");
} catch (FileNotFoundException e){
System.out.println("파일을 찾을 수 없어요.");
}
}
}
다중 Exception 처리
package bubu;
public class Exception6 {
public static void main(String[] args) {
int[] array = {4, 0};
int[] value = null;
try {
value[0] = array[0] / array[1];
} catch (ArrayIndexOutOfBoundsException e){
System.out.println("정해진 배열의 범위를 벗어났어요." +e.getMessage());
} catch (ArithmeticException e){ //여기서 에러 발생
System.out.print("0으로 나눌 수 없어요. ");
e.printStackTrace();
} catch (Exception e){
System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
}
//getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
}
}
두 번째 catch에서 에러가 발생한다. (ArithmeticException 에러)
package bubu;
public class Exception6 {
public static void main(String[] args) {
int[] array = {4, 2};
int[] value = null;
try {
value[0] = array[0] / array[1];
} catch (ArrayIndexOutOfBoundsException e){
System.out.println("정해진 배열의 범위를 벗어났어요." +e.getMessage());
} catch (ArithmeticException e){
System.out.print("0으로 나눌 수 없어요. ");
e.printStackTrace();
} catch (Exception e){ //여기서 에러 발생
System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
}
//getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
}
}
- 세 번째 catch에서 에러가 발생한다. (Exception 에러)
- Exception은 모든 Exception의 조상이다.
- int[ ] value = new int[1]; 로 바꿔서 배열에 저장 공간을 만들어주면 에러가 발생하지 않는다.
한편, int[ ] array = {4, 2} → {4}로 바꾸면 어떻게 될까?
package bubu;
public class Exception6 {
public static void main(String[] args) {
int[] array = {4};
int[] value = new int[1];
try {
value[0] = array[0] / array[1];
} catch (ArrayIndexOutOfBoundsException e){ //여기서 에러 발생
System.out.println("정해진 배열의 범위를 벗어났어요. " +e.getMessage());
} catch (ArithmeticException e){
System.out.print("0으로 나눌 수 없어요. ");
e.printStackTrace();
} catch (Exception e){
System.out.println(e.toString()); //Exception e가 가지고 있는 문자열 값 출력
}
//getMessage(), printSTackTrace(): catch가 어떤 맥락에서 발생했는지 파악해서 알려주는 메시지 출력
}
}
첫 번째 catch에서 에러가 발생한다. (ArrayIndexOutOfBoundsException 에러)
array[1]이 존재하지 않기 때문이다.
try 블럭 하나에 catch 블럭을 여러 개 사용할 수 있다.
이때, 자식 예외를 먼저 catch하고 부모 예외를 나중에 catch해야 한다.
자식 ArrayIndexOutOfBoundsException, ArithmeticException
부모 Exception
try {
value[0] = array[0] / array[1];
} catch (ArrayIndexOutOfBoundsException e){
System.out.println("정해진 배열의 범위를 벗어났어요. " +e.getMessage());
} catch (ArithmeticException e){
System.out.print("0으로 나눌 수 없어요. ");
e.printStackTrace();
} catch (Exception e){
System.out.println(e.toString());
}
try catch finally
try {
코드1
코드2 ...
} catch (Exception클래스명1 변수명1) {
Exception을 처리하는 코드
} finally {
예외 여부와 관계없이 반드시 한 번은 실행되는 코드
}
finally 블록으로 묶인 코드는 반드시 한 번은 실행되는 코드이다.
- 위에 return문이 있더라도 반드시 finally는 수행된다.
- 단, 위에 System.exit() 코드가 있으면 finally가 수행되지 않고 바로 프로그램이 종료된다.
package studyException;
import javax.swing.*;
public class ExceptionTest4 {
public static void main(String[] args) {
try {
String num1 = JOptionPane.showInputDialog("[정수1]을 입력하세요");
String num2 = JOptionPane.showInputDialog("[정수2]을 입력하세요");
int n1 = Integer.parseInt(num1);
int n2 = Integer.parseInt(num2);
System.out.println("result: " + n1 / n2);
//NumberFormatException: 숫자가 아닌 다른 값을 입력했을 때 발생
//ArithmeticException: n2가 0일 때 발생
} catch (NumberFormatException e){
System.out.println("입력 오류! 숫자를 입력하세요");
System.exit(-1);
} catch (ArithmeticException e){
System.out.println("분모가 0이 될 수 없어요. [정수2]는 0이 아닌 값을 입력하세요!");
return;
} catch (Exception e){
System.out.println(e.toString());
System.out.println(e.getMessage());
} finally {
System.out.println("###반드시 실행되어야 하는 코드###"); //return문에도 실행 O
}
System.out.println("~~~The End~~~"); //return문으로 실행 X
}
}
1) NumberForMatException: 숫자가 아닌 다른 값을 입력했을 때
System.exit(-1)으로 시스템이 종료되고, finally의 코드가 실행되지 않는다. 당연히 맨 아래의 "~~~The End~~~" 부분도 출력되지 않는다.
2) ArithmeticException: 분모를 0으로 입력했을 때
앞의 catch문에 return;이 있어도 finally의 코드는 실행된다. 그러나 맨 아래의 "~~~The End~~~" 부분은 출력되지 않는다.
Quiz
정답
1번>
AException 발생
반드시 한번 수행코드
2번>
BException 발생
반드시 한번 수행코드
3번>
CException 발생
4번>
암튼...예외 발생
반드시 한번 수행코드
Throw
코드를 작성하는 개발자가 예외를 강제로 발생시키는 것
throw new 발생시킬 예외; (강제로 Exception 객체를 생성하여 예외를 강제로 만듦)
package studyException;
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void divide(){
if(right == 0){
throw new ArithmeticException("0으로 나눌 수 없습니다.");
//강제로 ArithmeticException 객체를 생성하여 예외를 강제로 내주었음
}
try {
System.out.print("계산결과는 ");
System.out.print(this.left / this.right);
System.out.print(" 입니다.");
} catch (Exception e){
System.out.println(e.getMessage());
System.out.println(e.toString());
e.printStackTrace();
}
System.out.println("Divide End");
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 0);
try {
c1.divide();
} catch(ArithmeticException e){ //강제로 발생한 Exception으로 인해 catch문이 실행됨
System.out.println(e.getMessage());
//e 안에는 new ArithmeticException("0으로 나눌 수 없습니다."); 인스턴스가 들어있음
}
}
}
사용자 정의 Exception과 예외 발생시키기 (throw throws)
사용자 정의 예외 클래스 만들기
[1] Exception을 상속받는다.
[2] 생성자를 구성하고 그 안에서 super("예외메시지");를 호출한다.
public class MyException extends Exception{
public MyException (String msg){
super(msg);
}
public MyException (Exception e){
super(e);
}
}
나만의 예외를 만들기 전에 해야 할 것: 자신의 예외를 unchecked로 할 것인가? checked로 할 것인가를 정해야 한다.
API 쪽에서 예외를 던졌을 때 API 사용자 쪽에서 예외 상황을 복구할 수 있다면 checked 예외를 사용한다. checked 예외는 사용자에게 문제를 해결할 기회를 주는 것이면서 예외처리를 강제하는 것이다. 하지만 checked 예외를 너무 자주 사용하면 API 사용자를 몹시 힘들게 할 수 있기 때문에 적정선을 찾는 것이 중요하다.
사용자가 API의 사용법을 어겨서 발생하는 문제거나 예외 상황이 이미 발생한 시점에서 그냥 프로그램을 종료하는 것이 덜 위험할 때 unchekced를 사용한다.
[사용자 정의 Exception 예제1]
package bubu;
public class MyException extends RuntimeException{
//오류 메시지나 발생한 Exception을 감싼 결과로 내가 만든 Exception을 사용하고 싶을 때가 많다.
//message를 받는 인자생성자
public MyException(String message) {
super(message);
}
//Throwable: 예외처리의 최상위 클래스 - RumtimeException 이외의 예외를 처리해줌
public MyException(Throwable cause) {
super(cause);
}
}
package bubu;
public class Exception8 {
public static void main(String[] args) {
try {
ExceptionObj8 exobj = new ExceptionObj8();
int value = exobj.divide(10, 0);
System.out.println(value);
} catch(MyException e){
System.out.println(e.getMessage());
//e 안에는 new MyException("0으로 나눌 수 없어요."); 인스턴스가 들어있음
}
}
}
class ExceptionObj8{
public int divide(int i, int k) throws MyException{
int value = 0;
try{
value = i / k;
} catch (ArithmeticException e){
throw new MyException("0으로 나눌 수 없어요.");
//MyException 객체를 생성하여 강제로 예외를 만듦
}
return value;
}
}
[사용자 정의 Exception 예제2]
package day09;
/*
* 사용자 정의 예외 클래스 만들기
[1] Exception을 상속받는다.
[2] 생성자를 구성하고 그 안에서 super("예외메시지");를 호출한다.
* */
public class NotSupportedNameException extends Exception{ //checkedException
public NotSupportedNameException(){
super("지원되지 않는 성씨입니다."); //예외 메시지로 등록됨
}
public NotSupportedNameException(String msg){
super(msg);
}
}
package day09;
import java.util.Scanner;
public class PongSite {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("이름을 입력하세요=> ");
String name = scanner.next();
//join() 호출
PongSite ps = new PongSite();
try{
ps.join(name);
} catch(NotSupportedNameException e){
System.out.println(e.getMessage());
e.printStackTrace();
}
}
public void join(String name) throws NotSupportedNameException{
if(name.charAt(0) == '퐁'){
System.out.println(name + "님 환영합니다^^");
} else if(name.charAt(0) == '콩'){
throw new NotSupportedNameException();
//throw를 통해 예외 강제 발생
} else {
throw new NotSupportedNameException("가입은 가능하지만 제약이 있어요~");
}
}
}
throw를 통해 NotSupportedNameException을 강제로 발생시킨다. 발생시키면서 메시지를 한번 출력한다.
발생된 NotSupportedNameException의 예외는 main에서 처리해준다. (try ~ catch)
실행 결과를 보면 출력이 2번 (text 부분 / 빨간 글씨 부분)된 것을 확인할 수 있다. 결론은 예외가 2번 실행되는 것이다.
메서드에서 예외를 처리하고, 실행한 메인쪽에서도 예외를 추가적으로 처리할 수 있게 된 것이다.
=> 예외를 분산처리할 수 있게 되었다!