본문 바로가기

Java

Exception 정리

우테코 강의자료에서 Exception 부분을 눈으로만 읽다가
기억에 남지 않아서 글로 정리하고자 한다.

Exception의 상속구조

  • Error
    • 애플리케이션이 정상적으로 동작하는데 심각한 문제가 있는 경우
  • Exception
    • 비즈니스 로직 상에서 에러가 발생하는 경우 사용한다.
    • Exception을 사용하는 경우 컴파일 시점에 Exception을 확인할 수 있다.
    • Checked Exception 또는 Compile Time Exception 이라고 한다.
  • RuntimeException
    • NumberFormatException과 같이 Runtime 시에 발생하는 에러를 처리하는데 사용한다.
    • UnChecked Exception 또는 Runtime Time Exception이라고도 한다.

Checked Exception(Compile Time Exception) - Exception을 상속

컴파일 시점에 Exception을 catch하는지 확인한다.
컴파일 시점에 Exception에 대한 처리(try/catch)를 하지 않을 경우 컴파일 에러 발생
Exception이 발생하는 메소드에서 무조건 throws 예약어를 활용해 Exception을 호출 메소드에 전달해야 한다.

public class InvalidPositionException extends Exception {
    public InvalidPositionException(String msg) {
        super(msg);
    }
}
public class Position {
    public Position(String position) throws InvalidPositionException {
        if (position.length() != 2) {
            throw new InvalidPositionException("형식 오류");
        }
        ...
    }
}
public static void main(String[] args) {
    try {
        Position position = new Position("2");
    } catch (InvalidPositionException e) {
        System.out.println(e.getMessage());
    }
}

Unchecked Exception(RunTime Time Exception) - RuntimeException을 상속

컴파일 시점에 Exception을 처리(try/catch)하는지 확인하지 않는다.
컴파일 시점에 Exception이 발생할 것인지의 여부를 판단할 수 없다.
Exception이 발생하는 메소드에서 throws 예약어를 활용해 Exception을 처리할 필요가 없다. 하지만 처리해도 무방하다.

public class InvalidPositionException extends RuntimeException {
    public InvalidPositionException(String msg) {
        super(msg);
    }
}
public class Position {
    public Position(String position) {
        if (position.length() != 2) {
            throw new InvalidPositionException("형식 오류");
        }
        ...
    }
}

Checked Exception과 Unchecked Exception 선택 방법

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

선택 기준에 대한 설명을 더 찾아보다가
더 자세히 설명된 좋은 글을 찾았다.

Java 의 Exception 처리는 C++로부터 도입되었지만 checked exception은 Java만의 독특한 특징입니다.
아시다시피, 컴파일러가 exception을 꼭 처리해라고 강요하는 것이죠.
이것은 java 이후에 설계된 언어인 C#이나 루비에도 채택되지 않았습니다.
즉 Java 이외의 다른 언어들의 Exception 처리 방식은 Java의 unchecked exception과 동일한 방식입니다.
Java의 초기에는 checked Exception을 사용하라고 권장했지만,
지금은 많은 반론이 제기되고 있습니다.
극단적으로 Java언어에서checked Exception 도입 자체가 실패라고 주장하는 사람도 많습니다.
Thinking in Java의 저자인 Bruce Eckel도 그 중 한 사람입니다.
Spring framework의 아버지 Rod Johnson도 Checked Exception이 쓰여야 할 때도 있지만 그 것이 과도하게 선호되어 온 것은 지적하고 있습니다.
JDBC API에서도 Checked Exception을 남발한 문제가 보입니다.
catch 절에서 아무 것도 하지 않는 것은 바람직하지 않은 코딩이지만 JDBC API에서는 정말 할 것이 없습니다.
그래서 이런 문제점을 알고서 그 후에 나온 JDBC를 활용한 API들, Spring의 JdbcTemplet, Hibernate의 Query 인터페이스, JPA의 Query 인터페이스, JDO의 Query 인터페이스에서는 Checked Exception인 SQLException을 볼 수 없게 설계되어 있습니다.
현 시점에서는 unchecked exception을 디폴트로, 특별한 이유가 있는 것만 checked exception을 활용하는 방식이 더 보편적입니다.
- SLIPP에서 benelog님의 글


Exception 관련 알아야할 내용

여러개의 Exception 처리1

메소드에서 여러 개의 Exception을 throw하는 경우 다음과 같이 쉼표로 구분할 수 있다.

public void send() throws ObjectStreamException, UnknownHostException {
}

여러개의 Exception 처리2

메소드에서 여러 개의 Exception을 throw하는 경우 쉼표를 쓰지않고, 부모 클래스로 예외를 전달하는 것이 가능하다.

public void send() throws ObjectStreamException, UnknownHostException {
}

위와 같은 Exception 구현을 아래와 같이 구현

public void send() throws Exception {
}

여러개의 Exception 처리3

메소드에서 여러 개의 Exception을 처리할 때 다음 예제와 같이 처리할 수 있다.

public class StudentTest extends TestCase {    
    public void testCreateWhenNameIsNotValid() {
        try {
            new Student("A");
            fail("이름이 형식에 맞지 않아 Exception이 발생해야 한다.");
        } catch (StudentNameFormatException e) {
            assertEquals("A 이름은 형식에 맞지 않습니다.", e.getMessage());
        } catch (NoSuchElementException e) {

        }
    }
}

여러개의 Exception 처리4

위에서 봤던 예제를 다음과 같이 처리할 수도 있다.

public class StudentTest extends TestCase {    
    public void testCreateWhenNameIsNotValid() {
        try {
            new Student("A");
            fail("이름이 형식에 맞지 않아 Exception이 발생해야 한다.");
        } catch (StudentNameFormatException | NoSuchElementException e) {
            assertEquals("A 이름은 형식에 맞지 않습니다.", e.getMessage());
        } 
    }
}

여러개의 Exception 처리5

Exception에 대한 catch 절이 많은 경우 부모 클래스의 예외를 활용해 처리할 수 있다.

public class StudentTest extends TestCase {    
    public void testCreateWhenNameIsNotValid() {
        try {
            new Student("A");
            fail("이름이 형식에 맞지 않아 Exception이 발생해야 한다.");
        } catch (Exception e) {
            assertEquals("A 이름은 형식에 맞지 않습니다.", e.getMessage());
        }
    }
}

예외 다시 전달하기

메소드를 호출할 때 예외를 처리한 후 예외를 재전달 하는 것이 가능하다.

public class PositionTest extends TestCase {    
    public void testCreateWhenIllegalArgument() throws InvalidPositionException {
        try {
            new Position("a");
        } catch (InvalidPositionException e) {
            throw e;
        }
    }
}

Stack Trace

예외가 발생할 경우 예외가 발생한 원인을 찾기 위해 예외의 발생 경로를 추적하는 것이 가능하다.

public class PositionTest extends TestCase {    
    public void testCreateWhenIllegalArgument() throws InvalidPositionException {
        try {
            new Position("a");
            fail("Position 인자가 형식에 맞지 않아 Exception이 발생해야 한다.");
        } catch (InvalidPositionException e) {
            e.printStackTrace();
        }
    }
}