1. 概要

Java 8にラムダ式が導入されたことで、より簡潔で機能的な方法でコードを記述できるようになりました。 StreamsおよびFunctionalInterfaces は、Javaプラットフォームにおけるこの革新的な変更の中心です。

このクイックチュートリアルでは、リソースの観点からJava8ストリームを明示的に閉じる必要があるかどうかを学習します。

2. ストリームを閉じる

Java 8 ストリームは、AutoCloseableインターフェースを実装します。

public interface Stream<T> extends BaseStream<...> {
    // omitted
}
public interface BaseStream<...> extends AutoCloseable {
    // omitted
}

簡単に言えば、 ストリームは、使い終わったときに借りて返すことができるリソースと考える必要があります。 ほとんどのリソースとは対照的に、ストリームを常に閉じる必要はありません。

これは最初は直感に反するように聞こえるかもしれません。そこで、Java8ストリームを閉じる必要がある場合と閉じない場合を見てみましょう。

2.1. コレクション、配列、およびジェネレーター

ほとんどの場合、Javaコレクション、配列、またはジェネレーター関数からStreamインスタンスを作成します。 たとえば、ここでは、StreamAPIを介してStringのコレクションを操作しています。

List<String> colors = List.of("Red", "Blue", "Green")
  .stream()
  .filter(c -> c.length() > 4)
  .map(String::toUpperCase)
  .collect(Collectors.toList());

場合によっては、有限または無限のシーケンシャルストリームを生成しています。

Random random = new Random();
random.ints().takeWhile(i -> i < 1000).forEach(System.out::println);

さらに、配列ベースのストリームを使用することもできます。

String[] colors = {"Red", "Blue", "Green"};
Arrays.stream(colors).map(String::toUpperCase).toArray()

これらの種類のストリームを処理するときは、明示的に閉じないでください。 これらのストリームに関連する唯一の貴重なリソースはメモリであり、 ガベージコレクション (GC)はそれを自動的に処理します。

2.2. IOリソース

ただし、一部のストリームは、ファイルやソケットなどのIOリソースによってサポートされています。 たとえば、 Files.lines()メソッドは、指定されたファイルのすべての行をストリーミングします。

Files.lines(Paths.get("/path/to/file"))
  .flatMap(line -> Arrays.stream(line.split(",")))
  // omitted

内部的には、このメソッドは FileChannel インスタンスを開き、ストリームが閉じられると閉じます。 したがって、ストリームを閉じるのを忘れると、基になるチャネルは開いたままになり、リソースリークが発生します。

このようなリソースリークを防ぐために、try-with-resourcesイディオムを使用してIOベースのストリームを閉じることを強くお勧めします。

try (Stream<String> lines = Files.lines(Paths.get("/path/to/file"))) {
    lines.flatMap(line -> Arrays.stream(line.split(","))) // omitted
}

このようにして、コンパイラはチャネルを自動的に閉じます。 ここで重要なポイントは、すべてのIOベースのストリームを閉じることです

すでに閉じられているストリームを閉じると、IllegalStateExceptionがスローされることに注意してください。

3. 結論

この短いチュートリアルでは、単純なストリームとIOが多いストリームの違いを確認しました。 また、これらの違いが、Java8ストリームを閉じるかどうかの決定にどのように影響するかを学びました。

いつものように、サンプルコードはGitHubから入手できます。