예외처리 (Exception Handling)

rivermt·2023년 3월 27일
0

JAVA

목록 보기
2/9

프로그램 오류

프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
다음과 같이 세가지로 구분할 수 있다.

  • 컴파일 오류 : 컴파일 시 발생하는 에러
  • 런타임 에러 : 실행 시에 발생하는 에러
  • 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

자바는 실행 시 (runtime) 발생할 수 있는 프로그램 오류를 "에러(error)" 와 "예외(exception)" 두가지로 구분하였다.

에러는 메모리 부족 (OutOfMemoryError)나 스택오버플로우 (StackOverflowError)와 같이 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습될 수 있는 비교적 덜 심각한 것이다.

예외 클래스의 계층 구조

자바에서는 실행 시 발생할 수 있는 오류 (exception && error) 를 클래스로 정의했다.

예외처리하기

예외처리란 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 목적은 예외의 발생으로 인한 실행 중인 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.

발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외 (uncaught exceptoin)은 JVM의 '예외처리기 (UncaughtExceptionHandler)' 가 받아서 예외의 원인을 화면에 출력한다.

try catch

try {
	// 예외 가 발생할 가능성이 있는 코드
} catch (Exception1 e1) {
	// Exception1이 발생했을 경우 처리하기 위한 코드
} catch (Exception2 e2) {
	// Exception2이 발생했을 경우 처리하기 위한 코드
}

하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있으며 이 중 발생한 예외의 종류와 일치하는 단 한 개의 catch블럭만 수행된다.

발생한 예외의 종류와 일치하는 catch블럭이 없다면 예외는 처리되지 않는다.
if문과 다르게 블럭내에 포함된 문장이 하나뿐이어도 괄호를 생략할 수 없다.

class Example {
	public static void main (String[] args) {
    try {
    	try {	} catch (Exception e){ }
    	} catch (Exception e) {
        	try {	} catch (Exception e) {} // error : 변수 e 중복선언
       	}
    }
    try {
    	
    } catch (Exception e) {
    
    }
}

catch블럭 내에 또 하나의 try catch문이 포함된 경우 같은 이름의 참조변수를 사용해서는 안된다. 각 catch블럭에 선언된 두 참조변수의 영역이 서로 겹치므로 다른 이름을 사용해야만 서로 구별되기 때문이다.

예외의 발생과 catch블럭

catch 블럭은 괄호 () 와 블럭 {} 두 부분으로 나눠져 있다. 괄호에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언한다.

예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch 블럭이 있는지 찾게 된다.

첫 번째 catch블럭부터 순서대로 내려가며 catch블럭 괄호 내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof 연산자를 이용해서 검사하게 되는데, 검사결과가 true인 catch블럭을 만날 때까지 검사는 계속된다.

검사결과가 true인 catch블럭을 찾으면 블럭에 있느 무장들을 모두 수행한 후 try catch문을 빠져나가고 예외는 처리된다.

모든 예외 클래스의 조상은 Exception클래스 이므로 Exception클래스 타입의 참조변수를 괄호에 선언하면 어떤 종류의 예외가 발생하더라고 처리된다.

printStackTrace()와 getMessage

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

멀티 catch 블럭

JDK1.7부터 여러 catch블럭을 '|'기호를 이용하여 하나의 catch블럭으로 합칠 수 있게 되었다.

만일 멀티 catch블럭의 '|'기호로 연결된 예외 클래스가 조상과 자손의 관계라면 컴파일 에러가 발생한다. 조상클래스만 써주는 것과 똑같기 때문이다.

예외 발생 시키기

키워드 throw를 사용해서 프로그래머가 임의로 예외를 발생시킬 수 있다.

  1. 연산자 new를 통해 발생시키려는 예외 클래스의 객체를 만든다.
Exception e = new Exception("예외 발생");
  1. 키워드 throw를 통해 예외를 발생시킨다.
throw e;

Checked Exception && Unchecked Exception

Checked Exception

컴파일러가 예외처리를 강제하는 예외이다.
Checked Exception은 Exception을 상속한다.
Checked Exception을 발생시키는 메서드는 반드시 try catch블록을 사용하거나 throws를 통해 예외처리를 해야한다.
대표적으로 IOException, SQLException, ClassNotFoundException이 있다.

  • Compile Time Exception 으로도 불림
  • 컴파일 시점에 Exception을 catch하는지 확인함.
    컴파일 시점에 Exception에대한 처리를 하지 않을 경우 컴파일 에러 발생
  • Exception이 발생하는 메서드에서 throws를 활용해 Exception을 호출 메서드에 전달해야 함
  • Checked Exception은 OCP를 위배한다.

Unchecked Exception

컴파일러가 예외 처리를 강제하지 않는 예외이다.
Unchecked Exception은 RuntimeException 클래스를 상속한다.
try-catch나 throws를 사용하지 않아도 되며, 예외처리를 강제하지 않기 때문에 개발자가 예외처리를 하지 않아도 된다.
대표적으로 NullPointerException, IndexOutOfBoundException 등이 있다.

  • Runtime Exception 으로도 불림
  • 컴파일 시점에 Exception을 catch하는지 확인하지 않음
    컴파일 시점에 Exception이 발생할 것인지의 여부 확인 불가
  • Exception이 발생하는 메서드에서 throws를 활용해 Exception을 처리할 필요가 없다. 하지만 처리해도 무방하다.

차이점

Checked Exception은 외부 자원에 대한 I/O 작업이나 네트워크 통신 등의 예외를 처리하기 위해 사용된다. 반면에 Unchecked Exception은 프로그래밍 오류나 런타임 상황에서 예외를 처리하기 위해 사용된다.

또한, Checked Exception은 throws 절을 이용하여 예외를 떠넘길 수 있지만, Unchecked Exception은 throws 절을 사용할 필요가 없다. 또한, Checked Exception을 처리하지 않으면 컴파일 오류가 발생한다.

Checked Exception OR Unchecked Exception?

  • 호출하는 메서드가 Exception을 활용해 무엇인가 의미있는 작업을 할 수 있다면 Checked Exception을 사용하라.
  • 만약 호출하는 메서드가 Exception을 catch해 예외 상황을 해결하거나 문제를 해결할 수 없다면 Unchecked Exception을 사용하라.
  • 명확하지 않다면 Unchecked Exception을 사용하라.

Checked Exception, Unchecked Exception 과 Rollback에 관한 오해

"JAVA Exception의 종류와 DB 트랜잭션 Rollback은 아무 상관이 없다."

Checked Exception과 Unchecked Exception을 비교한 표가 많은 블로그에서 활용되고 있는데 이 표에서는 Checked Exception은 Rollback을 하지 않고 Unchecked Exception은 Rollback을 한다고 되어있다.

이는 Spring에서 기본적으로 Exception 종류에 따른 Rollback 정책이 JAVA에서 그렇게 하는 것처럼 잘못 와전된 것 같다.

Spring에서도 디폴트로는 이렇게 설정되어있지만 개발자가 Rollback에 대한 처리를 할 수 있도록 지원하고 있다.

메서드에 예외 선언하기

메서드에 예외를 선언하려면 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주면 된다.

class Example {
	void method throws Exception1, Exception 2 {
    	// method 내용
    }
}

예외를 메서드의 thorows에 명시하는 것은 예외를 처리하는 것이 아니라 자신을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.

예외가 발생한 메서드에서 예외처리를 하지 않고 자신을 호출한 메서드에게 예외를 넘길 수는 있지만, 이것은 예외가 처리된 것이 아니고 예외를 단순히 전달만 하는 것이다. 결국 어느 한 곳에서는 try catch문으로 예외처리를 해주어야 한다.

throw?, throws?

  • thorow : 메서드 블록 내에서 예외를 직접적으로 발생시킬 떄 사용
  • thorows : 메서드 선언부에서 에외처리를 위임하는 역할

finally 블럭

finally블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다.
try catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, 'try-catch-finally' 순으로 구성된다.
예외가 발생하지 않았을 경우에는 'try-finally' 순으로 실행된다.

try {
	// 예외가 발생할 수 있는 코드
} catch (Exception1 e1) {
	// 예외처리를 위한 코드
} finally {
	// 예외의 발생여부에 관계없이 항상 수행되어야하는 코드
}

try with resources - 자동 자원 반환

JDK1.7부터 추가되었다.
입출력(I/O) 와 관련된 클래스를 사용할 때 유용하다.
이넞에는 finally 블록을 사용하여 자원을 해제 했지만, try with resouces를 통해 자동으로 자원을 해제할 수 있게 되었다.

try catch 예제

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryCatchExample {

    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("input.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

try with resources 예제

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TryWithResourcesExample {

    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

AutoCloseable 인터페이스는 자원을 해제할 수 있는 close() 메서드를 정의한다.

try에 선언된 객체가 AutoCloseable을 구현했다면 try구문이 종료될 때 객체의 close() 메서드를 호출하게 된다.

예외 되던지기 (Exception Re-throwing)

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try catch를 통해 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 처리하도록 할 수 있다.

단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리하도록 할 수 있다.

예외가 발생할 메서드에서는 try catch문을 사용하여 예외처리를 해줌과 동시에 메서드의 선언부에 발생할 예외를 throws에 지정해줘야 한다.

public void method() throws Exception {
    try {
        // 하위 메서드 호출
        method2();
    } catch (Exception e) {
        // 예외 되던지기
        throw e;
    }
}

public void method2() throws Exception {
    // 하위 메서드에서 예외 발생
    throw new Exception("예외 발생");
}

연결된 예외 (Chained Exception)

한 예외가 다른 예외를 발생시킬 수도 있다. 에외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)'라고 한다.

  • initcause() : Throwable클래스에 정의되어 있기 떄문에 모든 예외에서 사용 가능
try {
	startInstall();
    copyFiles();
} catch (SpaceException e) {
	InstallException ie = new InstallException("설치중 예외 발생");
    ie.initCause(e);
    throw ie;
} catch (MemoryException me) {
	...
}

Why?

  • 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위한 것
  • checked exception을 unchecked exception으로 바꾸기 위한 것

    Runtime Exception을 만들고 그 안에 Checked Exception을 포함 시켜버린다.

참고자료

자바의 정석 by 남궁성
코드스쿼드 자료
백기선님 유투브 : https://www.youtube.com/watch?v=_WkMhytqoCc
클린코드

profile
화이팅!!

0개의 댓글