1. 예외 처리 (exception handling)

예외처리란?

프로그램이 실행 도중 발생하면 비정상적으로 프로그램이 종료되지만 발생할 수 있는 상황을 미리 예측하여 처리하는 것을 말한다. 예외 처리의 목적은 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하기 위함이다.

발생하는 예외를 처리하지 못하면 JVM의 예외 처리기가 받아서 예외를 화면에 출력한다.

 

* 프로그램의 오류

  • 컴파일 에러 : 컴파일 과정 중 발생하는 에러
  • 런타임 에러 : 프로그램 실행 도중 발생하는 에러
  • 논리적 에러 : 실행은 되지만 의도와 다르게 동작하는 에러

컴파일러가 잘못된 구문들을 체크하여 컴파일 후 class 파일이 생성된다. 하지만 실행 도중 발생하는 잠재적 에러는 파악할 수 없다.

 

* 런타임 에러의 종류

  1. 에러 : OutOfMemory, StackOverFlow 등 발생하면 복구할 수 없는 심각한 상태를 말하며 프로그램 코드에 의해 수습될 수 없는 심각한 오류이다.
  2. 예외 : 발생하더라도 수습이 가능한 상태이며 프로그램 코드에 의해 수습될 수 있는 다소 미약한 오류이다.

2. 예외 클래스의 계층 구조

상속 계층도

예외 클래스를 나누면 Exception class와 RuntimeException class 두 가지로 나눌 수 있다.

  • RuntimeException 하위 클래스들은 프로그래머의 실수로 발생하는 예외이다. (uncheckd 예외)
    ex)
    IndexOutOfBoundException
    NullPointerException
    ClassCastException
    ArithmeticException
  • Exception 하위 클래스들은 사용자의 실수로 발생하는 예외이다. (checkd 예외)
    ex)
    FileNotFoundException
    ClassNotFoundException
    DataFormatException

3. 예외 처리하기 - try - catch문

public class Sample {
    public int getAge() {
        try {
            // 예외가 발생할 가능성이 있는 코드
        } catch(ArithmeticException e1) {
            // ArithmeticException이 발생할 경우 실행되는 코드
        } catch(IOException e2) {
            // IOException이 발생할 경우 실행되는 코드
        }
    }
}

try 다음에 여러 종류의 catch 블록이 올 수 있으며 해당 예외 발생 시 발생한 예외의 catch 문이 실행된다. catch에 없는 예외 발생 시 예외가 처리되지 않는다.

try - catch문은 실행 코드가 한 줄 이어도 중괄호 생략 불가능하다.

try - catch문안에 또 다른 try - catch 문이 올 수 있으며 같은 레벨에 여러 try - catch문이 올 수 있다.

4. try - catch 문에서의 흐름

* 실행 순서

try 블록 내에서 예외가 발생한 경우

  1. 발생한 예외와 일치하는 catch 블록이 있는지 확인
  2. 일치하는 catch 문을 찾았다면
     - 해당 catch문을 실행하고 try - catch문을 빠져나간다.
    일치하는 catch문을 못 찾았다면 예외 처리되지 않는다.

try 블록 내에서 예외가 발생하지 않는 경우

  1. catch 문을 거치지 않고 try - catch 문을 빠져나간다.

try 안에서 예외가 발생할 때 발생한 라인 이하의 코드는 실행되지 않고 catch문으로 빠져나간다.

5. 예외의 발생과 catch 블록

catch문은 괄호와 블록으로 나뉜다.

괄호에는 예외 처리할 예외 타입의 참조 변수를 정의한다.

try {
    ...
} catch (ArithmeticException e) {
    ...
}

첫 번째 catch부터 마지막 catch까지 예외 인스턴스와 catch의 참조 변수 종류를 instanceof 연산자를 이용해 검사하고 검사 결과 true인 catch 블록을 만날 때까지 검사한다.

catch문에 'Exception'을 지정하면 모든 예외가 잡힌다. 그 이유는 모든 예외가 Exception의 자식이기 때문이다.

try {
    ...
} catch (ArithmeticException e) {
    ...
} catch (Exception e) {
    ...
}

위의 예제의 경우 두 번째 catch는 ArithMeticException을 제외한 나머지 Exception을 처리한다.

반대로 첫 번째에 Exception e를 선언하면 모든 예외는 첫 번째 catch문에서만 실행된다.

5.1. printStackTrace()와 getMessage()

printStackTrace와 getMessage는 catch의 참조 변수를 통해 많이 사용되는 함수이다.

  • printStackTrace() : 예외 발생 당시에 호출 스택에 있었던 메서드의 정보와 예외 메시지를 화면에 출력하는 함수이다.
  • getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있는 함수이다.
try {
    ...
} catch (ArithmeticException e) {
    e.printStackTrace();
    e.getMessage();
}

* 예외 정보를 파일로 저장할 수 있는 printStackTrace(PrintStream p), printStackTrace(PrintWriter p)가 있다.

5.2. 멀티 catch 블록

try {}
catch (Exception1 e1) {}
catch (Exception2 e2) {}
catch (Exception3 e3) {}


catch (Exception1 | Exception2 | Exception3 e) {}

파이프 ( | )를 이용 시 클래스가 상위, 하위 관계에 있다면 컴파일 에러가 발생한다. 그 이유는 상위 클래스만 넣어도 예외 처리가 잡히기 때문이다.멀티 catch 블록을 사용 시 어떤 예외로 들어왔는지 모르기 때문에 예외 클래스들의 공통분모인 상위 멤버만 사용 가능하다. 또한 instanceof를 이용하여 예외처리해도 된다.

catch (Exception1 | Exception2 e) {
    if(e instanceof Exception1) {
        ...
    } else if(e instanceof Exception2) {
        ...
    }
}

멀티 catch 블록의 참조 변수는 상수이다.

6. 예외 발생시키기

'new' 연산자를 이용해 발생시키고자 하는 예외 클래스를 객체로 만든 후 throw 키워드로 예외를 발생시킨다.

Exception e = new Exception("고의");

throw e;

// or 

throw new Exception("고의");

7. 메서드에 예외 선언하기

메서드 선언부에 'throws' 키워드로 예외 클래스를 선언한다. 예외가 여러 개일 경우 ', '(쉼표)로 구분하여 선언한다.

public void method throws IOException, ArithmeticException {}

* 주의) throw와 throws 구분 필요

반드시 처리해야 하는 예외만 처리하고 보통 RuntimeException은 처리하지 않는다.

throws는 예외 처리를 하는 것이 아닌 어떤 예외가 발생할 수 있는지 전달해주는 것이다.

메서드를 호출 한 곳에서 에러 처리를 할 것인지 호출된 메서드에서 에러 처리를 할 것인지 정해야 한다.

// 1. 호출 한 곳에서 에러처리
main() {
    try {}
    catch (Exception e) {}   
}

// 2. 호출 된 메서드에서 에러 처리
main() {
    Sample s = new Sample();
    
    s.method();
}

public class Sample {
    public void method() {
        try {}
        catch (Exception e) {}
    }
}

8. finally 블록

try - catch와 함께 선택적으로 사용되며 예외와 관계없이 무조건 실행된다.

선언 순서는 try - catch - finally 순서이다.

try에서 메서드가 return 되어도 finally가 실행되고 메서드가 return이 된다.

try {
    return 1;
} catch (Exception e) {
    ...
} finally {
    return 0;
}

9. 자동 자원 반환 - try - with - resources문

입출력에 유용하며 열려 있는 자원을 반환할 때 사용된다.

public class CustomAutoCloseable implements AutoCloseable {
    public void close() throws Exception {
        System.out.println("닫기");
    }
}

try (CustomAutoCloseable c = new CustomAutoCloseable()) {
    ...
} ...

try에 괄호를 넣어 참조 변수를 선언할 수 있으며 AutoCloseable 인터페이스로 구현된 것만 가능하며 AutoCloseable의 close() 함수를 자동으로 실행해주어 자원을 반환할 수 있다.

10. 사용자 정의 예외 만들기

public class CustomException extends Exception {
    CustomException(String msg) {
        super(msg);
    }
}

Exception 클래스를 상속받은 하위 클래스에서 상위 클래스의 생성자를 호출하여 사용자 정의 예외 클래스를 만들 수 있으며 단순 메시지 입력뿐만 아니라 다양한 구현을 할 수 있다.

* 되도록 기존의 예외 클래스를 사용해야지 다른 사람이 봤을 때도 공통으로 예외 처리하기 편하다

11. 예외 던지기 (exception re-throwing)

main() {
    try {
        Sample s = new Sample();
        s.method();
    } catch (Exception e) {}
}

public class Sample {
    public void method() {
        try {}
        catch (Exception e) {
           throw new Exception("고의");
        }
    }
}

호출당한 쪽에서 예외를 처리하고 throw를 통해 예외를 발생시켜 main 메서드에서 예외를 한 번 더 실행시키는 것을 예외 던지기라고 한다.

주의할 점은 try - catch 문으로 예외 처리와 동시에 throws로 예외를 지정해 줘야 한다.

12. 연결된 예외 (chained exception)

A 예외가 B예외를 발생시킨다면 A를 B의 '원인 예외(cause exception)'이라 한다.

try {}
catch (IOException e1) {
    ArithmeticException ae = new ArithmeticException();
    ae.initCause(e1); // IOException을 원인 예외로 지정
    throw ae;
} catch (ArithmeticException e2) {
    ...
}

위의 예제에서 initCause(e) : 원인 예외를 e (IOException)으로 지정하였고 주로 여러 예외들을 묶어서 분류할 때 사용한다. 여러 클래스를 묶어 상위 클래스를 사용하면 상속 관계 변경, 실제 인스턴스 판단 미스 등의 이유로 인해 부담되는 이유로 사용한다.

* Exception 클래스의 상위인 Throwable에 initCause(), getCause()가 정의되어 있다.

12.1. checked 예외 -> unchecked 예외

// checked 예외
public void getName() throws SpaceException, MemoryException {
    ...
}


// unchecked 예외
public void getName() throws SpaceException {
    if(공간부족) {
        throw new SapceException("공간이 부족합니다.");
    }
    
    if(메모리 부족) {
        throw new RuntimeException(new MomoryException("메모리가 부족합니다."));
    }
}

MemoryException을 RuntimeException으로 감싸면서 checked예외가 unchecked예외로 바뀌었다. 또한 RuntimeException의 원인을 등록하는 생성자를 사용하여 원인 등록까지 해주었다.

'개발 언어 > Java' 카테고리의 다른 글

[Java] Double Underscore  (0) 2021.10.22
[Java] Enum to Class  (0) 2021.09.07
[Java] 내부 클래스  (0) 2021.08.27
[Java] 인터페이스  (0) 2021.08.27
[Java] 추상 클래스&추상 메서드  (0) 2021.08.26

+ Recent posts