예외 처리

dev_314·2022년 11월 15일
0

자바 라이브 스터디

목록 보기
15/18

| 백기선님의 라이브 스터디를 참고하여 작성한 게시물입니다.

참고
Types of Exceptions in Java

Types of Error

Compile Error

컴파일 시에 발생하는 에러 (Java Source Code → Byte Code)

Runtime Error

실행 시에 발생하는 에러 (Byte Code → Machine Code)

Logic Error

실행은 되지만, 의도와 다르게 동작하는것

물품 제고가 -1개인데 주문 가능
hp가 -1인데도 게임 오버가 안 됨

자바가 제공하는 예외 계층 구조

이미지 출처: https://rollbar.com/blog/java-exceptions-hierarchy-explained/

자바는 Runtime Error를 ExceptionError로 구분함

Exception과 Error의 차이는?

Error

OutOFMemoryError, StackOverFlowError등 발생하면 복구할 수 없는 심각한 오류

Exception

발생하더라도 수습할 수 있는 덜 심각한 오류

RuntimeException과 RE가 아닌 것의 차이는?

Checked Exception

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();
    }
}

Unchecked Exception

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로 잡아주지 않았을 때)

Throwable과 메소드

ErrorException의 부모 Class
Class인데 왜 interface 네이밍을 했는지는 모르겠다

getStackTrace, printStackTrace

발생한 에러가 어떤 메소드들을 거쳐 발생했는지 파악 가능

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();
    }
}

콘솔에 다음과 같은 로그가 찍힌다.

getMessage

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("예외 발생");
    }
}

자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

finally is inevitable

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가 수행된다.

checked Exception은 까다롭다.

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();
    }
}

이제 문제가 안 생긴다.

Exception 상속 계층

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류 예외를 전부 잡기 때문이다.

이러한 코드는 파악하기 힘들다. 예외는 구체적으로 잡아야 한다.

catch multiple Exception at Once

자바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을 따로 검증하는건 중복이기 때문이다.

다시 말하지만, 예외는 좁은 범위로 잡아야한다.

try - with -resource

자바 7에 생긴 기능이라고 한다.

리소스를 반납할 때 사용하는 구문이라고 하는데 아직은 와닫지 않아서 작성하지 않았다.

나중에 작성해보자.

커스텀한 예외 만드는 방법


예외를 상속해서 커스텀 예외를 만들 수 있다.

profile
블로그 이전했습니다 https://dev314.tistory.com/

0개의 댓글