Stream close()에 대해

내가 프로젝트에 적극적으로 Stream과 람다를 사용하면서 팀 내에서 Stream, 람다 과도기가 오고있다.

그 중 한 명이 원하는대로 코드가 나오지 않아 나에게 코드를 좀 봐달라고 했는데 보니까 Stream을 사용 후 close() 메서드를 호출하는 것이었다.
다른 코드에서도 본 적이 없었고 나도 그 동안 close()를 사용하지 않았을 뿐더러 생각을 해본적이 없었다(아직 Java 8에 대해 깊이 공부하지 못한 탓이다…).
덕분에 Stream을 사용한 후 close()를 호출하는 것에 대해 생각해보게 되었다.

일단 Collection을 통해 생성한 Stream은 굳이 close를 호출하여 닫을 필요는 없다.
하지만 I/O 리소스를 사용한다면 (e.g. Files.lines()) close()를 명시적으로 호출해야할 필요가 있다.

Stream API doc에도 해당 내용에 대해 언급하고 있다.

Streams have a BaseStream.close() method and implement AutoCloseable, but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset)) will require closing. Most streams are backed by collections, arrays, or generating functions, which require no special resource management. (If a stream does require closing, it can be declared as a resource in a try-with-resources statement.)

그렇다면 왜 I/O 리소스를 사용한 Stream의 경우 close()를 호출해야할까?
왜 같은 Stream 인데도 다르게 동작하도록 API를 설계해서 실수를 만들게 하는 걸까?
(물론 사용전에 단순히 사용법만이 아닌 API도 읽어보면서 공부했다면 얘기가 다르지만 :) )

stackoverflow의 한 질문에 따르면 이는 의도적인 설계라고한다.

무엇이 의도적인가 하니 리소스를 받아온 뒤엔 리소스를 반환해야한다는 것이다.
InputStream과 같이 I/O 리소스를 받아서 사용이 끝나도 해당 리소스는 자동으로 닫히지 않는다.
try-with-resources를 사용하거나 명시적으로 close()를 호출하거나 해야 리소스가 반환된다.
이것이 그대로 위 Stream에도 동일하게 적용된 것이다.
그렇기 때문에 Files.lines 같은 메서드를 통해 Stream을 사용한다면 아래처럼 try-with-resources를 통해 자동으로 닫히게하자.

1
2
3
try (Stream<String> s = Files.lines(...)) {
s.forEach(...);
}

굳이 이런 식으로 설계를 해야했나? 라고 생각할 수도 있다.
기능 뿐만 아니라 이런 논리적인 설계를 생각하는 것이 소프트웨어 개발자로서의 자세이자 미래에 있을 확장에 있어 확고한 지표가 된다고 생각한다.

Share