본문 바로가기

Java

try-with-resources 를 사용하자

0. 문제 상황

 

 Read 버튼을 누르면 파일을 읽어 TextField에 표시하고, Delete를 누르면 파일을 삭제하는 간단한 Swing Application 이다.

Read 버튼을 누르면 파일을 읽어 내용을 TextField에 표시한다.

 

Read 버튼을 누르면 Scanner 객체를 이용해 파일을 읽는다. Delete 버튼은 File.delete() 메서드로 파일을 삭제한다.

 

Read 버튼을 누르지 않고 Delete 버튼을 먼저 누르면, 파일은 문제없이 삭제된다. 그러나 Read 버튼으로 파일을 읽고 Delete 버튼을 누르면, 파일이 삭제되지 않은것을 확인할 수 있다. 혹시 프로세스에서 사용중이라 삭제가 안되나 싶어 Windows 탐색기에서 삭제를 시도하니 아래와 같은 경고창이 나왔다.

 

다른 프로세스에서 사용중이어서 지울 수 없다는 경고가 나온다.

 

 

1. Scanner.close()

Scanner 로 파일을 읽고, Resource(test.txt) 를 release 하지 않아 발생한 문제이다.

 

public class FileReader {

    public String read () throws FileNotFoundException {
        StringBuilder sb = new StringBuilder();

        try {
            Scanner scanner = new Scanner(new File("test.txt"));

            while(scanner.hasNextLine()){
                sb.append(scanner.nextLine());
            }
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException();
        }

        return sb.toString();
    }
}

 

Scanner를 닫지 않아도, 딱히 문제가 있다고 나오지 않는다. 읽은 파일을 지우는 작업을 하지 않았다면, 아무것도 모르고 그냥 넘어갔을 것이다. 혹시 delete() 할 때, 다른 프로세스가 사용중일경우 Exception 이 발생하는지 확인해봤지만, Exception이 발생하지 않았다.

 

private class DeleteButton extends AbstractAction {
	private JFrame frame;

	DeleteButton(JFrame frame) {
		this.frame = frame;
	}
		@Override
	public void actionPerformed(ActionEvent e) {
		try {
			File file = new File("test.txt");
			if (file.exists()) {
				file.delete();
			}
		} catch (Exception exception){
        	// Exception 이 발생하지 않는다.
			System.out.println(exception.getMessage());
		}
	}
}

 

사용자가 객체를 닫지 않는다면, Scanner 가 계속 열려있어 메모리 누수가 발생하게 된다.

 

2. try-catch-finally

실행을 무사히 마치거나 exception 이 발생하더라도, finally 를 항상 실행한다. finally 에 scanner.close() 를 넣어 메모리 누수를 막을 수 있다.

 

public String read () throws FileNotFoundException {
    StringBuilder sb = new StringBuilder();

    Scanner scanner = null;

    try {
        scanner = new Scanner(new File("test.txt"));

        while(scanner.hasNextLine()){
            sb.append(scanner.nextLine());
        }
    } catch (FileNotFoundException e) {
        throw new FileNotFoundException();
    } finally {
        scanner.close();
    }

    return sb.toString();
}

 

하지만 사용자가 scanner.close() 를 넣어야 하는 것을 인지하고 있지 않다면, 역시 메모리 누수가 발생할 수 밖에 없다.

 

 

3. try-with-resources

 

try-with-resources 는 AutoCloseable 인터페이스를 상속받아 구현한 클래스의 close() 메소드를 실행한다. close()에 해당 자원 사용을 중단하는 코드를 구현한다. try-with-resources 스코프가 끝나면 close() 메소드를 실행한다.

 

Scanner.java

public final class Scanner implements Iterator<String>, Closeable {
	...
    
    public void close() {
        if (closed)
            return;
        if (source instanceof Closeable) {
            try {
                ((Closeable)source).close();
            } catch (IOException ioe) {
                lastException = ioe;
            }
        }
        sourceClosed = true;
        source = null;
        closed = true;
    }
    ...
}
    

Closeable 인터페이스는 AutoCloseable 인터페이스를 상속받는다. Scanner는 Closeable 인터페이스의 close() 메소드를 구현하고 있는 것을 확인할 수 있다.

 

 

try-with-resources 를 적용한 코드

public class FileReader {

	public String read() throws FileNotFoundException {
		StringBuilder sb = new StringBuilder();

		try (Scanner scanner = new Scanner(new File("test.txt"))) {
			while (scanner.hasNextLine()) {
				sb.append(scanner.nextLine());
			}
		} catch (FileNotFoundException e) {
			throw new FileNotFoundException();
		}

		return sb.toString();
	}
}

 

다시 Application 으로 Read/Delete 를 수행하면 파일이 잘 삭제되는 것을 확인할 수 있다.

 

 

4. Source Code

https://github.com/kimjh4930/try-with-resources-example.git

'Java' 카테고리의 다른 글

ClassLoader  (0) 2021.11.30
Class 객체  (0) 2021.11.30
애너테이션 (Annotation)  (0) 2021.11.29
OpenJDK 버전 변경하며 사용하는 방법  (0) 2021.10.20
Reflection 을 이용해 Interface 정보 얻기  (0) 2021.05.03