セミコロンレスJavaはJava8の夢を見るか?〜変数編〜
前回の記事で書き残した部分である変数についてです.
セミコロンレスJava8以前の変数定義
変数を定義したい場合は,今までのセミコロンレスJavaでは拡張for文を使っていました.
for (int n : new int[]{0}) { if (System.out.printf("%d\n", n) == null) {} }
また,変数の値の変更はかなり面倒でした.
for (int n : new int[]{300}) { if (n = n / 10) {} if (System.out.printf("%d\n", n) == null) {} }
if文などを使ってセミコロンが入り込まないように記述しなければいけません.
特に変数の型が変わるような物は更にfor文を挟まなければいけません.
for (int n : new int[]{0}) { for (Integer m : new Integer[]{n}) { if (System.out.printf("%d\n", m) == null) {} } }
ジェネリクスな型の変数を定義する場合は,更に面倒くさく,ジェネリクスな型の配列を定義できないため,リストにしなければいけないなど,更にソースコードが複雑になります.
for (int n : new int[]{0}) { for (java.util.List<Integer> l : java.util.Arrays.asList(java.util.Arrays.asList(0))) { if (System.out.printf("%d\n", l.get()) == null) {} } }
セミコロンレスJava8時代の変数定義
では,Java8時代のセミコロンレスJavaでの変数の扱いはどのように変化するのでしょうか.
これもStreamAPIが強力な武器になります.*1
StreamAPIを用いた変数定義と変数の使用を見て行きましょう.
まずは先ほどの例をStreamAPIを用いて記述していきます.
まず,StreamAPIのStream#of等を用いると,値が一つのStreamを生成することができます.
つまり,初期値を与えて変数を保持するコンテナ(以下,変数コンテナ)を生成することになります.
先程の例はintのStreamなのでIntStreamですね.
IntStream.of(0)
値を使用したい場合は,forEachなどで変数コンテナから変数を取って処理します.
出力をさせたいので,forEachにn -> System.out.println(n)を渡します.
IntStream.of(0) .forEach(n -> System.out.println(n))
後はこれをセミコロンレス化するだけです.
if文に挟んで,forEachをpeekにして,countを呼び出せばいいだけですね.
if (java.utill.stream. IntStream.of(0) .peek(n -> System.out.println(n)) .count() == 0) {}
どうでしょう?
かなり簡潔に記述できていると思います.
更に,変数の値を変更する処理を挟んでみます.
変数の値を変更する場合はmapを使います.
前回のFizzBuzzでもこのテクニックを使っていました.
300を初期値で与えて,30で割った値を出力させてみます.
if (java.utill.stream. IntStream.of(300) .map(n -> n / 30) .peek(n -> System.out.println(n)) .count() == 0) {}
新しい変数を定義したい場合はふた通りの書き方ができます.
まず,一つ目として,新しい変数を定義して,今使っていた変数を使用しない場合です.
この場合は先ほど見たmapを使用すればよいです.
例として,intの変数をボクシングしてIntegerの変数にしてみます.
if (java.utill.stream. IntStream.of(0) .mapToObj(n -> n) .peek(n -> System.out.println(n)) .count() == 0) {}
IntStreamからStreamへの変換になるためmapToObjを使うことになりますね.
ボクシングしたい場合はboxedを使用してもよいですが,一般的に記述したいためmapToObjを使用しました.
ジェネリクスな型の変数に変換する場合でも同じイディオムを使うことができるため,for文の時よりもより一層簡潔に記述できます.
if (java.utill.stream. IntStream.of(0) .mapToObj(n -> java.util.Arrays.asList(n)) .peek(l -> System.out.println(l.get(0))) .count() == 0) {}
Streamは同じコンテナを使って格納している値の型を変える事ができるため,こういった書き方ができます.
一方で,一度変換してしまうと,変換前の値を使用することはできなくなります.
では,複数の変数を用いたい場合はどうすればよいでしょうか?
少し複雑なイディオムになりますが,peek(forEach)を応用して,peek内で新しいStream(変数コンテナ)を生成すればよいです.
複雑になるため,まずはセミコロンありで記述してしながら説明していきます.
例題として,8を7で割ったあまりを「8 % 7 = 1」という様な形式で出力するプログラムを考えます.
普通のJavaで記述すれば以下のようになります.
int n = 8; int m = n % 7; System.out.printf("%d % 7 = %d\n", n, m);
ではこれをStreamAPIで記述していきます.
まず変数nのための変数コンテナを定義します.
IntStream.of(8) // n
続いて変数mを定義していくのですが,mapではなくforEachを使用して,変数mのための新しい変数コンテナを生成します.
この時forEachを使うことで変数コンテナから変数を取り出すことができます.
IntStream.of(8) // n .forEach(n -> IntStream.of(n % 7) // m // );
最後にmの変数コンテナのforEachを呼んで値を出力すれば同様のプログラムになります.
IntStream.of(8) // n .forEach(n -> IntStream.of(n % 7) // m .forEach(m -> System.out.printf("%d % 7 = %d\n", n, m)) );
後は,これをセミコロンレス化してあげます.
nの変数コンテナの部分をセミコロンレス化すれば十分です.
if (java.util.stream. IntStream.of(8) // n .peek(n -> java.util.stream.IntStream.of(n % 7) // m .forEach(m -> System.out.printf("%d % 7 = %d\n", n, m)) ).count == 0) {}
どうです?
StreamAPI+ラムダ式はJavaだけでなく,セミコロンレスJavaにも大きな変化をもたらすということが分かるかと思います.
*1:Optionalでも良いですが,プリミティブ型との変換が若干弱いのでStreamAPIのほうが良いです