코딩하는 털보

STUDY HALLE - 9주차 본문

Diary/Study Halle

STUDY HALLE - 9주차

이정인 2021. 1. 15. 23:04

STUDY HALLE

9주차 : 예외 처리


목표

자바의 예외 처리에 대해 학습하세요.

학습할 것

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

프로그래밍 용어의 예외

프로그래밍 언어에서 명령을 실행할 시, 입력 값과 계산 대상의 자료형을 함수에서 지원하지 않거나, 함수에서 대상을 계산할 수 없을 때 나오는 오류이다.

프로그래밍 시 이를 미리 예측하고 그에 대한 대처법(예외 처리, Exception Handling)을 만들어 두어야 한다.

대부분의 언어는 직접 예외를 발생시킬 수도 있다.

참고 : 나무위키 예외

ex) 배열을 만들고 배열 크기를 초과하는 인덱스 값을 검색한다면?

public class TryCatch {
    public static void main(String[] args) {

        int[] arr = new int[5];

        for (int i = 0; i < 6; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("complete");
    }
}
[/Users/ijeong-in/Documents]> javac TryCatch.java
[/Users/ijeong-in/Documents]> java TryCatch     
0
0
0
0
0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
    at TryCatch.main(TryCatch.java:7)

위 예제를 통해 알 수 있는 것.

  • 우리는 이 예외가 컴파일 시점이 아닌 실행 시점에서 발생했음을 알 수 있다. (RuntimeException)

  • 그리고 로그를 통하여 예외의 이름이 'ArrayIndexOutOfBoundsException'인 것을 알 수 있다.

    실제로 ArrayIndexOutOfBoundsException 클래스는 RuntimeException의 하위 클래스이다.

  • 예외의 구체적인 내용 또한 알 수 있었다. (Index 5 out of bounds for length 5)

  • "complete" 가 출력되지 않았다? => 예외와 함께 쓰레드는 종료되었다는 것을 알 수 있다.

예외가 발생하면서 쓰레드가 종료된 이유는 무엇일까?

JVM은 예외 처리를 찾지 못하면 쓰레드를 강제 종료시키기 때문이다.

보다 더 자세히 말하면, JVM은 예외 처리를 찾지 못하면 쓰레드를 종료 시킨 뒤 Thread.getUncaughtExceptionHandler() 메소드를 통해 받은 기본 예외 처리를 사용하여 예외를 처리하는데, 만약 명시적으로 설정된 기본 예외 처리가 없다면 ThreadGroup에 구현되어 있는 메소드를 사용한다.

아래는 java.lang.ThreadGroup 클래스의 기본 예외 처리 메소드이다.

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

참고 : Oracle, Interface Thread.UncaughtExceptionHandler

try~catch 문으로 예외처리하기

try {
    //예외가 발생할 수 있는 동작
} catch(처리할 예외 타입 var) {
    //try블록 안에서 예외가 발생했을 때 수행되는 부분
}

위의 예제에서 기본 예외 처리를 사용하지 않도록 try-catch 문을 사용해보자.

public class TryCatch {
  public static void main(String[] args) {

      int[] arr = new int[5];

      try {
          for (int i = 0; i < 6; i++) {
              System.out.println(arr[i]);
          }
      }catch(RuntimeException e) {
          System.out.println(e);
          System.out.println("예외 처리");
      }
      System.out.println("프로그램 종료");
  }
}
[/Users/ijeong-in/Documents]> java TryCatch         
0
0
0
0
0
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
예외 처리
프로그램 종료

위 예제를 통해 알 수 있는 것.

  • 상위 예외 클래스를 catch 해도 하위 예외 클래스 까지 처리한다.

  • 예외와 함께 쓰레드가 종료되지 않고 프로그램은 계속 수행된다.

try~finally 문으로 예외처리하기

try {
    //예외가 발생할 수 있는 동작
} catch(처리할 예외 타입 var) {
    //try블록 안에서 예외가 발생했을 때 수행되는 부분
} finally {
    //예외 발생 여부와 상관 없이 항상 수행되는 부분
    //보통 리소스를 정리하는 코드를 주로 쓴다.
}
public class TryCatch {
  public static void main(String[] args) {

      int[] arr = new int[5];

      try {
          for (int i = 0; i < 6; i++) {
              System.out.println(arr[i]);
          }
      } finally {
        System.out.println("마지막으로");
      }
      System.out.println("프로그램 종료");
  }
}
[/Users/ijeong-in/Documents]> java TryCatch      
0
0
0
0
0
마지막으로
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
    at TryCatch.main(TryCatch.java:8)

위 예제를 통해 알 수 있는 것.

  • 예외와 함께 쓰레드가 종료되었으나, 그전에 finally 블록을 실행한다.

예외 발생시키기

throw 키워드를 통해 예외를 직접 발생시킬 수 있다.

public class TryCatch {
  public static void main(String[] args) {

      int[] arr = new int[5];

      for (int i = 0; i < 6; i++) {
          if (i == 5) {
            throw new RuntimeException("배열 크기 초과");
          }
          System.out.println(arr[i]);
      }
      System.out.println("프로그램 종료");
  }
}
[/Users/ijeong-in/Documents]> java TryCatch      
0
0
0
0
0
Exception in thread "main" java.lang.RuntimeException: 배열 크기 초과
    at TryCatch.main(TryCatch.java:8)

위 예제를 통해 알 수 있는 것.

  • ArrayIndexOutOfBoundsException가 아닌 직접 명시한 RuntimeException 예외가 발생하였다.
  • 기본 예외 처리가 발생하여 쓰레드는 종료되었다.

예외 처리 미루기

throws 키워드를 통해 메소드를 호출한 상위 메소드로 예외를 전달할 수 있다.

public class TryCatch {
  public static void main(String[] args) {
    try {
      show();
    } catch(RuntimeException e) {
      throw new RuntimeException("예외가 발생했습니다.");
    }
  }

  //throws를 메소드 명칭 뒤에 추가하여 해당 예외 타입의 예외 처리를 호출한 메소드로 인계한다.
  public static void show() throws RuntimeException {
    int[] arr = new int[5];

    for (int i = 0; i < 6; i++) {
        System.out.println(arr[i]);
    }
  }
}
[/Users/ijeong-in/Documents]> javac TryCatch.java
[/Users/ijeong-in/Documents]> java TryCatch      
0
0
0
0
0
Exception in thread "main" java.lang.RuntimeException: 예외가 발생했습니다.
    at TryCatch.main(TryCatch.java:6)

위 예제를 통해 알 수 있는 것.

  • 예외는 show() 메소드에서 발생했지만, 예외 처리는 main 메소드로 인계되었다.
  • main 메소드의 try-catch 문이 사용되었다.

자바가 제공하는 예외 계층 구조

자바에서는 예외와 오류를 클래스로 정의해두었으며, Throwable 클래스는 모든 예외와 에러의 슈퍼클래스이다.

Throwable 클래스의 상속 구조

hierarchy of exception handling

사진 출처 : https://www.javatpoint.com/exception-handling-in-java

대표적인 예외들

Exception 내용
IndexOutOfBoundsExcetion 범위를 벗어난 index에 접근 시 발생
ClassCastExcetion 변환할 수 없는 타입으로 객체를 변환 시 발생
NullPointException 존재하지 않는 레퍼런스를 참조할때 발생
IllegalArgumentException 잘못된 인자를 전달 할 때 발생
IOException 입출력 동작 실패 또는 인터럽트 시 발생
NumberFormatException 숫자로 변환할 수 없는 문자를 숫자로 변환 시 발생
ArithmeticException 정수를 0으로 나눌경우 발생

Throwable 클래스의 주요 메소드

  • void printStackTrace() : 발생한 예외의 출처를 추적하여 결과를 보여준다.
  • String getMessage() : 예외에 대한 요약 메시지를 반환한다.

Exception과 Error의 차이는?

위에서 알아본 것 처럼 Throwable 클래스는 크게 Exception과 Error 클래스로 나뉘어 상속된다.

그럼 두 개 클래스의 차이는 무엇일까?

구체적으로는 더 많은 차이가 있겠지만, 여러 기록을 찾아 보면서 결국에 내가 얻은 답은, 이 오류가 개발자가 구현한 로직에서 발생하는가 또는 그 외에서 발생하는가의 차이인 것 같다.

다른 차이들은 위의 구분을 통해 어느정도 예상 가능하다.

  • Exception은 개발자가 구현한 로직에서 발생한다.

    • 개발자가 오류의 발생을 예측할 수 있다.

    • 개발자는 애플리케이션 로직을 변경하여 문제를 해결할 수 있다.

    • 개발자는 예외에 대한 방안을 구축해 놓을 수 있다.

    • 주요 원인 : 잘못된 데이터 입력, 잘못된 연산, 잘못된 로직 등.

  • Error는 개발자가 구현한 로직 외의 시스템 레벨에서 발생한다.
    • 개발자가 오류의 발생을 예측할 수 없다.
    • 애플리케이션에서 오류에 대해 방안을 세울 수 없다.
    • 주요 원인 : 시스템 과부하, 시스템 오작동 등.

try-catch 문에서 Error 타입을 잡을 수 도 있으나 대부분 의미가 없는 코드가 된다.

대부분의 Error 타입 오류는 복구가 불가능하여 프로그램이 강제로 종료되기 때문이다.

참고 :

https://techdifferences.com/difference-between-error-and-exception.html

https://stackoverflow.com/questions/5813614/what-is-difference-between-errors-and-exceptions


RuntimeException과 RE가 아닌 것의 차이는?

Exception은 RuntimeException인지 아닌지에 따라 다시 두 종류로 나뉠 수 있다.

두 예외 종류의 가장 큰 차이는? '꼭 예외 처리를 해야하느냐' 이다.

Checked Exception VS Unchecked Exception

Checked Exception Unchecked Exception
예외 처리 확인 시점 컴파일 시점 실행 시점
예외 처리 반드시 예외를 처리해야 한다. 처리를 강제하지 않는다.
예외 종류 Exception을 상속받는 하위 클래스 중, RuntimeException을 제외한 모든 예외 RuntimeException을 상속받는 모든 예외
트랜젝션 rollback 하지 않는다. rollback 한다.

Checked Exception은 예외 처리를 해야한다.

예외 처리를 하지 않으면?

public class TryCatch {
  public static void main(String[] args) {

      int[] arr = new int[5];

      for (int i = 0; i < 5; i++) {
          if (i == 7) {
            throw new ClassNotFoundException("Class Not Found");
          }
          System.out.println(arr[i]);
      }
      System.out.println("프로그램 종료");
  }
}
[/Users/ijeong-in/Documents]> javac TryCatch.java
TryCatch.java:8: error: unreported exception ClassNotFoundException; must be caught or declared to be thrown
            throw new ClassNotFoundException("Class Not Found");
            ^
1 error

ClassNotFoundException 예외 처리를 미루도록 해보자.

public class TryCatch {
  public static void main(String[] args) throws ClassNotFoundException {

      int[] arr = new int[5];

      for (int i = 0; i < 5; i++) {
          if (i == 7) {
            throw new ClassNotFoundException("Class Not Found");
          }
          System.out.println(arr[i]);
      }
      System.out.println("프로그램 종료");
  }
}
[/Users/ijeong-in/Documents]> javac TryCatch.java
[/Users/ijeong-in/Documents]> java TryCatch      
0
0
0
0
0
프로그램 종료

위 예제를 통해 알 수 있는 것.

  • ClassNotFoundException은 Checked Exception 중 하나이다.
  • Checked Exception는 예외 처리가 없으면 컴파일 단계에서 예외가 발생했다.
  • Checked Exception는 예외 처리가 없으면 프로그램이 전혀 실행되지 않았다.
  • 이론적으로 발생할 수 없는 위치에 있는 예외이지만, 그럼에도 불구하고 예외 처리를 필요로 한다.

Checked Exception은 코드와 별개로 실행 상황에 따라 발생가능성이 있는 예외들로 구성된다.

(DB connect, File/Network 입출력, Thread 충돌 등)

개발자는 Checked Exception가 발생할 수 있는 모든 작업에 예외 처리를 구현해야 하며 그렇지 않으면 컴파일 에러가 발생한다.

다시말하면, 실행 시점에서 발생하는 모든 Checked Exception은 예외 처리가 구현되어 있다고 볼 수 도 있다.

반면 Unchecked Exception 같은 경우, 단순히 개발자의 실수에 의하여 발생하는 경우가 많다.

(0으로 나누기, 크기를 초과한 인덱스 검색, 잘못된 코드, 잘못된 프로그램 사용 등)

컴파일 단계에서 예외 처리가 체크되지 않으며, 때문에 예외 처리가 구현되어 있지 않더라도 컴파일이 가능하다.


커스텀한 예외 만드는 방법

Exception 클래스를 상속받아 새로운 예외 클래스를 생성해보자.

public class IDFormatException extends Exception {
    public IDFormatException(String message) { //생성자의 매개변수로 예외 상황 메시지를 받음    
       super(massage); //Exception의 생성자, 메시지를 문자열 매개변수로 받는다.
    }
}
public class IDFormatTest {

    private String userID;

    public String getUserID() {
        return userID;
    }

    public void setUserID(String userID) throws IDFormatException {
        if (userID == null) {
            throw new IDFormatException("아이디는 null일 수 없습니다.");
        } else if (userID.length() <8 || userID.length() > 20) {
            throw new IDFormatException("아이디는 8자 이상 20자 이하로 쓰세요.");
        } else {
            this.userID = userID;
        }
    }

    //Test
    public static void main(String[] args) {
        IDFormatTest idTest = new IDFormatTest();

        String myID = "tuna";

        try {  
            idTest.setUserID(myID);
        } catch (IDFormatException e) {
            System.out.println(e);
        }

        myID = null;

        try {
            idTest.setUserID(myID);
        } catch (IDFormatException e) {
            System.out.println(e);
        }

    }
}
Comments