ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Item 9: Prefer try-with-resources to try-finally
    독서/Effective Java 2021. 12. 5. 06:30

    try-finally보다는 try-with-resources를 사용하자

     

    Java 라이브러리에는 close 메서드를 호출하여 수동으로 닫아야 하는 많은 자원들이 포함되어 있다.

    close를 직접 호출하는 대표적인 예: InputStream, OutputStream, BufferedReader 등

    만약 이걸 닫지 않는다면? 예측할 수 없을 정도로 심각한 성능 결과를 초래한다.

    꼭 닫게 하기 위해 finalizer를 사용하는 경우도 있지만...(Item 8).

    전통적으로 try-finally 문은 예외 또는 반환에 직면하더라도 리소스가 제대로 닫히도록 보장하는 가장 보편적인 방법이었다.

     

    Try-Finally 사용

    Try-Catch-Finally를 사용하는 가장 익숙한 예는 개인적으로는 JDBC이다. 

    public String getContactEmail(int contactNumber) throws SQLException {
        Connection connection = null;
        PreparedStatement doSelect = null;
        try {
            String contactEmail = null;
            String selectStatement = "SELECT contact_email FROM store WHERE contact_id = ?";
            connection = ConnectionPool.getConnection();
            doSelect = connection.prepareStatement(selectStatement);
            doSelect.setInt(1,contactNumber);
            ResultSet rs = doSelect.executeQuery();
            if ( rs.next() ) {
                contactEmail = rs.getString(1);
                return contactEmail;
            } else {
                return null;
            }
        } catch (SQLException se) {
            // log the exception
            log.error(se);
            // re-throw the exception
            throw se;
        } finally {
            try {
                doSelect.close();
                ConnectionPool.freeConnection(connection);
            } catch (Exception e) {}
        }
    }
    // from https://alvinalexander.com/blog/post/jdbc/-decent-example-of-using-try-catch-finally-with-jdbc/

    이미 finally 문에서 connection을 닫기 위해 또 다른 try-catch 문을 사용한 것을 볼 수 있다. 그래도 하나의 자원을 사용한 것이기 때문에 그나마 try - finally에 대한 가독성이 어느 정도 존재한다.

     

    만약 하나의 자원이 아닌 여러 자원이라면?

    // try-finally is ugly when used with more than one resource!
    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();
        }
    }

    일단 try 내에 또 try 문이 들어가 가독성을 해친다.

    또한, 위의 JDBC의 예를 봤듯이 try, finally 모두 예외를 던질 수 있다(그렇기에 JDBC의 예에서는 try 시의 catch, finally 내에서의 try - catch도 구현이 되어 있다.)

    만약 catch 없이 구현이 되었다면 finally에서의 예외가 try에서의 예외를 덮어버리는 상황이 발생할 수 있고, 그로 인해 개발자는 디버깅에 혼란을 겪을 수 있다.

    그렇기에 Java 7에서부터는 새로운 방식의 자원 종료를 위한 방법이 도입되었다.

     

    Try-with-Resources

    새로운 방법은 try-with-resources[JLS, 14.20.3]를 사용하는 것이다.
    전제 조건은 사용할 자원(JDBC Connector, InputStream 등)이 AutoCloseable 인터페이스를 구현해야 한다는 것밖에 없다.

    닫혀야 하는 리소스를 나타내는 클래스를 작성하는 경우 클래스도 AutoCloseable을 구현해야 한다.

     

    try-with-resources를 사용하면 다음과 같이 가능하다.

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

    이미 기본적으로 autoCloseable이 구현되어 있는 클래스로 해당 작업을 수행했기에 다음과 같이 비즈니스 로직만 볼 수 있게 된다.

     

    여러 자원을 사용하는 경우도 다음과 같이 구현할 수 있다.

    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);
        }
    }

     

    예외 상황은 어떻게 만족할 수 있을까?

    // try-with-resources with a catch clause
    static String firstLineOfFile(String path, String defaultVal) {
      try (BufferedReader br = new BufferedReader(
      		new FileReader(path))) {
      	return br.readLine();
      } catch (IOException e) {
      	return defaultVal;
      }
    }

     

     

    댓글

Designed by Tistory.