본문 바로가기

기술 서적/자바의 정석

[자바의 정석] ch8. 예외처리

 

>>프로그램 오류

:  에러는 어쩔 수 없지만 예외는 처리해야 함

에러 : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
예외 : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 

>>예외 클래스의 계층구조

- Object를 가장 상위계층으로

- Throwable => Exception + Error

- RunException 클래스 -> 프로그래머의 실수로 발생하는 예외(예외처리 선택)
- Exception 클래스 들 => 사용자의 실수와 같은 외적인 요인에 의해 발생(예외처리 필수)

 

>>예외 처리하기

: 프로그램 실행시 발생가능한 예외에 대비한 코드를 작성하는 것

=> 프로그램 비정상 종료를 막음

 

try-catch문

- 1. 하나의 메서드에 여러개의 try-catch문 사용 가능

- 2. try블록 / catch 블록 에 또다른 try-catch문 포함 가능

 

ex)

public class ExceptionEx3 {
    public static void main(String[] args) {
        int num= 100;
        int result=0;

        for(int i=0; i<10; i++){
            try{
                result= num/(int)(Math.random()*10);
                System.out.println(result);
            }catch(ArithmeticException e){
                System.out.println(0);
            }
        }
    }
}

 

random값이 0일 때 => ArthmeticException

 

>>실행결과

20
33
14
12
20
11
0 => 예외처리
33
25
16

 

>>try-catch문에서의 흐름

=> try블럭에서 예외가 발생하면, 이후의 코드는 수행되지 않으므로

코드의 범위를 잘 선택해야 함

 

>> 예외의 발생과 catch 블럭

- 괄호 안에 예외와 같은 타입의 참조변수 선택

- 차례로catch 블럭을 내려가면서 catch블럭 괄호 안에 참조변수의 종류와 생성된 예외 클래스의 인스턴스에 instanceof연산자를 이용해서 검사하게 됨

 

=> Exception 클래스 처리 == 어떤 종류의 예외라도 처리됨

package ch8;

public class ExceptionEx7 {
    public static void main(String[] args) {
        System.out.println(1);
        System.out.println(2);

        try{
            System.out.println(3);
            System.out.println(0/0); //예외 발생
            System.out.println(4); // 실행되지 않음
        }catch( ArithmeticException ae){
            if( ae instanceof ArithmeticException){
                System.out.println("true");
                System.out.println("ArithmeticException");
            }
        }catch(Exception e) //이미 위에서 처리가 되었기에 실행 x
        {
            System.out.println("Exception");
        }
        System.out.println(6);
    }
}

 

>>실행결과

1
2
3
true
ArithmeticException
6

 

=> 첫번째 catch문에서 일치하는 블럭을 찾아씩 때문에 두번째 catch 블럭은 검사x

 

>>printStackTrace()와 getMessage()

printStackTrace()
: 예외 발생 당시 호출 스택에 있었던 메서드 정보와 예외메시지를 한번에 출력

getMessage()
: 예외 인스턴스에 저장된 메시지를 얻을 수 있음

 

ex)

public class ExceptionEx8 {
    public static void main(String[] args) {
        System.out.println(1);
        System.out.println(2);
        try{
            System.out.println(3);
            System.out.println(0/0);
            System.out.println(4);
        }catch(ArithmeticException ae){
            ae.printStackTrace();
            System.out.println("예외 메시지 : " +ae.getMessage());
        }
        System.out.println(6);
    }
}

 

>>실행결과

1
2
3

java.lang.ArithmeticException: / by zero
at ch8.ExceptionEx8.main(ExceptionEx8.java:9)
예외 메시지 : / by zero
6

 

>>멀티 캐치 블럭

: |를 이용해서 하나의 catch블록으로 합칠 수 있게 됨

: 조상과 자손 관계에 있으면 컴파일 에러 => 조상 클래스 하나만 예외 처리해도 되므로

: 둘 중 어떤 예외가 발생한 것인지 알 수 없으므로 instacneof로 객체 확인 필요

 

>>예외 발생시키기

- Exception 인스턴스 생성자에 String을 넣으면 => String이 인스턴스의 메시지로 저장

- Exception 클래스들은 오류가능성이 있는데 예외처리를 안하면 컴파일x =>checked

- RuntimeException 클래스들은 오류가능성이 있어도 컴파일이 됨 => unchecked

 

 

>>메서드에 예외 선언하기

- 메서드 선언부에 throws를 사용해서 발생가능한 예외를 적어주면 됨

 

=>  메서드를 사용하는 사람이 보았을 때, 어떤 예외가 처리되어야 하는지 파악가능

=> 보통 Exception 클래스들(반드시 처리되어야 하는 것들)만 적음

 

ex)

 

알 수 있는 것

- 3개의 메서드(method2, method1, main)이 스택에 있었음

- method2에서 예외가 발생

- main -> method1 -> method2를 호출

 

=> 예외처리를 분담할 수도, 한 메서드에서 자체적으로 처리하게 할 수도 있음

 

>>finally 블록

- 예외 발생여부와 상관없이 실행

- 주로 try와 catch블럭에 공통적으로 들어가하는 부분을 포함

- return이 되어도 finally 실행 후 종료

 

ex) finally를 실행이 보장된다. => return되어도 실행!!

package ch8;

public class FinallyTest {
    public static void main(String[] args) {
        FinallyTest.method1();
        System.out.println("method1의 수해을 마치고 main으로 돌아옴");
    }

    static void method1(){
        try{
            System.out.println("method1이 호출됨");
            return;
        }catch(Exception e){
            e.printStackTrace();
        }finally{ //실행됨
            System.out.println("method1()의 finally블록이 실행되었습니다.");
        }
        System.out.println(1); //실행안됨

    }
}

 

>>실행결과

method1이 호출됨
method1()의 finally블록이 실행되었습니다.
method1의 수해을 마치고 main으로 돌아옴

 

>>try-with-resource 자원 자동반환

- 괄호 안에 객체를 생성하는 문장을 넣으면 try문을 벗어나느 순간 자동적으로 close() 호출

public class TryWithResourceEx {
    public static void main(String[] args) {
        try(CloseableResource cr = new CloseableResource()){
            cr.exceptionWork(false);
        }catch(WorkException e){
            e.printStackTrace();
        }catch(CloseException e){
            e.printStackTrace();
        }
        
        System.out.println();
        
        try(CloseableResource cr = new CloseableResource()){
            cr.exceptionWork(true);
        }catch(WorkException e){
            e.printStackTrace();
        }catch(CloseException e){
            e.printStackTrace();
        }
    }
}

class CloseableResource implements AutoCloseable{

    public void exceptionWork(boolean flag) throws WorkException{
        System.out.println("ExceptionWork가 호출됨");
        if(flag){
            throw new WorkException("WorkException 발생!!");
        }
    }
    public void close() throws CloseException{
        System.out.println("close()가 호출됨");
        throw new CloseException("CloseException발생!!");
    }
}

class WorkException extends Exception{
    WorkException(String msg){super(msg);}
}

class CloseException extends Exception{
    CloseException(String msg){super(msg);}
}

 

>>실행결과

ExceptionWork가 호출됨
close()가 호출됨
ch8.CloseException: CloseException발생!!
at ch8.CloseableResource.close(TryWithResourceEx.java:35)
at ch8.TryWithResourceEx.main(TryWithResourceEx.java:9)


ExceptionWork가 호출됨
close()가 호출됨

ch8.WorkException: WorkException 발생!!
      at ch8.CloseableResource.exceptionWork(TryWithResourceEx.java:30)
      at ch8.TryWithResourceEx.main(TryWithResourceEx.java:16)
          Suppressed: ch8.CloseException: CloseException발생!!
                at ch8.CloseableResource.close(TryWithResourceEx.java:35)
                at ch8.TryWithResourceEx.main(TryWithResourceEx.java:15)

 

=> 첫번째 try-catch에서는 CloseException

=> 두번째 try-catch에서는 WorkException + CloseException(Surppressed)

 

 

>>사용자 정의 예외 만들기

- Exception 혹은 RuntimeException 클래스를 상속받아 만듦

 

=> 만약 exception을 상속했다면 반드시, 예외처리를 해주어야 함 == checked

=> 만약 RuntimeException을 상속했다면 예외처리를 선택적으로 == unchecked(트렌드)

 

>>예외 되던지기

- 메서드 내에서 예외를 처리하고 나머지는 caller에서 처리되게 하는 것

- catch하고 같은 예외를 발생시키면 됨

- 예외가 발생한 메서드와 호출한 메서드 양쪽 모두에서 처리해주어야 할 작업이 있을 때

-> 되던지기를 하면 return을 적어주지 않는 것이 가능하다.

 

 

>>연결된 예외

- 예외 a가 예외 b를 발생시키게 함

- a를 b의 원인예외로 지정함

 

=> 왜 원인을 지정할까?

 

이유1. 하나의 큰 분류의 예외로 묶어서 다루기 위해서

 

ex) SpaceException -> InstallException

 

상속관계로 처리하게 되면 SpaceException인지 MemeoryException인지 알수가 없게 됨

 

이유2. checked예외를 unchecked로 바꿀 수 있도록 하기 위해서

 

=> MemoryException은 Exception을 상속받음(필수 예외)

그러나, RuntimeException으로 MemoryException을 감싸서 unchecked가 됨