| 백기선님의 라이브 스터디를 참고하여 작성한 게시물입니다.
컴파일 시에 발생하는 에러 (Java Source Code → Byte Code)
실행 시에 발생하는 에러 (Byte Code → Machine Code)
실행은 되지만, 의도와 다르게 동작하는것
물품 제고가 -1개인데 주문 가능
hp가 -1인데도 게임 오버가 안 됨
이미지 출처: https://rollbar.com/blog/java-exceptions-hierarchy-explained/
자바는 Runtime Error를 Exception
과 Error
로 구분함
OutOFMemoryError, StackOverFlowError등 발생하면 복구할 수 없는
심각한 오류
발생하더라도 수습할 수 있는
덜 심각한 오류
IOException과 같은 RuntimeException가 아닌 Exception들은 Chekced Exception이라고도 불린다.
cheked?
누군가 예외를 확인한다는 의미다.
누군가?
바로 컴파일러다.
import java.io.IOException;
public class Test {
public static void main(String[] args) {
throw new IOException(); // Unhandled exception: java.io.IOException
}
}
Checked Exception은 반드시 어디에선가, 누군가가(호출 메소드) 처리해줘야 한다.
import java.io.IOException;
public class Test {
public static void main(String[] args) {
try {
throw new IOException(); // no error
} catch (IOException e) {
System.out.println("error occurred");
}
}
}
또는
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
throw new IOException();
}
}
RuntimeException은 Unchecked Exception
이라고도 불린다.
Runtime에서 발생할 만한 예외 상황들을 컴파일 단게에서 확인할 방법이 없다.
그러므로 RuntimeException는 check될 필요 없다.(될 수가 없다)
public class Test {
public static void main(String[] args) {
throw new RuntimeException(); // no error
}
}
물론 RuntimeException도 예외기 때문에, check해줘도 된다.
public class Test {
public static void main(String[] args) {
try {
throw new RuntimeException();
} catch (RuntimeException e) {
System.out.println("error occurred");
}
}
}
또는
public class Test {
public static void main(String[] args) throws RuntimeException {
throw new RuntimeException();
}
}
Unchecked Exception은 명시적으로 throws
를 하지 않아도 알아서 throws
를 한다. (try - catch로 잡아주지 않았을 때)
Error
와 Exception
의 부모 Class
Class인데 왜 interface 네이밍을 했는지는 모르겠다
발생한 에러가 어떤 메소드들을 거쳐 발생했는지 파악 가능
public class Test {
public static void main(String[] args) {
try {
A();
} catch (RuntimeException e) {
e.printStackTrace();
StackTraceElement[] stackTraces = e.getStackTrace();
for (StackTraceElement stackTrace : stackTraces) {
System.out.println("MethodName " + stackTrace.getMethodName());
System.out.println("LineNumber " + stackTrace.getLineNumber());
System.out.println("ClassName " + stackTrace.getClassName());
System.out.println("FileName " + stackTrace.getFileName());
System.out.println();
}
}
}
private static void A() {
B();
}
private static void B() {
throw new RuntimeException();
}
}
콘솔에 다음과 같은 로그가 찍힌다.
package temp;
public class Test {
public static void main(String[] args) {
try {
A();
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // 예외 발생
}
}
private static void A() {
B();
}
private static void B() {
throw new RuntimeException("예외 발생");
}
}
public class Test {
public static void main(String[] args) {
A();
}
private static void A() {
try {
B();
} catch (RuntimeException e) {
System.out.println(1);
return;
} finally {
System.out.println(2);
}
System.out.println(3);
}
private static void B() {
throw new RuntimeException();
}
}
과연 위 코드의 실행 결과는 어떻게 될까
1
2
catch에서 return을 해도, finally가 수행된다.
import java.io.IOException;
public class Test {
public static void main(String[] args) {
A();
}
private static void A() {
try {
B();
} catch (IllegalArgumentException e) { // 이상한 Exception을 처리하지만, 컴파일 에러는 아니다.
System.out.println(1);
return;
} finally {
System.out.println(2);
}
System.out.println(3);
}
private static void B() {
throw new IOException(); // Unhandled exception: java.io.IOException
}
}
위 코드와 동일하나, Unchecked Exception이 아닌, Checked Exception을 throw한다.
위 코드는 정상적으로 컴파일 되지 않는다.
메소드 B에 나한테 Exception 발생할 수도 있음
을 나타내는 throws
가 없기 때문에, 컴파일러가 여기서 예외가 발생하는겨 아닌겨~
하기 때문이다.
따라서 다음과 같이 변경해 보자.
package temp;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
A();
}
private static void A() {
try {
B(); // Unhandled exception: java.io.IOException
} catch (IllegalArgumentException e) {
System.out.println(1);
return;
} finally {
System.out.println(2);
}
System.out.println(3);
}
private static void B() throws IOException {
throw new IOException();
}
}
메소드 B는 throws
를 통해 나한테서 IOException이 발생할 수도 있음
을 나타냈다.
그런데 이제는 메소드 A에서 난리다.
메소드 A에 IOException에 대한 대응 방법이 명시되지 않았기 때문이다.
Checked Exception은 컴파일러가 알아들을 수 있도록 명시적으로 Exception을 핸들해줘야 한다.
다음과 같이 수정하자.
import java.io.IOException;
public class Test {
public static void main(String[] args) {
A();
}
private static void A() {
try {
B();
} catch (IOException e) {
System.out.println(1);
return;
} finally {
System.out.println(2);
}
System.out.println(3);
}
private static void B() throws IOException {
throw new IOException();
}
}
이제 문제가 안 생긴다.
package temp;
public class Test {
public static void main(String[] args) {
try {
A();
} catch (Exception e) {
e.printStackTrace();
}
}
private static void A() {
throw new IllegalArgumentException();
}
}
catch
에서 IllegalArgumentException
의 조상인 Exception
을 검증해도 작동한다. catch는 Exception 상속 계층에 따라, 명시된 XX류 예외를 전부 잡기 때문이다.
이러한 코드는 파악하기 힘들다. 예외는 구체적으로 잡아야 한다.
자바7부터 한 번에 여러 예외를 잡을 수 있게 되었다.
try {
...
} catch (ExceptionA | ExceptionB | ExceptionC e) {
...
}
그런데 예외들이 상속 관계를 이루면 컴파일러가 화를 낸다.
try {
...
} catch (Exception | IllegalArgumentException e) {
// Types in multi-catch must be disjoint: 'java.lang.IllegalArgumentException' is a subclass of 'java.lang.Exception'
e.printStackTrace();
}
이미 Exception
이라는 넓은 범위에서 예외를 잡는데, 작은 범위의 IllegalArgumentException
을 따로 검증하는건 중복이기 때문이다.
다시 말하지만, 예외는 좁은 범위로 잡아야한다.
자바 7에 생긴 기능이라고 한다.
리소스를 반납할 때 사용하는 구문이라고 하는데 아직은 와닫지 않아서 작성하지 않았다.
나중에 작성해보자.
예외를 상속해서 커스텀 예외를 만들 수 있다.