예외처리의 종류는?

 

- 오라클 문서에서는 예외(Exception)을 3가지로 구분하고있다. 

1) Checked Exception

 : 컴파일러가 예외처리를 제대로 했는지, 확인하는 예외들을 의미한다. 다시 말하여, 프로그래머인 우리가 catch문에서 예외를 처리해주든, 혹은 throws를 통하여 처리해달라고 요청을 하여 예외를 처리할 수 있게끔해야 한다. 

 

2) Error

 : 프로그램 외부에서 발생하는 예외를 의미한다. 오라클 문서에서는 Error 를 예외로 분류하고 있으나, Error.java의 주석을 읽어보면 비정상적인 상황으로 기인한 Runtime Exception으로 간주되어 진다. 

 

3) Unchecked Exception(=Runtime Exception) 

 : 프로그램 내부에서 발생하는 예외이나, 컴파일러가 확인하지 않는 예외들을 의미한다. 이는 보통 실행시에 발생하는 예외들이다. 

 

현상만 놓고보자면, Checked Exception 이 발생되는 경우에는 컴파일이 안될 것이며, 그외는 런타임에서 발생한다

 

예외처리는 어떻게하는가? 

 

- 예외처리는 try-catch-(finally) 로 처리하면 된다. 

public void tryCatchFinally() {
        int[] array = new int[5];

        try {
            // 처리해야하는 코드 - (1)
            System.out.println(array[5]);

        }

        catch(NullPointerException e){
            // 예외가 잡혔을 때 처리할 코드 - (2)
            // 부모클래스를 먼저 catch 문의 조건으로 잡으면 컴파일이 되지 않음.  - (2.1)
                // java: exception java.lang.NullPointerException has already been caught
            // NullPointerException 이 아닌경우 해당 catch 문은 수행되지 않음 - (2.2)
            e.printStackTrace();

        }
        catch (Exception e) {
            // 예외가 잡혔을 때 처리할 코드 - (2)
            // Exception은 Throwable 을 상속받음 - (2.3)

            // printStackTrace - (2.4)
            e.printStackTrace();
            // getMessage() - (2.5)
            System.out.println(e.getMessage());
            // getCause - (2.6)
            System.out.println(e.getCause());

        }
        finally {
            // 예외 발생과는 상관없이 언제나 실행되는 코드, 생략가능 - (3)
            System.out.println("Finally Block is called");
        }
    }

위의 코드를 보면 

(1) : try 블록 내에서는 예외가 발생할 수 있는 코드를 작성한다. 

(2) : catch 블록 내에서는 해당하는 예외를 처리하는데, 

 (2.1) Exception 을 catch하는 경우 더 포괄적인 부분을 작성한 경우 컴파일이 되지 않는다. 이 예제 기준으로 만약  Throwable 혹은 Exception을 먼저 catch하게끔 작성한 경우 NullPointerException 예외는 검사되지 않는다. 

고로 Exception은 상세한 내용부터 catch하여 포괄적인 내용으로 catch 되어야 한다. 

 (2.2) ArrayIndexOutOfBoundsException 이 발생했는데, 다른 NullPointerException 같은걸로 잡으려면 안잡힌다. 

그렇지만 부모 클래스인 IndexOutOfBoundsException 으로 catch는 가능하다. 

 (2.4 ~ 2.6) 

  - printStackTrace :  예외가 발생했을 때의 호출스택을 보여준다

  - getMeassage : 예외가 왜 발생했는지를 알려준다

  - getCause : 예외의 종류 + 예외가 왜 발생했는지를 알려준다

 

(3) : finally 블록 내에서는 예외 발생과 상관없이 언제나 실행되는 코드들이며, 생략가능한 블록이다. 오라클 공식문서에서는 try block이 exit하기 전에 언제나 수행된다고 설명되어있다(다음 예외처리의 동작흐름에 대해 별도로 설명할 예정)

 

즉, 특별히 return 을 하지 않는 이상 try -> finally 또는 try -> 오류발생(오류 발생지점 이후 try문은 수행되지 않음) -> catch -> finally 이다. 다만 특이한 점이 있어 따로 흐름에 대해서는 다음 내용에 정리하겠다. 

 

try-with-resources ! 

try-with-resources는 자원해제(close()) 처리를 까먹는 경우를 예방하기 위함이다. 이렇게 사용하기 위해서는 

java.lang.AutoCloseable(Closeable포함) 인터페이스를 구현한 것들에 한해 사용이 가능하다. 

 

아래의 코드에서 out.write(b) 가 실패하는 경우, out 자원을 해제하기 위해 finally 블록에서 자원해제를 해주는 코드가 필여요하다. (애초에 try 블록에서 안하면 되지만 어쨋든 코드의 중복을 얘기하고 싶었다) 

private void writeByte(byte[] b) {
        // get current path
        String path = System.getProperty("user.dir");

        FileOutputStream out = null;
        File file = new File(path.toString()+"/writeBeforeJDK1_7.txt");
        try{
            out = new FileOutputStream(file, true);
            out.write(b); // Write byte[] in here - (1)
            out.close();  // Close File in here

        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        finally {
            // Check whether outputStream is closed or not. - (2)
            if (out !=null){
                try{
                    out.close();
                }
                catch (Exception e ){
                    // Handle Exception
                    e.printStackTrace();
                }
            }
        }

    }

위의 코드를 try-with-resources 를 활용하여 작성하면 이렇게 많은 생산성을 확보하고 실수가능성을 줄일 수 있다.

 

private void writeByteTryWithResources(byte[] toWrite){
        // get current path
        String path = System.getProperty("user.dir");

        File file = new File(path.toString()+"/writeAfterJDK1_7.txt");
        try(FileOutputStream out = new FileOutputStream(file, true)){
            out.write(toWrite);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
    }

 

 

예외처리는 어떻게 흘러가는가? 

위에서 소결론을 내렸으나, return 에 따라 특이한 부분이 존재하여 별도로 정리해보고자 한다. 

아래는 실제 return을 catch문과 finally문에 작성해본 코드이다. 

 public String noReturnInCatchAndFinally() {
        System.out.println("noReturnInCatchAndFinally method is Called!!");
        int[] array = new int[5];

        try {
            System.out.println("try Block is called");
            System.out.println(array[5]);
        }
        catch (Exception e) {
            System.out.println("catch Block is called");
            e.printStackTrace();
        }

        finally {
            System.out.println("finally Block is called");
        }
        return "endOfControl";

    }

    public String returnInCatch_noReturnInFinally() {
        System.out.println("returnInCatch_noReturnInFinally method is called!!");
        int[] array = new int[5];

        try {
            System.out.println("try Block is called");
            System.out.println(array[5]);
        }
        catch (Exception e) {
            System.out.println("catch Block is called");
            return "catch";
        }
        finally {
            System.out.println("finally Block is called");
        }
        // (1) try 문으로 이어짐.
        return "OutSideOffinally";
    }

    public String noReturnInCatch_returnInFinally() {
        System.out.println("noReturnInCatch_returnInFinally method is called!!");
        int[] array = new int[5];

        try {
            System.out.println("try Block is called");
            System.out.println(array[5]);
            return "try";
        }
        catch (Exception e) {
            System.out.println("catch Block is called");
        }
        finally {
            System.out.println("finally Block is called");
            // (2) try 문의 return은 사라짐
            return "OutSideOffinally";
        }
    }

이 코드를 디컴파일해보면, 

(1) catch 블록에 return 이 있는 경우 

 :별도의 String 변수를 생성하여, Exception이 발생하는 경우 이를 return 함 

(2) finally 블록에 return 이 있는 경우 

 : finally에만 return 이 남게됨. 이는 try, catch에 아무리 return 을 넣어도 결과적으로 finally에서 return을 통해 exit를 하기 때문에 컴파일러가 컴파일 시, try 및 catch 블록에 return 을 만들지 않음!!! 

 

이를 통해 얻는 교훈은, finally 블록안에는 절대 return 예약어를 사용하지 말것!!!!

 

 

예외는 어떻게 발생시키는가(+throws)?

// Exception handling by try-catch - (1)
    public void howToThrowExceptionWithTryCatch() {
        try {
            throw new Exception("예외 발생!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
// Exception handling with throws - (2)
public void howToThrowExceptionWithOutTryCatch() throws Exception {
    throw new Exception("예외 발생!");
}

// Throw Runtime Exception - (3)
public void throwUncheckedException() {
    throw new RuntimeException("Unchecked Exception!");
}

 (1) : throw 예약어를 통해 예외를 발생시키되, try-catch로 예외를 처리를 해주는 경우

 (2) : 메소드 시그니쳐에 throws 를 추가하여 메소드를 호출한 쪽에서 예외를 처리하게끔 요청하는 경우 

-> 호출한 쪽에서 예외를 처리(try-catch 혹은 throws)를 해야하는데, throws로 처리하는 경우, 프로그램의 시작점인 main메소드에서도 throws를 하는 경우 프로그램은 종료. 

 (3) : 컴파일러가 위의 1번과 2번 케이스처럼 예외처리를 요청하지 않음. 용어그대로, Unchecked Exception이기 때문!

 

 

커스텀한 예외는 어떻게 만드는가?

// extends Exception or RuntimeException
public class CustomException extends Exception {

    public CustomException() {
        super();
    }

    public CustomException(String message) {
        super(message);
    }

    public CustomException(String message, Throwable cause) {
        super(message, cause);
    }

    public static void main(String[] args) throws Exception {
        throw new CustomException("Custom Error", new Throwable("Error happens with..."));
    }
}

Exception 또는 RuntimeException을 상속받아서, message, cause 등을 다 받을 수 있게끔 처리하면 된다. 

 

'가치코딩 [Java] > 기초문법' 카테고리의 다른 글

[Java 문법] enum  (0) 2021.02.04
[Java 문법] 패키지  (1) 2021.02.03
[Java 문법] 클래스  (0) 2020.12.18
[Java 문법] 선택문 및 반복문  (0) 2020.12.08
[Java 문법] 자바 연산자  (0) 2020.11.28

+ Recent posts