Javaでリストを操作するときにfor文をやめてみる

やぎすけAdventCalendar2016 Java

Posted on Dec 10


こんにちは、やぎにいです。
やぎすけ Advent Calendar 2016の10日目です。
ついに10日ですね。今年も残すところあと21日、そしてアドベントカレンダーはのこり15日。そう考えると半分近く来たものですね。

昨日は僕はAndroidアプリ開発のちょっとした小ネタを書きました。
これを書いた当日の夜にも実はタレが増えました。そのうちまとめてご紹介します。

今日はプログラムを書くときに欠かせないリスト操作のお話です。

はじめに

1
2
3
4
5
6
7
List<String> messages = Arrays.asList("やぎにい", 
                                      "うなすけ",
                                      "やぎすけ", 
                                      "アドベントカレンダー", 
                                      "2016-12-10", 
                                      "首を突っ込む", 
                                      "やっていき");

のようなリストがあり、それぞれの要素を1つずつ表示したい時どうやって表示していたでしょうか。
僕は以下の通りやっていました。

1
2
3
for (String str : messages) {
    System.out.println(str);
}

定番ですね拡張for文(for-each)を使って処理してあげると非常にスマートにかけます。
僕は初めて触ってそのまま長いこと使っていたのがC言語だったため最初は

1
2
3
for (int i = 0; i < messages.size(); i++) {
    System.out.println(messages.get(i));
}

の用に書いていましたが、初めてfor-eachに出会った時はすんごい楽だし見やすいなと感動したのを覚えています。

さて、今回の話はこういうリストなどを操作するときにfor文を使わずにやってみようというお話です。
前提条件として以下の2つのリストを使います。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
List<String> msgJapanese = Arrays.asList("やぎにい", 
                                         "うなすけ", 
                                         "やぎすけ", 
                                         "アドベントカレンダー", 
                                         "2016-12-10", 
                                         "首を突っ込む", 
                                         "やっていき");

List<String> msgEnglish = Arrays.asList("yagi2", 
                                        "unasuke", 
                                        "yagisuke",  
                                        "advent-calendar",
                                        "2016-12-10", 
                                        "kubi wo tukkomu", 
                                        "yatteiki");



出力してみる

java8にjava.util.function.Consumerを引数に取るforEach(Consumer action)メソッドが存在します。
名前がいかにもそれっぽい! 早速使ってみる。 Consumerのインスタンスはaccept(T)メソッドで処理を実装できる。引数の消費者。

1
2
3
4
5
msgJapanese.forEach(new Consumer<String>() {
    public void accept(final String str) {
            System.out.println(str);
        }
});

おお、いけた。でもfor-each文でよくね?感が出てきちゃいました。
そこでjava8のラムダ式。こいつを使ってみます。

1
msgJapanese.forEach(str -> System.out.println(str));

……は?1行だと……。
ラムダ式はちょっと慣れるまで読みづらいイメージがありますが、RxJavaでのサンプルコードや知見はラムダ式で転がっていることが多いので僕もラムダ式に慣れていきましょう。


加工してみる

msgEnglishを大文字に加工してみる

1
2
3
List<String> msgEnglishUppercase = new ArrayList<>();
msgEnglish.forEach(str -> msgEnglishUppercase.add(str.toUpperCase()));
msgEnglishUppercase.forEach(str -> System.out.println(str));

出力まですると.forEach(str -> System.out.println(str.toUpperCase()));でいいのでは?となりますが、今回は加工のお話なのでご容赦を。

ふむふむなるほど。でもやっぱり変数を宣言してそれに加工して〜というのがいまいちな気がする。
加工するということは加工した結果を使うわけだからそれを次に渡す、のような処理を行いたいですね。

そんなときはJava8のStreamです。
Streamでは関数を連続して適用するようなことができます。◯◯した結果を✕✕して▲▲する。のようにできます。
やってみましょう。

1
msgEnglish.stream().map(str -> str.toUpperCase()).forEach(str -> System.out.println(str));

これです……まさに求めていたのはこれ! まるまる→ばつばつ→さんかくさんかく 流れるStreamです。
せっかくなのでメソッド参照を使ってみましょう。

1
msgEnglish.stream().map(String::toUpperCase).forEach(str -> System.out.println(str));

つよい……!見た目でわかりやすい。
今までの拡張for文を使って加工してそれを出力よりだいぶコード量もスッキリするはずです。

Stream

じゃあめっちゃStreamってすごいじゃん。となります。 上記ではリストを加工するためにmapを使いました。これは基本的に要素を変換します。 他にも中間操作が存在し、僕がよく使うのを紹介します。

  • filter

これは式が真になる要素を次に渡します。

1
msgJapanese.stream().filter(str -> str == "うなすけ").forEach(str -> System.out.println(str));

とすれば”うなすけ”という文字列だけ取り出すことが出来ます。
数値であれば

1
2
List<Integer> num = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
num.stream().filter(n -> n % 2 == 0).forEach(n -> System.out.println(String.valueOf(n)));

で1〜10の数字の中で偶数だけ取り出すことが出来ます。 他にもsortedskip等の中間操作もあります。

中間操作は上記で上げたようにいろいろありますが、終端操作(いままでは.forEach)も複数種類があります。 こちらも個人的によく使うのも紹介します。

  • count

最終的な要素数を返します。

1
2
3
4
List<Integer> num = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(String.valueOf(
    num.stream().filter(n -> n % 2 == 0).count()
    ));

とやれば、1〜10で偶数の個数を出力することが出来ます。 ※printlnで囲っているため、Streamの部分だけわかりやすいように変な改行をしています。

  • max, min

最小値、最大値を返します。

1
2
3
4
5
6
7
8
Random rnd = new Random();
System.out.println(String.valueOf(
    num.stream().map(n -> rnd.nextInt(101)).max(Integer::compare)
    .orElse(-1)));

System.out.println(String.valueOf(
    num.stream().map(n -> rnd.nextInt(101)).min(Integer::compare)
    .orElse(-1)));

min(Compare) max(Compare)Compareで比較した結果の最大値最小値がOptionalで返ってきます。

ここらへんをよく使います。

おわりに

今日はコード量も多く、長かったですがJavaでリストの操作をするときに拡張for文をやめよう!という話でした。
今回はStreamやラムダ式のようなJava8での機能をふんだんに使いましたが、Java7でも

のようなライブラリを使うと同じような操作をすることができます。
僕もほとんどのAndroidアプリをJava7で触っていますが、そろそろJava8の時期になっていますね。

今回いろいろな操作はGradle Projectで作成しyagi2/java8-list-sampleにおいておきました。
大切なのはMain.javaです。(ちょっとインデント崩れてる)

以上、やぎにいでした!!


このエントリーをはてなブックマークに追加
comments powered by Disqus

<< RxJava+RxAndroid+RxLifecycleを使った非同期処理を行う     Androidアプリ開発のちょっとした小ネタ >>



2018やぎ小屋