예외처리 정리

이동규·2023년 4월 9일

예외 처리

Throwable Class

  • 예외 또는 오류를 알기에 앞서서 throwable class를 알아야 한다. 이는 모든 예외 클래스의 최상위 클래스로 예외 처리 및 예외 정보 관리를 위한 메서드와 필드를 제공한다.
  • Throwable 클래스는 Object 클래스를 상속 받음
    • Object클래스는 Java의 모든 클래스가 가지고 있는 메소드와 필드를 정의
      • 즉, throwable 클래스도 이러한 메소드를 사용할 수 있음
      • 그래서 throwable클래스는 예외 처리에 필수적인 getMessage()메소드와 PrintStackTrace()메소드를 정의 (printStackTrace() 메서드는 예외 스택 트레이스를 출력 → 예외 스택 트레이스란 예외가 발생한 메서드의 호출 계층 구조로 이를 통해 예외가 발생한 원인을 파악)

예외

  • 회복 가능
  • Checked Exception / Unchecked Exception
    • Checked Exception : 컴파일 시점에 확인

      public void readFile(String fileName) throws FileNotFoundException {
          BufferedReader reader = new BufferedReader(new FileReader(fileName));
          String line;
          while ((line = reader.readLine()) != null) {
              System.out.println(line);
          }
          reader.close();
      }

      → FileNotFoundException 예외가 발생할 가능성이 있으므로 메소드 선언부에 throws FileNotFoundException을 추가하여 예외 처리를 미루고 있음

      → 이 코드를 호출할 때는 try-catch 블록으로 FileNotFoundException을 처리하거나, 호출한 메소드에서 다시 throws로 예외 처리를 미룰 수 있음

    • Unchecked Exception : 런타임 시점에 확인되는 예외

      public class ArrayExample {
          public static void main(String[] args) {
              int[] arr = {1, 2, 3};
              System.out.println(arr[3]);
          }
      }

      → ArrayIndexOutOfBoundsException이 발생

      → 이 예외는 컴파일 시에 체크되지 않기 때문에, 이 예외를 처리하기 위해서는 예외 처리 코드를 추가해야 함

      → 예외 처리 코드 (try catch 블록)

      public static void main(String[] args) {
              int[] arr = {1, 2, 3};
              try {
                  System.out.println(arr[3]);
              } catch (ArrayIndexOutOfBoundsException e) {
                  System.out.println("배열 인덱스를 벗어났습니다.");
              }
          }

Throw / try-catch-finally 블록

  • Throw
    • throws 절을 이용하여 예외 처리를 미루는 경우에는 해당 예외를 처리할 책임이 있는 상위 메서드에서 예외 처리를 수행해야 함.

      public static void main(String[] args) throws IOException {
          BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
          try {
              String line = reader.readLine();
              System.out.println(line);
          } finally {
              reader.close();
          }
      }

      → throws 절을 이용해서 예외처리를 했으므로, main 메서드를 호출한 상위 메서드에서 예외를 처리해야 한다. (즉, main 메서드를 호출하는 코드는 없으므로 JVM이 예외 처리를 책임)

      → JVM은 기본 예외 처리기를 호출하여 예외 처리 가능 But, 예외 처리는 일반적으로 프로그램에서 적절한 처리를 수행하는 것이 바람직

  • try-catch-finally 블록
    • try 블록 안에서 예외가 발생하면 해당 예외를 던지고, catch 블록에서 예외를 처리, finally 블록에서는 예외 발생 여부와 관계없이 항상 수행되는 코드

      import java.io.BufferedReader;
      import java.io.FileReader;
      import java.io.IOException;
      
      public class FileHandler {
          public static void main(String[] args) {
              BufferedReader reader = null;
              try {
                  reader = new BufferedReader(new FileReader("file.txt"));
                  String line = reader.readLine();
                  System.out.println(line);
              } catch (IOException e) {
                  System.err.println("Error reading file: " + e.getMessage());
              } finally {
                  try {
                      if (reader != null) {
                          reader.close();
                      }
                  } catch (IOException e) {
                      System.err.println("Error closing file: " + e.getMessage());
                  }
              }
          }
      }

      → 파일을 닫는 코드를 finally 블록 내에 작성하여, 파일 핸들을 안전하게 해제할 수 있도록 함 즉, 핸들링!!

  • Throw / try-catch-finally 블록 같이 사용한 예제 코드
import java.io.*;

public class FileReaderClass {
    public static void readFile(String fileName) throws IOException {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(fileName));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
    }
}

public class MainClass {
    public static void main(String[] args) {
        String fileName = "input.txt";
        try {
            FileReaderClass.readFile(fileName);
        } catch (IOException e) {
            System.out.println("파일을 읽는 도중 오류가 발생했습니다: " + e.getMessage());
        } finally {
            System.out.println("프로그램을 종료합니다.");
        }
    }
}

→ readFile 클래스 안에서는 BufferReader 객체를 생성하여 파일을 읽어들이는 동작을 수행하는데 이때 try-finally블록을 사용하여, 파일을 읽어들이는 도중 예외가 발생하더라도 BufferedReader
객체를 항상 닫아주도록 구현

try블록에서 예외가 발생하든 발생하지 않든 항상 실행되므로, finally에서 BufferReader 객체를 닫는 동작을 수행한다.

→ readFile 메서드에서 IOException 예외를 미루는 이유는 파일을 읽어들이는 도중 예외가 발생할 가능성이 있기 때문이다. 만약 예외가 발생하면 main 메서드로 던진다.

→ main 메서드가 예외를 받으면 try-catch-finally블록을 사용하여 예외를 처리

→ 예외 처리를 강제하기 때문에 checked exception

(어떤 메서드가 예외를 발생시킬 가능성이 있으며, 이 예외를 해당 메서드를 호출한 곳에서 처리할 수 있다면, throws키워드를 사용하여 예외를 미루는 것이 좋다)

Chained Exception

  • 하나의 예외가 다른 예외를 발생시키는 경우 발생하는 예외
  • 위의 코드에서 Chained Exception을 보여주는 예제 코드 추가
import java.io.*;

public class FileReaderClass {
    public static void readFile(String fileName) throws FileNotFoundException, IOException {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(fileName));
            String line = null;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            // 파일을 읽는 도중에 예외가 발생한 경우, 예외를 잡아서 새로운 예외를 던진다.
            throw new IOException("파일을 읽는 도중 오류가 발생했습니다.");
						newException.initCause(e);
            throw newException;
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
    }
}

public class MainClass {
    public static void main(String[] args) {
        String fileName = "input.txt";
        try {
            FileReaderClass.readFile(fileName);
        } catch (IOException e) {
            // 예외를 처리할 때, getCause() 메서드를 이용해서 chained exception을 확인할 수 있다.
            System.out.println("파일을 읽는 도중 오류가 발생했습니다: " + e.getMessage());
            System.out.println("원인 예외: " + e.getCause().getMessage());
        } finally {
            System.out.println("프로그램을 종료합니다.");
        }
    }
}

→ readFIle 메서드에서 파일을 읽는 도중 예외가 발생하면 catch 블록에서 IOException을 새로운 예외로 wrapping하여 던지고 있다.

→ 이때, IOException의 생성자에 e를 인자로 전달하여 원래 발생한 예외를 chained exception으로 포함시켜주고 있다.

→ 예외가 발생한 경우 getMessage()메서드를 이용해서 메시지를 출력하고, getCause()메서드를 이용해서 chained exception을 확인하고 있다.

→ FileNotFoundException은 IOException의 하위 클래스 중 하나이다. 그래서 FileNotFoundException은 IOException을 상속받아 구현한다.

즉, FileNotFoundException은 파일을 열 때 파일을 찾을 수 없는 경우에 발생하며, 이 예외는 IOException으로 wrapping된다. 이것이 Chained Exception

(FileNotFoundException은 Java API에서 제공하는 예외 클래스 중 하나로, 파일을 열 때 파일을 찾을 수 없는 경우에 발생 → checked Exception)

  • getCause() / initCause()

→ initCause()를 사용하여 원인 예외를 설정

→ 즉, IOException이 발생한 원인으로 FileNotFoundException을 감싸고 있고 이를 통해 예외 처리에 있어서 원인 예외 FileNotFoundException에 대한 정보를 함께 전달

→ e.getMessage()는 해당 예외 클래스에 정의된 예외 메시지를 반환. 위 코드에서는 IOException
이 발생하므로 "파일을 읽는 도중 오류가 발생했습니다."가 출력

→ e.getCause().getMessage()는 Chained Exception에서 사용된다.

→ 각 예외는 이전 예외의 원인을 저장

→ getCause() 메서드를 호출하면 이전 예외를 반환

→ 즉, 이전 예외의 메시지를 반환하므로 FileNotFoundException에서 정의된 메시지를 반환

profile
진짜 개발자가 되고 싶다

0개의 댓글