본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 9 - try-finally 보다 try-with-resouces 를사용하라 - 핵심 정리

by 개발인생 2022. 10. 14.
반응형

아이템 9 - try-finally 보다 try-with-resouces 를사용하라 - 핵심 정리

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

try-finally 는 자바7 버전 부터 더이상 최선의 방법이 아니다.

try-with-resouces 를 사용해야 한다.

public class TopLine {
   // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
   static String firstLineOfFile(String path) throws IOException {
      BufferedReader br = new BufferedReader(new FileReader(path));
      try {
         return br.readLine();
      } finally {
         br.close();
      }
   }

   public static void main(String[] args) throws IOException {
      String path = args[0];
      System.out.println(firstLineOfFile(path));
   }
}

위의 코드는 finally 블럭에서 자원을 반납하는 코드이다.

이 코드에서 딱히 문제가 있는 건 아니다.

자원을 반납하려는 객체가 여러개인 경우에는 어떻게 할까?

public class Copy {
   private static final int BUFFER_SIZE = 8 * 1024;

   // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽)
   static void copy(String src, String dst) throws IOException {
      InputStream in = new FileInputStream(src);
      try {
         OutputStream out = new FileOutputStream(dst);
         try {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0)
               out.write(buf, 0, n);
         } finally {
            out.close();
         }
      } finally {
         in.close();
      }
   }

   public static void main(String[] args) throws IOException {
      String src = args[0];
      String dst = args[1];
      copy(src, dst);
   }
}

자원을 반납하려는 객체가 여러개인 경우에는 위처럼 코드를 작성하면 된다.

먼저 OutputStream 을 반납하고 그 뒤에 InputStream 을 반납한다.

public class Copy {
   private static final int BUFFER_SIZE = 8 * 1024;

   // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽)
   static void copy(String src, String dst) throws IOException {
      InputStream in = new FileInputStream(src);
      OutputStream out = new FileOutputStream(dst);

      try {
         byte[] buf = new byte[BUFFER_SIZE];
         int n;
         while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);

      } finally {
         in.close();
         out.close();
      }
   }

   public static void main(String[] args) throws IOException {
      String src = args[0];
      String dst = args[1];
      copy(src, dst);
   }
}

만약 위처럼 try - catch 블럭을 중첩하지 않고 하나로만 작성하게 된다면

자원이 반납처리되지 않을 수 있다.

만약 in.close(); 에서 에러가 난다면 그 다음 줄인 out.close(); 은 실행되지 않는다.

try - catch 블럭을 중첩 하면 in.close(); 에서 에러가 나더라도 out.close(); 를 실행하게 된다.

public class TopLine {
   // 코드 9-3 try-with-resources - 자원을 회수하는 최선책! (48쪽)
   static String firstLineOfFile(String path) throws IOException {
      try (BufferedReader br = new BufferedReader(
            new FileReader(path))) {
         return br.readLine();
      }
   }

   public static void main(String[] args) throws IOException {
      String path = args[0];
      System.out.println(firstLineOfFile(path));
   }
}

권장하는 방법은 try-with-resouces 를 사용하는 것이다.

코드가 훨씬 간결해지게 된다.

close() 를 우리가 실행하지 않아도 된다.

BufferedReaderAutoCloseable 를 상속한 Closeable 을 구현했기 때문이다.

이런 방법은 자원할 리소스가 여러개 일때 더 빛을 발한다.

public class Copy {
   private static final int BUFFER_SIZE = 8 * 1024;

   // 코드 9-4 복수의 자원을 처리하는 try-with-resources - 짧고 매혹적이다! (49쪽)
   static void copy(String src, String dst) throws IOException {
      try (InputStream   in = new FileInputStream(src);
           OutputStream out = new FileOutputStream(dst)) {
         byte[] buf = new byte[BUFFER_SIZE];
         int n;
         while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
      }
   }

   public static void main(String[] args) throws IOException {
      String src = args[0];
      String dst = args[1];
      copy(src, dst);
   }
}

InputStream 과 OutputStream 의 객체 둘다 close 메서드를 실행해준다.

이것보다 중요한 장점이 있다.

바로 예외를 잡아먹지 않는다는 것이다.

public class BadBufferedReader extends BufferedReader {
   public BadBufferedReader(Reader in, int sz) {
      super(in, sz);
   }

   public BadBufferedReader(Reader in) {
      super(in);
   }

   @Override
   public String readLine() throws IOException {
      throw new CharConversionException();
   }

   @Override
   public void close() throws IOException {
      throw new StreamCorruptedException();
   }
}

BufferedReader 를 상속한 BadBufferedReader 클래스를 작성한다.

readLine(), close() 를 실행할 때 에러를 발생시키도록 한다.

public class TopLine {
   // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
   static String firstLineOfFile(String path) throws IOException {
      BufferedReader br = new BadBufferedReader(new FileReader(path));
      try {
         return br.readLine(); // 에러 발생
      } finally {
         br.close(); // 에러 발생
      }
   }

   public static void main(String[] args) throws IOException {
      String path = args[0];
      System.out.println(firstLineOfFile(path));
   }
}

BadBufferedReader 를 사용해 BufferedReader 객체를 생성한다.

br.readLine(); 실행할 때 에러가 발생하게 되고, br.close(); 를 실행할 때도 에러가 발생하게 된다.

코드를 실행해보면 br.close(); 에서 발생한 에러만 보이게된다.

가장 나중에 발생한 예외만 보인다.

가장 처음에 발생한 예외가 중요할 때가 많지만 가장 나중에 발생한 예외만 보이게 된다는 것이다.

이게 try - finally 를 사용했을 때 발생하는 예외가 먹히는 문제이다.

물론 try - catch - finally 를 이용해 가장 먼저 발생한 예외가 안먹히게 작성할 수 있지만

코드가 많이 지저분해진다.

public class TopLine {
   // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
   static String firstLineOfFile(String path) throws IOException {
      try(BufferedReader br = new BadBufferedReader(new FileReader(path))) {
         return br.readLine();
      }
   }

   public static void main(String[] args) throws IOException {
      System.out.println(firstLineOfFile("pom.xml"));
   }
}

try-with-resouces 를 사용하면 예외가 먹히지 않는다.

가장 먼저 발생한 예외가 보이고 후속으로 발생한 예외도 보인다.

감춰진 예외는 Suppressed 뒤에 붙어서 로그에 출력된다.

물론, try-with-resouces 를 사용해도 catch, finally 블록을 사용할 수 있다.

반응형

댓글