-
[Java]STEP1 - 15) 예외 처리, 실행 예외, try와 catch, 자동 리소스 닫기, throws와 throw, 예외 정보 얻기개발 공부/Java 2020. 10. 4. 07:47
본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다.
모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍이번 게시물에서는 자바의 예외(Exception)에 대해서 알아보겠습니다.
1. 예외(Exception)란?
에러(Error)란 컴퓨터 하드웨어의 오동작 또는 고장으로 인해 응용프로그램 실행오류가 발생하는 것을 말한다.
따라서 에러는 JVM 실행에 문제가 있는 것이기 때문에 개발자가 이런 에러에 대처할 방법은 없다.
예외(Exception)이란 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.
예외는 에러와 달리 개발자의 예외 처리를 통해 프로그램을 종료하지 않고 정상 실행 상태를 유지할 수 있다.
예외는 두 가지로 나뉜다.
1. 일반 예외(Exception)
- 컴파일러 체크 예외라고도 하며, 자바 소스를 컴파일 하는 과정에서 예외 처리 코드가 필요한지 검사하여 예외 처리 코드가 없다면 컴파일 오류가 발생하는 예외.
- java.lang.Exception을 상속받는 예외 클래스 중 java.lang.RuntimeException을 제외한 나머지 예외 클래스
2. 실행 예외(Runtime Exception)
- 컴파일 하는 과정에서 예외 처리 코드를 검사하지 않기 때문에 개발자의 경험에 의해서 예외 처리 코드를 삽입해야하는 예외.
- java.lang.Exception과 java.lang.RuntimeException을 모두 상속받는 예외 클래스
- 실행 예외의 대표적인 예로는 다음과 같이 네 가지가 있다.
1. NullPointerException
- 객체 참조가 없는 상태, 즉 null값을 갖는 참조 변수로 객체 접근 연산자인 도트(.)연산자를 사용했을 때 발생한다.
2. ArrayIndexOutOfBoundsException
- 배열에서 인덱스 범위를 초과하여 사용할 경우 발생한다.
3. NumberFormatException
- 문자열이 숫자로 변환될 수 있다면 숫자를 리턴하지만, 숫자로 변환될 수 없는 문자가 포함되어 있는 경우 발생한다.
4. ClassCastException
- 상위 클래스와 하위 클래스 / 구현 클래스와 인터페이스 관계가 아닌 다른 클래스끼리 억지로 타입 변환을 할 경우 발생한다.
2. 예외 처리
프로그램에서 예외가 발생했을 경우 프로그램의 갑작스러운 종료를 막고, 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.
예외 처리 코드는 try, catch, finally를 통해서 작성할 수 있다.
- try : 예외 발생 가능 코드를 위치시키는 곳이다. 예외가 발생하면 catch로 넘어간다.
- catch: 예외 처리 코드를 실행하는 곳이다. 지정한 예외 클래스에 맞는 객체가 들어오면 실행한다.
- finally: 예외 발생 여부와 상관없이 항상 실행하는 곳이다. try나 catch에서 return을 작성하더라고 finally는 무조건 실행한다.
try {
int value1 = Integer.parseInt(data1);
int value2 = Integer.parseInt(data2);
int result = value1 + value2;
} catch (NumberFormatException e) {
System.out.println("숫자로 변환할 수 없습니다.");
} finally {
System.out.println("다시 실행하세요.");
}
다음은 어느 소스 코드의 일부분만 떼어온 것이다.
try는 예외가 발생할 수 있을 만한 코드를 위치시킨다.
data1과 data2의 타입에 따라 Integer 타입으로 변환될 수 있을지 없을지 결정되기 때문이다.
만약 정상적으로 Integer 타입으로 변환된다면, catch는 실행되지 않고 finally가 실행되며 종료될 것이다.
그러나 정상적으로 Integer 타입으로 변환되지 않는다면 NumberFormatException 예외가 발생하며 catch가 실행된다.
그 다음으로 finally가 실행되며 종료될 것이다.try 블록 내부에는 다양한 종류의 예외가 있을 수 있기 때문에 자바는 다중 catch를 허용한다.
여러 개의 catch 블록을 만들어서 다양한 예외에 대처하기 위함이다.
try {
//ArrayIndexOutOfBoundException
//NumberFormatException
} catch (ArrayIndexOutOfBoundException e) {
//예외 처리1
} catch (NumberFormatException e) {
//예외 처리2
}
try 블록 안에 두 종류의 예외가 발생했다.
따라서 catch 블록을 두 개 작성하여 각 예외에 대처할 수 있도록 하였다.
catch 블록이 여러 개라 할지라도 실행되는 블록은 단 하나이다.
그 이유는 try에서 예외가 동시다발적으로 일어나는 것이 아니라 하나의 예외가 발생하면 바로 catch로 이동하기 때문이다.다중 catch를 사용할 때 주의할 점이 있다.
바로 상위 예외 클래스를 가장 아래 쪽에 위치해야 한다는 점이다.
try {
//ArrayIndexOutOfBoundException
//NumberFormatException
} catch (Exception e) {
//예외 처리1
} catch (NumberFormatException e) {
//예외 처리2
}
Exception 클래스는 모든 예외 클래스의 상위 클래스(부모 클래스)이다.
그리고 catch 블록은 작성되어있는 순서에 따라 실행이 되는 특성이있다.
그렇기 때문에 try 블록에서 어떠한 예외가 발생하든, Exception이 존재하는 첫 번째 catch에 걸릴 수 밖에 없다.
따라서 위와 같은 코드에서는 NumberFormatException의 catch가 실행되지 않게 되므로,
정상적인 예외 처리를 위해서는 Exception의 catch가 가장 마지막으로 와야한다.자바 7부터는 하나의 catch에서 여러 예외를 처리할 수 있도록 멀티 catch 기능을 제공한다.
예외끼리 | 로 연결하면 가능하다.
try {
//ArrayIndexOutOfBoundException
//NumberFormatException
} catch (ArrayIndexOutOfBoundException | NumberFormatException e) {
//예외 처리1
} catch (Exception e) {
//예외 처리2
}
ArrayIndexOutOfBound 예외와 NumberFormatException 예외는 | 에 의해 연결되어 있다.
이런 경우 두 개의 예외 중 하나만 일어나더라도 첫 번째 catch 블록이 실행되도록 되어있다.또한 자바 7부터 try-with-resources를 사용하면 예외 발생 여부와 상관없이 사용했던 리소스 객체의 close() 메소드를 호출하여 안전하게 리소스를 닫아주는 '자동 리소스 닫기' 기능이 추가되었다.
try (FileInputStream fis = new FileInputStream("file.txt") {
//ArrayIndexOutOfBoundException
//NumberFormatException
} catch (ArrayIndexOutOfBoundException e) {
//예외 처리1
} catch (Exception e) {
//예외 처리2
}
try 키워드 다음에 괄호를 통해서 리소스 객체를 선언해 주게 되면
try-with-resources 기능에 의해서 try 블록이 끝나면 자동으로 fis.close() 를 호출하여 리소스를 닫아준다.
이는 자바 7 이전에는 일일히 close() 메소드를 호출해야하는 번거로움을 줄이고 코드 길이를 단축하였다.
중간에 예외가 발생하더라도 close() 메소드를 호출하여 리소스를 닫고 catch로 넘어간다.
단! AutoCloseable 인터페이스를 구현한 리소스 객체만 이 기능을 사용할 수 있다.
AutoCloseable 인터페이스의 구현 여부는 자바 API 도큐먼트에서 확인할 수 있다.
3. throws와 throw
throws와 throw는 알파벳 's' 하나의 차이이지만 그 기능은 완전히 다르다.
먼저 throws 부터 알아보자.
원래라면 메소드 내부에서 예외처리를 위해서는 try, catch 블록을 사용하였다.
하지만 경우에 따라서는 메소드를 호출한 곳으로 예외를 떠넘길 수 있는데, 이때 사용하는 것이 throws이다.
throws는 메소드의 선언부 끝에 작성되어 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘기는 역할을 한다.
public void method1() {
try {
method2();
} catch (ClassNotFoundException e) {
//예외 처리
}
}
public void method2() throws ClassNotFoundException {
Class one = Class.forName("java.lang.String555");
}
method1()은 method2()를 호출하는 메소드이다.
method2()에서는 "String555" 라고 하는 존재하지 않는 클래스를 불러오고 있기 때문에
ClassNotFoundException이 발생하게된다. 하지만 method2()에서는 어떠한 예외 처리도 하고 있지 않다.
그 이유는 method2() 선언부 오른쪽에 있는 throws로 예외를 떠넘기고 있기 때문이다.
떠넘겨진 예외는 method2()를 호출한 method1()으로 옮겨진다.
따라서 method1()에서 try, catch 블록을 이용하여 ClassNotFoundException에 대한 예외 처리를 하고 있는 것이다.다음으로 throw에 대해서 알아보자.
지금까지는 자바 표준 API에서 제공하는 예외 클래스들만을 다루어 보았다.
하지만 현실 세계에서는 표준 API에서 제공하는 예외 말고도, 여러가지 상황의 예외가 발생한다.
따라서 사용자가 직접 예외를 정의하여 만들 수 있다. 이를 '사용자 정의 예외'라고 한다.
throw는 해당 사용자 정의 예외를 발생시킬 때 필요한 키워드이다.
throw의 사용법에 앞서, 사용자 정의 예외를 어떻게 만드는지 살펴보겠다.
public class BalanceInsufficientException extends Exception {
public BalanceInsufficientException() { }
public BalanceInsufficientException(String message) {
super(message);
}
}
위 사용자 정의 예외는 잔고 보다 많은 돈을 인출할 때 발생하는 잔고 부족 예외이다.
사용자 정의 예외를 선언하기 위해서는
1. Exception 혹은 RuntimeException 예외 클래스를 상속받아야 한다.
2. 기본 생성자와 message를 매개 변수로 받는 생성자를 선언해야 한다.
3. message를 매개 변수로 받는 생성자에서는 super(message); 를 통해 부모 생성자를 호출해야 한다.이제 사용자 정의 예외를 만들었으니, 특정 상황에서 예외를 발생시켜서 예외를 처리해야 한다.
이때 throw가 사용되어 예외를 임의적으로 발생시킬 수 있다.
public void withdraw (int money) throws BalanceInsufficientException {
if (balance < money) {
throw new BalanceInsufficientException("남아있는 잔고보다 출금 금액이 많습니다.");
}
balance -= money;
}
withdraw라는 메소드를 실행할 때 if 조건문에 의해서 잔액과 출금 금액을 비교한다.
만약 잔액보다 출금 금액이 많다면 throw 키워드를 통해서 BalanceInsufficientException을 임의로 발생시킨다.
이때 "남아있는 잔고보다 출금 금액이 많습니다." 와 같이 예외 출력 메시지를 같이 매개변수로 보내야한다.
만약 예외가 발생한다면 throws BalanceInsufficientException을 통해서 예외가 떠넘겨질 것이다.
4. 예외 정보 얻기
try 블록에서 예외가 발생되면 예외 객체는 catch 블록의 매개 변수에서 참조하게 되므로 매개 변수를 이용하면 예외 객체의 정보를 알 수 있다.
또한 모든 예외 객체는 Exception 클래스를 상속받기 때문에, Exception이 가지고 있는 메소드를 사용할 수 있다.
예외 정보를 얻는데 가장 많이 사용되는 메소드는 getMessage()와 printStackTrace()가 있다.
try {
//ArrayIndexOutOfBoundException
} catch (ArrayIndexOutOfBoundException e) {
String message = e.getMessage();
System.out.println(message);
e.printStackTrace();
}
e.getMessage()는 ArrayIndexOutOfBoundException 객체에 매개변수로 전달되는 예외 message를 가져온다.
"배열의 인덱스를 초과하였습니다."와 같은 message이다.
e.printStackTrace()는 예외 발생 코드를 추적해서 모두 콘솔에 출력한다.
보통 이클립스 같은 IDE에서 예외가 발생했을 때 나오는 예외 코드와 같다.본 게시글은 여기서 마치겠습니다.
읽어주셔서 감사하고, 혹시나 틀린 부분이나 보완해야할 부분이 있다면 댓글에 남겨주세요~!
'개발 공부 > Java' 카테고리의 다른 글
[Java]STEP1 - 17) System 클래스, Class 클래스 (0) 2020.10.14 [Java]STEP1 - 16) java.lang과 java.util 패키지, Object 클래스, Objects 클래스 (0) 2020.10.07 [Java]STEP1 - 14) 중첩 클래스와 중첩 인터페이스 (0) 2020.10.02 [Java]STEP1 - 13) 인터페이스(Interface) (0) 2020.09.29 [Java]STEP1 - 12) 자바의 다형성, 클래스 타입 변환, 필드와 매개변수의 다형성, 강제 타입 변환(Casting), 추상클래스와 추상메소드 (0) 2020.09.24