テキストデータを読み込む場合、言語にかかわらず1行ずつ読み込んで処理します。ここではこのイディオムを"read-lines"と呼ぶことにします。

スクリプト言語なんかだと大抵、最初からforeachで済んでしまっていたりしますが、初期のJavaではちょっと面倒な手順が必要でした。

新しいバージョンではだいぶ簡単簡潔に書けるようになりましたが、今度はたくさんありすぎて、どれがなんなのかが良くわからなくなっています（大袈裟）。

そこで今回は、"read-lines"の変遷についてまとめてみました。

最近Javaを始めた方がバージョンが古い時に書かれたソースコードを読むときに役に立つ...かも知れません。









目次 基本

BufferedReader （Java1.1～）

（Java1.1～） Scanner と拡張for文 （Java5～）

と拡張for文 （Java5～） java.nio.file.Files とtry-with-resources（Java7～）

とtry-with-resources（Java7～） Streamとラムダ式 （Java8～）

付録：標準入力の"read-lines"

※Java1.1より前については調べていません。

※本文中の標準APIのJavadocへのリンクは、すべてJava8のものにリンクしています。







基本 Javaでは、入力ソースを扱うクラスとして、主に java.io.InputStream と java.io.Reader という２つの基本クラスがあります。 InputStream はバイトストリームの入力、 Reader は文字ストリームの入力を行うクラスです。いずれも入力ストリームを表します。



以下の項では、小文字のアルファベットで書いたテキストファイルを読み込み、逐次その内容のアルファベットを大文字に変換する処理を書きます。 入力データ( files.txt ) aaa bbb ccc 実行結果の例 AAA BBB CCC



BufferedReader （Java1.1～） Java5より前のバージョンでは、read-linesする一般的な方法は java.io.BufferedReader で入力文字ストリームをラップするものでした。 機能的には十分なんですけれど、簡潔さに欠けますね。

ちなみに、対応する Reader がない場合は、 InputStreamReader でラップする必要があります。



例： BufferedReader を使ったread-lines try { BufferedReader br = new BufferedReader( new FileReader( "file.txt" )); try { while ( true ) { String line = br.readLine(); if (line == null ) { break ; } System.out.println(line.toUpperCase()); } } finally { br.close(); } } catch (IOException e) { e.printStackTrace(); }





Scanner と拡張for文 （Java5～） Java5では、 java.util.Scanner という、よりread-linesをしやすいクラスが追加されました。 Scanner には、複数の入力ソースに対応したコンストラクターが用意されています。

String が引数のコンストラクターもあります。 FileInputStream などのクラスから類推すると、文字列はファイルパスかと思ってしまい紛らわしいのですが、 Scanner の場合、文字列はそれ自体が入力ソースです。



例： Scanner を使ったread-lines Scanner scanner = new Scanner( new File( "file.txt" )); try { while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println(line.toUpperCase()); } } finally { scanner.close(); } Scanner scanner2 = new Scanner( "aaa

bbb

ccc" );





Scanner は Iterator インターフェイスを実装しているので、イテレーターとして使うこともできます。 例： Scanner をイテレーターとして使ってread-linesする Scanner scanner = new Scanner( "aaa

bbb

ccc" ); try { Iterator<String> it = scanner; while (it.hasNext()) { String line = it.next(); System.out.println(line.toUpperCase()); } } finally { scanner.close(); }

でも、これだと直接使うのと変わらないのであまり面白くありません。

どうせなら（？）直接、拡張for文で使ってみたいですね。

ただそれには Iterable インターフェイスを実装している必要があります。 あまり得することはありませんが、いくつかの例を書いておきます。



例： Scanner を Iterable に変換して拡張for文を使う Scanner scanner = new Scanner( "aaa

bbb

ccc" ); try { final Iterator<String> it = scanner; Iterable<String> iterable = new Iterable<String>() { public Iterator<String> iterator() { return it; } }; for (String line : iterable) { System.out.println(line.toUpperCase()); } } finally { scanner.close(); }



Apache Commons Collections4（バージョン4）の IteratorUtils というクラスを使うともっと簡潔に書けます。



例：Commons Collections4の IteratorUtils.asIterable を使って Scanner を Iterable に変換 Scanner scanner = new Scanner( new File( "file.txt" )); try { for (String line : IteratorUtils.asIterable(scanner)) { System.out.println(line.toUpperCase()); } } finally { scanner.close(); }





java.nio.file.Files とtry-with-resources（Java7～） Java7では、New I/O 2の機能のひとつとして、 java.nio.file.Files クラスが追加されました。このクラスはユーティリティークラスです。 Java7では他にも、言語機能にtry-with-resourcesが追加されています。これは、直接read-linesとは関係ありませんが、read-linesのコードが少し簡潔に書けるようになります。特に、コンストラクターでは無くファクトリーからインスタンスを生成する場合は、 if (in != null) in.close; のようなコードを書かなくてはいけなくなりますので、try-with-resourcesがあればそれを考えなくても良くなります。



例： Files.newBufferedReader とtry-with-resourcesでread-lines try (BufferedReader br = Files.newBufferedReader(Paths.get( "file.txt" ), StandardCharsets.UTF_8)) { while ( true ) { String line = br.readLine(); if (line == null ) { break ; } System.out.println(line.toUpperCase()); } } catch (IOException e) { e.printStackTrace(); }





Files クラスには他にも、1行ずつ読み込むread-linesとは違いますが、全ての行を List として返す Files.readAllLines というメソッドもあります。

ただし、一度にすべてのデータを ArrayList に読み込んでしまうので、大きなサイズのファイルの読み込みには向きません。ドキュメントにもそう書いてあります。







Streamとラムダ式＋関数型インターフェイス （Java8～） Java8では、Stream APIと言語機能としてラムダ式と関数型インターフェイスが追加されました。

Streamはread-linesにぴったりの機能なわけですが、ラムダ式＋関数型インターフェイスと組み合わせることで真価を発揮します。 Files クラスには、Streamに対応した Files.lines メソッドが追加されています。また、同類の機能として BufferedReader#lines メソッドが追加されています。



例： Files.lines でread-lines try (Stream<String> a = Files.lines(Paths.get( "file.txt" ))) { a.map(String::toUpperCase).forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); }



