예외
는 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류를 말한다.
예외가 발생되면 프로그램은 곧바로 종료된다는 점에서는 에러와 비슷하다. 하지만 예외는 예외 처리를 통해 프로그램을 종료하지 않고 정상 실행 상태가 유지되도록 할 수 있다.
예외의 종류에는 두 가지가 있다.
일반 예외와 실행 예외 클래스는 RuntimeException
클래스를 기준으로 구별한다.
RuntimeException의 하위 클래스가 아니면 일반 예외 클래스이고, 하위 클래스이면 실행 예외 클래스이다.
클래스 상속 관계에서 부모(조상)에 RuntimeException이 있다면 실행 예외 클래스이다.
일반 예외
컴파일러 체크 예외라고도 하는데, 프로그램 실행 시 예외가 발생할 가능성이 높기 때문에 컴파일 하는 과정에서 해당 예외 처리 코드가 있는지 검사한다.
만약 예외 처리 코드가 없다면 컴파일 에러가 발생한다.
실행 예외
컴파일러 넌 체크 예외라고도 하는데, 실행 시 예측할 수 없이 갑자기 발생하기 때문에 컴파일하는 과정에서 예외 처리 코드가 있는지 검사하지 않는다.
실행 예외에 대해 예외 처리 코드를 넣지 않았을 경우, 해당 예외가 발생하면 프로그램은 곧바로 종료된다.
소스 파일을 컴파일할 때 일반 예외가 발생할 가능성이 있는 코드를 발견하면 컴파일 에러를 발생시켜 개발자가 강제적으로 예외 처리 코드
를 작성하도록 요구한다.
그러나 실행 예외는 컴파일러가 체크해주지 않기 때문에 개발자의 경험을 바탕으로 예외 처리 코드를 작성해야 한다.
try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성되어 일반 예외와 실행 예외가 발생할 경우 예외 처리를 할 수 있도록 해준다.
try-catch-finally 블록은 다음과 같이 작성한다.
try {
// 예외 발생 가능 코드
} catch(예외 클래스 e) {
// 예외 처리
} finally {
// 항상 실행
}
try 블록에는 예외 발생 가능 코드가 위치한다.
try 블록의 코드가 예외 발생 없이 정상 실행되면 catch 블록의 코드는 실행되지 않고 finally 블록의 코드를 실행한다.
만약 try 블록의 코드에서 예외가 발생하면 즉시 실행을 멈추고 catch 블록으로 이동하여 예외 처리 코드를 실행한다.
그리고 finally 블록의 코드를 실행한다.finally 블록은 생략 가능며, 예외 발생 여부와 상관없이 항상 실행한다.
심지어 try 블록과 catch 블록에서 return문을 사용하더라도 finally 블록은 항상 실행된다.
NullPointerException 예외
객체 참조가 없는 상태, 객체가 없는 상태에서 객체를 사용하려 했기 때문에 예외가 발생한다.
public class Main { public static void main(String[] args) { String data = null; System.out.println(data.toString()); } }
객체를 참조하고 있지 않을 때 예외가 발생하는 것을 볼 수 있다.
여기에 try-catch-finally를 사용하면 다음과 같다.
public class Main { public static void main(String[] args) { String data = null; try { System.out.println(data.toString()); } catch(NullPointerException e) { System.out.println("예외 발생.. 예외 발생.. NullPointerException 발생"); } finally { System.out.println("항상 실행되기 때문에 예외가 발생했는지 모름"); } } }
자료형에 알맞지 않은 값을 대입 했을 때
예를 들어int형에 알맞지 않은 값을 대입하면 예외가 발생한다.
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); int A = stdIn.nextInt(); } }
int형 A에 숫자를 입력 했을 때와 문자열을 입력했을 때의 차이점을 볼 수 있다.
A의 값에 따라 프로그램이 진행되거나 끊기기 때문에 예외 처리를 해주면 다음과 같다.
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner stdIn = new Scanner(System.in); try { int A = stdIn.nextInt(); }catch( java.util.InputMismatchException e) { System.out.println("int형에 맞지않는 값을 대입"); } } }
예외 발생 여부에 따라 실행되는 코드가 다른 것을 볼 수 있다.
다중 catch
예외의 종류에 따라 예외 처리 코드를 다르게 작성할 수 있다.
다중 catch 블록을 작성할 때 주의할 점은 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다는 것이다. try 블록에서 예외가 발생했을 때, 예외를 처리해줄 catch 블록은 위에서부터 차례대로 검색되기 때문이다.
만약 상위 예외 클래스의 catch 블록이 위에 있다면, 하위 예외 클래스의 catch 블록은 실행되지 않는다.
잘못된 예시
try { ArrayIndexOutOfBoundsException 발생 NumberFormatException 발생 } catch (Exception e) { // 예외 발생 시 첫 번째 catch 블록만 실행됨 예외 처리 1 } catch (ArrayIndexOutOfBoundsException e) { 예외 처리 2 }
ArrayIndexOutOfBoundsException과 NumberFormatException은 모두 Exception을 상속 받기 때문에 첫 번째 catch 블록만 선택되어 실행된다.
올바른 예시
try { ArrayIndexOutOfBoundsException 발생 NumberFormatException 발생 } catch (ArrayIndexOutOfBoundsException e) { 예외 처리 1 } catch (Exception e) { 예외 처리 2 }
try 블록에서 ArrayIndexOutOfBoundsExceptio이 발생하면 첫 번째 catch 블록을 실행하고, 그 밖의 다른 예외가 발생하면 두 번째 catch 블록을 실행한다.
예외 떠넘기기
메소드 내부에서 예외가 발생할 수 있는 코드를 작성할 때 try-catch 블록으로 예외를 처리하는 것이 기본이지만, 경우에 따라서는 메소드를 호출한 곳으로 예외를 떠넘길 수도 있다.
이때 사용하는 키워드가 throws
이다. throws 키워드는 메소드 선언부 끝에 작성되어 메소드에서 처리하지 않은 예외를 호출한 곳으로 떠넘기는 역할을 한다.
리턴타입 메소드이름(매개변수, ....) throws 예외클래스1, 예외클래스2, .... { }
떠넘길 예외 클래스를 쉼표로 구분해서 나열해주면 된다.
발생할 수 있는 예외의 종류별로 throws 뒤에 나열하는 것이 일반적이지만, throws Exception 만으로 모든 예외를 간단히 떠넘길 수도 있다.
throws 사용
public static void main(String[] args) { try { findClass(); } catch(ClassNotFoundException e) { System.out.println("클래스가 존재하지 않습니다."); } } public static void findClass() throws ClassNotFoundException { Class clazz = Class.forName("java.lang.String2"); }
main() 메소드에서 try-catch 블록을 사용해서 예외를 처리했다.
throws 사용하지 않음
public static void main(String[] args) { findClass(); } public static void findClass(){ try { Class clazz = Class.forName("java.lang.String2"); } catch (ClassNotFoundException e) { System.out.println("클래스가 존재하지 않는다."); } }
findClass() 메소드에서 try-catch 블록을 작성한 모습이고, 아래는 실행 결과이다.