남궁성님의 Java의 정석(3rd Edition)을 보고 정리한 글입니다.
1. 에러의 종류
에러의 종류에는 논리적 에러,컴파일 에러, 런타입 에러가 있다.
a. 논리적 에러
- 개발자 실수(논리적 오류)로 인해 실행은 되지만, 의도와 다르게 동작하는 것
b. 컴파일 에러
- 컴파일 시점에 발생하는 에러
- ex) 문법을 잘못 작성해서 발생하는 에러
- ex) 타입체크 에러
c. 런타임 에러
- 실행 시 발생하는 에러
런타임 시 발생할 수 있는 오류에는 2가지가 있다.
- 에러(error)
- 프로그램 코드에 의해서 수습될 수 없는 심각한 오류로 시스템이 종료되어야 할 수준
- ex) StackOverflowError: 재귀가 지속되어 호출 깊이가 깊어져 발생하는 오류
- ex) OutOfMemoryError: JVM 메모리 부족으로 객체를 할당할 수 없을 때 발생하는 오류
- 예외(exception)
- 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류로 개발자의 실수나, 사용자의 영향에 의해 발생
- 개발자가 미리 예측하여 방지할 수 있기 때문에 예외처리를 해야한다.
- ex) NullPointException: 객체가 필요한 경우인데 null을 사용할 경우 발생할 수 있는 예외
- ex) IllegalArgumentException: 메서드가 허가되지 않거나 부적절한 인수를 받았을 경우 발생할 수 있는 예외
컴파일 에러는 컴파일 시 발생하는 에러이기 때문에 실행 전에 수정하면 되지만, 런타임 에러의 경우는 실행 도중에 발생하는 에러이기 때문이 이를 대비해야 한다.
2. 예외 클래스 계층 구조
- 자바에서는 Exception와 Error를 클래스로 정의했다.
- Throwable 클래스는 Object를 제외하고 Excetion, Error 클래스의 최상위 클래스로 대표적인 2가지 메서드
- public String getMessage(): 발생한 예외클래스의 인스턴스에 저장된 메시지 반환
- public void printStackTrace(): 예외가 발생tl 호출 스택에 있었던 메서드 정보와 예외 메시지 출력
a. Exception 계층 구조
Exception 클래스는 두 개의 그룹으로 나눌 수 있다.
- Exception 클래스와 그 자손(checked exception)
- RuntimeException 클래스를 상속하지 않은 예외 클래스
- 반드시 명시적으로 예외처리를 해야한다.
- ex) ClassNotFoundException: 클래스를 찾지 못할 때 발생하는 예외
public class Main {
public static void main(String[] args) {
System.out.println("hello");
throw new Exception(); // 예외처리 필수
}
}
실행결과
java: unreported exception java.lang.Exception; must be caught or declared to be thrown
- RuntimeException과 그 자손(unchecked exception)
- 실행 중(runtime)에 발생할 수 있는 예외
- 주로 프로그래머의 실수에 의해서 발생하는 예외
- 예외 처리를 강제적으로 하지 않아도 된다.
- ex)ArrayIndexOutOfBoundException: 배열의 범위를 벗어날 때 발생하는 예외
public class Main {
public static void main(String[] args) {
System.out.println("hello");
throw new RuntimeException(); // 예외처리를 하지 않아도 정상적으로 컴파일이 되어 main메서드 실행
}
}
실행결과
hello
Exception in thread "main" java.lang.RuntimeException
at org.example.exception.Main.main(Main.java:11)
3. 예외 처리를 위한 try-catch
try {
// 예외가 발생할 가능성 있는 문장
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
// Exception2가 발생했을 경우, 이를 처리하기 위한 문자
}
- if, for문과 달리 { }(괄호)를 생략할 수 없다.
- catch 블럭을 여러개 사용하여 다양한 Exception에 대한 처리를 할 수 있다.
try {
try {
} catch (Exception e) {
}
} catch (Exception e) {
try {
} catch (Exception e) { // 에러 발생(참조변수 명)
}
}
- try-catch 블럭안에 또 다른 try-catch문을 사용할 수 있다.
- catch 블럭 내의 try-catch문에 선언되어 있는 참조변수 명은 서로 달라야 한다.
4. try-catch문의 흐름
a. try 블럭 내에서 예외가 발생한 경우
- 발생한 예외와 일치하는 catch블럭이 있는지 확인
- 일치하는 catch 블럭을 찾는다. (못 찾을 경우 예외가 처리되지 않음)
- catch 블럭 내의 문장을 수행한다.
- try-catch 블럭을 빠져나가서 수행을 계속한다.
b. try 블럭 내에서 예외가 발생하지 않은 경우
- try-catch 블럭을 빠져나가서 수행을 계속한다.
참고: catch 블럭(예외클래스)를 찾을 때 instancof 연산자를 이용해서 검사하기 때문에 자식 타입의 예외가 발생하면 부모 타입의 예외 클래스의 catch 블럭에 일치
예외가 발생해서 catch 블럭을 찾은 경우
System.out.println(1);
try {
System.out.println(2);
System.out.println(0 / 0); // ArithmeticException 발생
System.out.println(3); // 실행되지 않음.
} catch (Exception e) {
System.out.println(4);
}
System.out.println(5);
실행결과
1
2
4
5
예외가 발생했지만 catch 블럭을 찾지 못하는 경우
- 예외를 처리하지 못한 것이기 때문에 메서드 호출 스택을 따라 올라가 처리되지 않은 예외로 간주되어 비정상적으로 종료.
System.out.println(1);
try {
System.out.println(2);
System.out.println(0 / 0); // ArithmeticException 발생
System.out.println(3); // 실행되지 않음.
} catch (NullPointerException e) {
System.out.println(4);
}
System.out.println(5);
실행결과
1
2
Exception in thread "main" java.lang.ArithmeticException: / by zero at org.example.exception.Main.main(Main.java:11)**
try- catch 예제1
System.out.println(1);
try {
System.out.println(2);
System.out.println(0 / 0); // ArithmeticException 발생
System.out.println(3);
} catch (Exception e) {
System.out.println(4);
} catch (ArithmeticException e) { // 에러 발생
System.out.println(5);
}
System.out.println(6);
- ArithmeticException은 Exception의 자식 클래스이다.
- catch문에서 자식 Exception은 부모 Exception보다 뒤에 올 경우 컴파일 에러가 발생한다.
try- catch 예제2
System.out.println(1);
try {
System.out.println(2);
System.out.println(0 / 0); // ArithmeticException 발생
System.out.println(3);
} catch (ArithmeticException e) {
System.out.println(4);
} catch (Exception e) {
System.out.println(5);
}
System.out.println(6);
실행 결과
1
2
4
6
- ArithmeticException이 발생할 경우 ArithmeticException catch 블럭과 Exception catch 블럭둘 다 해당된다.
- 하지만 자식 Exception(먼저 만나는 catch) 블럭 문장만 수행한다.
c. try 블럭 내에서 예외가 발생하지 않은 경우
- catch 블럭을 거치지 않고 try-catch 블럭을 빠져나가서 수행을 계속한다.
System.out.println(1);
try {
System.out.println(2);
System.out.println(1 / 1);
System.out.println(3);
} catch (ArithmeticException e) {
System.out.println(4);
}
System.out.println(6);
실행결과
1
2
1
3
6
5. 멀티 catch 블럭
JDK1.7부터 여러 catch 블럭을 ‘|’ 기호를 이용해서 하나의 블럭에 여러 Exception을 처리할 수 있도록 추가되었다.
try {
// ...
} catch (ArithmeticException | NullPointerException e) {
}
try {
// ...
} catch (ArithmeticException | Exception e) { // 에러 발생
}
- 부모와 자식관계에 있다면 컴파일 에러가 발생한다.
- ‘|’ 기호는 논리 연산자가 아닌 기호이다.
6. 예외 발생시키기
throw 키워드를 사용해서 개발자가 고의로 예외를 발생시킬 수 있다.
Exception e = new Exception("예외 발생"); // 1. 예외 클래스의 객체를 만든다.
throw e; // 2. throw 키워드를 이용해서 예외를 발생시킨다.
throw 예제
try {
Exception e = new Exception("예외 발생");
throw e;
} catch (Exception e) {
e.printStackTrace();
}
실행결과
java.lang.Exception: 예외 발생 at org.example.exception.Main.main(Main.java:9)**
7. 메서드 예외 선언하기
throws 키워드를 사용해서 메서드에 예외를 선언할 수 있다.
호출된 메서드로 예외를 던지는 역할을 한다.
- Checked Exception의 경우 상위 메서드로 예외를 던질 때 명시적으로 throws를 사용해야 한다.
- UnChecked Exception의 경우에는 예외처리를 강제 하지 않기 때문에 예외 처리를 하지 않을 경우 상위 메서드로 예외를 던져 throws 키워드와 동일하게 동작한다.
// throws 기본 사용방법
void method() throws Exception1, Exception2 {
}
// 오버라이딩 시 적용
public class Parent {
void method() throws Exception {
}
}
public class Child extends Parent {
@Override
void method() throws Exception {
super.method();
}
}
- ,(쉼표)로 구분해서 여러개의 예외를 선언할 수 있다.
- 부모 클래스의 메서드에 예외가 선언되어 있으면 오버라이딩 시에도 적용된다.
메서드 예외 예제
public class Main {
public static void main(String[] args) throws Exception {
method1();
}
private static void method1() throws Exception {
method2();
}
private static void method2() throws Exception {
throw new Exception();
}
}
실행결과
Exception in thread "main" java.lang.Exception
at org.example.exception.Main.method2(Main.java:16)
at org.example.exception.Main.method1(Main.java:12)
at org.example.exception.Main.main(Main.java:8)
- method2()에서 예외가 발생했는데 예외를 처리하지 않고 method1()로 예외를 던진다.
- method1()도 예외를 처리하지 않아 main() 메서드에 예외를 던진다.
- main() 메서드도 예외를 처리하지 않아 프로그램이 종료되고, jvm의 예외처리기가 예외를 받아서 에러의 원인을 화면에 출력한다.
8. finally 블럭
finally문은 try-catch문 끝에 선택적으로 덧붙여 사용할 수 있다.
try {
// 예외 발생 가능성 있는 문장
} catch (Exception e) {
// 예외 처리를 위한 문장
} finally {
// 예외 발생여부에 관계없이 항상 수행되는 문장
}
예외 발생할 경우
try → catch → finally
예외 발생 x 경우
tty → fianaaly
try-catch 문장 수행중에 return 문을 만나도 finally 문장은 수행된다.
public class Main {
public static void main(String[] args) {
try {
System.out.println("method1 ()의 try 블럭이 실행 되었습니다.");
return;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("method1()의 finally 블럭이 실행되었습니다.");
}
System.out.println("method1()이 이미 종료되었습니다.");
}
}
**실행결과**
method1 ()의 try 블럭이 실행 되었습니다.
method1()의 finally 블럭이 실행되었습니다.
- return을 만나면 실행중인 메서드는 종료되지만, finally 블럭이 실행되고 나서 메서드가 종료된다.
9. try-with-resource문 (자동 resource 반환)
try-catch-finally문을 사용한 resource 반환 예제
public class Main {
public static void main(String[] args) {
FileInputStream fis;
DataInputStream dis = null;
int score = 0;
int sum = 0;
try {
fis = new FileInputStream("src/score.txt");
dis = new DataInputStream(fis);
while (true) {
score = dis.readInt();
System.out.println(score);
sum += score;
}
} catch (EOFException e) {
System.out.println("점수의 총합은 " + sum + "입니다");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if(dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
파일로부터 데이터를 읽는 작업이 끝나면 DataInputStream을 close 하는 코드이다. 코드가 복잡하다… try-with-resources문을 사용하면 개선할 수 있다.
try-with-resource문을 사용한 자동 resource 반환 예제
public class Main {
public static void main(String[] args) {
int score = 0;
int sum = 0;
try (FileInputStream fis = new FileInputStream("src/score.txt");
DataInputStream dis = new DataInputStream(fis)) {
while (true) {
score = dis.readInt();
System.out.println(score);
sum += score;
}
} catch (EOFException e) {
System.out.println("점수의 총합은 " + sum + "입니다");
} catch (IOException e) {
e.printStackTrace();
}
}
}
try 괄호() 안에 객체를생성하지는 문장을 넣으면 이 객체는 close()를 호출하지 않아도 try 블럭을 벗어나는 순간 자동으로close()가 호출된다.
10. 사용자 정의 예외
필요에 따라 새로운 예외 클래스를 정의하여 사용할 수 있다. 보통 Exception 클래스(Checked Exception), RuntimeException 클래스(Unchecked Exception)로부터 상속받아 예외 클래스를 사용한다.
사용자 정의 예외 예제
public class MyException extends Exception {
private final int ERR_CODE; // 에러 코드 값을 저장하기 위한 필드
MyException(String msg, int errCode) {
super(msg);
ERR_CODE = errCode;
}
MyException(String msg) {
this(msg, 100);
}
public int getErrCode() {
return ERR_CODE;
}
}
public class Main {
public static void main(String[] args) {
try {
System.out.println("hello");
throw new MyException("내가 만든 예외");
} catch (MyException e) {
e.printStackTrace();
}
}
}
실행결과
hello
org.example.exception.MyException: 내가 만든 예외
at org.example.exception.Main.main(Main.java:13)
checked exception은 try-catch 문 등을 사용하여 반드시 예외를 처리해야 하기 때문에 코드가 복잡해진다. 상황에 따라 다르겠지만, 일반적으로 코드 복잡성 등을 고려해서 unchecked exception을 사용하는 것이 좋다.
'Programming > Java' 카테고리의 다른 글
[Java] 컬렉션 프레임워크와 계층 구조 (0) | 2023.11.03 |
---|---|
[Java] .java(소스파일) vs. .class(바이트코드파일) vs. .jar (0) | 2023.10.20 |
[Java] 내부 클래스(inner class)란? (0) | 2023.10.16 |
[Java] 다형성 (추상클래스 - 인터페이스) (0) | 2023.10.16 |
[Java] 자바의 제어자 정리 (0) | 2023.10.16 |