Programming/Java

[Effective Java] item9 - try-finally 보다는 try-with-resource를 사용하라.

VSFe 2021. 2. 6. 21:05
try-finally 보다는 try-with-resource를 사용하라!

백준을 자바로 풀면 bufferedReader를 참 많이 사용할 것이다. 사실 단순히 문제를 푸는거라 close를 안하고 제출하는 소스가 많긴 한데, 어쨌든 close를 통해 직접 닫아줘야 한다.

위에서 말했던 것 처럼 수많은 코드가 저걸 그냥 안 닫고 넘겨버리는 경우가 있는데, 이런 경우엔 예측 불가능한 성능 문제로 이어지곤 한다. 앞에서 finalizer를 설명하면서 안전 장치로 사용할 수 있다곤 말했는데, 이 친구는 썩 좋은 친구는 아니라서...

static String doReader(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

기존에는 이런 방식으로 try - finally 구조를 많이 사용했다. 은근 나쁘지 않다. try 내부에서 무슨 일이 벌어져도 문제 없이 BufferedReader를 잘 닫을 수 있다. 문제는 자원을 여러개 쓸 때 발생한다.

static String doubleReader(String src, String dist) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dist);
        try {
            byte[] buf = new byte[20];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}
우윀

일단 가독성도 안 좋은데다, 다른 문제가 남아있다. try와 finally 블록에서 예외가 하나씩 터졌다고 가정해보자. 이런 경우 두 에러 중 하나의 에러만 띄우게 된다. (결국 나중에 발생한 에러가 기존 에러를 덮어 씌우니 finally 블록에서의 에러를 띄울 것이다.) 이렇게 되면 디버깅이 조금 복잡해진다...

그러다 자바 7에서 try-with-resource 라는 친구가 등장했다. 이걸 써먹으려면 AutoCloseable 인터페이스를 구현해야 하는데, 어떻게 생겼을까?

public interface AutoCloseable { void close() throws Exception; }

짧다... 아무튼 자바 라이브러리에 내장된 수많은 클래스는 이걸 구현하거나 확장했기에, 일반적으론 사용해도 문제 없다. 다만 클래스를 새로 만들 때 try-with-resource를 쓸 일이 있으면 이걸 구현해야 할 것이다.

그럼 위에 있는 이중 중첩을 바꿔보자.

static String fixedDoubleReader(String src, String dist) throws IOException {
    try (InputStream in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dist)) {
        byte[] buf = new byte[20];
        int n;
        while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
    }
}

일단 가독성은 확실히 괜찮은데, 위에서 제기한 다른 문제인 에러 진단 측면에선 어떨까?

read와 close에서 모두 예외가 발생한다고 가정하자. (close는 AutoCloseable) 이런 경우엔 read에서 발생한 예외가 기록되고, close는 숨겨진다. 이때 Throwable에 있는 getSuppressed 메소드를 활용하면 가져올 수도 있다.

물론 try-finally 처럼 catch절을 사용할 수 있기 때문에, 다수의 예외도 처리할 수 있다.