아래 사진을 살펴보자.

Error는 아래와 같다.
- 메모리 부족, 스택 오버플로우와 같이 발생하면 복구할 수 없는 상황이다.
- 프로그램의 비 정상적 종류를 막을 수 없다.
- 미래에는 예외 처리 가능할 수도 있지만, 현재는 JVM이 다운되는 것이 최선의 방안이다.
Exception은 아래와 같다.
- 읽으려는 파일이 없거나 네트워크 연결이 안 되는 등 수습될 수 있는 상황이다.
- 프로그램 코드에 의해 수습될 수 있다.
- 대처 코드가 있으면 컴파일이 진행되는 Checked 계열(대처할 수 없는 상황을 대비함)
대처 코드가 없어도 컴파일이 진행되는 Unchecked 계열(코드를 고쳐야 함)이 있다.
※ Unchecked 계열의 예외는 예외 처리의 대상이 아니라 디버깅의 대상이 된다.
따라서 조건문으로 Unchecked 계열의 예외는 피해야 한다.
Java는 try ~ catch를 이용해 예외를 처리한다. 아래 코드를 살펴보자.
public static void main(String[] args) {
int[] intArray = { 10 };
try {
System.out.println(intArray[2]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("예외 처리 완료 : " + e.getMessage());
e.printStackTrace();
}
System.out.println("프로그램 종료합니다.");
}
실행 결과는 아래와 같다. 예외가 적절히 처리된 걸 알 수 있다.
예외 처리 완료 : 2
프로그램 종료합니다.
java.lang.ArrayIndexOutOfBoundsException: 2
at g_collection.exception.SimpleException.main(SimpleException.java:7)
try ~ catch 문의 흐름을 자세히 알아보자.

- try에서 예외가 발생하면 해당 Exception 클래스의 객체 생성 후 이를 던진다.
(throw new XXException)- 던져진 exception을 처리할 수 있는 catch에서 받은 후 예외 처리한다.
(exception을 catch가 처리하지 못하면 예외 처리는 실패한다.)- 예외 처리가 끝나면 try ~ catch 문을 벗어난다.
Throwable 클래스의 주요 메서드는 아래와 같다.
- public String getMessage() : 발생된 예외에 대한 구체적인 메시지를 반환한다.
- public Throwable getCause() : 예외의 원인인 Throwable 객체 혹은 null을 반환한다.
- public void printStackTrace() : 메서드 호출 스택을 출력한다.
다중 예외 처리 시 주의해야 할 점은 아래와 같다.
- 상위 타입의 예외가 뒤에 있는 예외보다 위에 선언되면 뒤에 있는 catch가 동작하지 못하므로 예외가 발생한다.
- try에서 예외 발생 시 가장 상위 catch부터 예외인 지 살펴본다.
그리고 적절한 catch를 만나면 예외 처리 후 try ~ catch 문을 나간다.- 예외는 상황별로 처리하는 걸 권장한다. 따라서 Exception e 같은 예외 처리는 지양한다.
- 모든 예외를 세분화하는 것도 권장하지 않는다. |를 이용해 여러 개의 Exception을 처리할 수 있다.
(ClassNotFoundException | FileNotFoundException e)
아래 코드는 다중 예외 처리의 예시이다.
public static void main(String[] args) {
try {
Class.forName("abc.def");
new FileInputStream("abc.txt");
DriverManager.getConnection("some");
} catch (ClassNotFoundException e) {
// Exception e로 하면 아래 catch들 예외 발생
System.out.println("클래스를 찾을 수 없습니다.");
} catch (FileNotFoundException e) {
System.out.println("파일을 찾을 수 없습니다.");
} catch (SQLException e) {
System.out.println("DB 접속 실패");
}
System.out.println("프로그램 정상 종료");
}
출력 결과는 아래와 같다.
클래스를 찾을 수 없습니다.
프로그램 정상 종료
finally는 예외 발생 여부와 상관없이 항상 실행된다. 아래 코드를 살펴보자.
public static void main(String[] args) {
int num = new Random().nextInt(2); // 0 혹은 1
try {
System.out.println("code 1, num: " + num);
int i = 1 / num;
System.out.println("code 2 - 예외 없음: " + i);
return;
} catch (ArithmeticException e) {
System.out.println("code 3 - exception handling 완료");
} finally {
System.out.println("code 4 - 언제나 실행");
}
System.out.println("code 5");
}
예외가 발생하면 1 -> 3 -> 4 -> 5 순으로,
예외가 발생하지 않으면 1 -> 2 -> 4 순으로 실행된다.
즉, try에 return이 있음에도 finally가 실행된다.
finally를 사용하면 코드가 지저분해질 수 밖에 없다. 아래 코드를 살펴보자.
public void useStream() {
FileInputStream fileInput = null;
try {
fileInput = new FileInputStream("abc.txt");
fileInput.read();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInput != null) {
try {
fileInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
따라서 JDK 1.7 이상부터 AutoCloseable 인터페이스를 구현한 객체들에 대해
자동으로 close를 호출한다.
아래 코드를 살펴보자.
public void useStream() {
try (FileInputStream fileInput = new FileInputStream("abc.txt")) {
fileInput.read();
} catch (IOException e) {
e.printStackTrace();
}
}
예외 발생 여부에 상관없이 close가 실행된다.
※ AutoCloseable 인터페이스를 implements하는 클래스는 아래와 같다.

예외 처리의 책임을 호출한 곳으로 전달하는 방법이 throws이다.
아래 코드를 살펴보자.
public class CTest {
void aa() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("a.txt");
}
public CTest() throws FileNotFoundException {
aa();
}
public static void main(String[] args) throws FileNotFoundException {
new CTest();
}
}
aa() -> CTest() -> main() 순으로 예외 처리의 책임이 전달된다.
※ 메서드 오버라이딩 시 자식 메서드에선 부모 메서드보다 작거나 같은 범위의
throws만 가능하다.
Checked Exception은 Exception 클래스를 상속받아서,
Unchecked Exception은 RuntimeException 클래스를 상속받아서 Exception을 작성한다.
아래 코드는 Checked Exception을 작성한 예시이다.
class MyException extends Exception {
String msg;
public MyException(String msg) {
super(msg);
this.msg = msg;
}
@Override
public String toString() {
return "MyException [" + msg + "]";
}
}
public class DTest {
public DTest() { // 처리 가능할 때 try ~ catch
try {
m1();
} catch (MyException e) {
System.out.println("오류 처리 : " + e);
System.out.println("오류 처리 : " + e.toString());
System.out.println("오류 처리 getMessage() : " + e.getMessage());
}
}
void m1() throws MyException { // 전파 목적이므로 throws
int type = 1;
if (type == 0) {
System.out.println("정상");
} else {
throw new MyException("오류");
}
}
public static void main(String[] args) throws MyException {
new DTest();
}
}
출력 결과는 아래와 같다.
오류 처리 : MyException [오류]
오류 처리 : MyException [오류]
오류 처리 getMessage() : 오류
toString()을 오버라이딩하거나 super로 getMessage()에 인자를 전달해 오류의 내용을 출력한다.
주로 후자를 선택한다.