3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Java][StreamAPI]allMatch()について

Last updated at Posted at 2017-10-21

Streamの落とし穴

Java8のStreamは非常に便利だがあくまでも「フロー」を提供するものであることは注意する必要がある。その中でもallMatchは非常に危険!というか注意して使わないと副作用が起きる。なぜならallMatchは短絡評価をするからである(判定条件にそぐわないものがあった場合、以降の処理を継続せず、そこで処理を終了する)

テスト内容

文字列を保持するクラスをつくり、それを保持するListに対して、文字列長がすべて5文字以下であるかの判定をし、かつ6文字以上のデータが含まれていた場合にはその文字列の代わりにsorryという文字列を代入するプログラム。以下の4種類の処理方法でテスト。

  • A. Listに対して直接allMatchを実行
  • B. mapしてからStream処理内でallMatchを実行
  • C: mapをして一度Listに保存してから新たにStreamをひらいてallMatchを実行
  • D: 処理結果をfilterしてその数とStreamを流している対象のリスト長とを比較する

サンプルコード。簡単なものだとおもう。

MyProject.java
public class MyProject {

    public static void main(String[] args) {

        List<String> baseStrList = Arrays.asList("test", "tetetete", "tetetete", "tetetete", "tetetete");
        List<TestData> listA = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        List<TestData> listB = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        List<TestData> listC = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        List<TestData> listD = baseStrList.stream().map(TestData::new).collect(Collectors.toList());

        boolean a = listA.stream().allMatch(TestData::checkLength);
        boolean b = listB.stream().map(TestData::checkLength).allMatch(e -> e);
        List<Boolean> list3Temp = listC.stream().map(TestData::checkLength).collect(Collectors.toList());
        boolean c = list3Temp.stream().allMatch(e -> e);
        boolean d = listD.size() == listD.stream().filter(TestData::checkLength).count();

        System.out.println("a:" + a + ", list:" + listA.toString());
        System.out.println("b:" + b + ", list:" + listB.toString());
        System.out.println("c:" + c + ", list:" + listC.toString());
        System.out.println("d:" + d + ", list:" + listD.toString());
    }


    public static class TestData {
        public String content = "fefe";

        @Override
        public String toString() {
            return content;
        }

        public TestData(String a) {
            this.content = a;
        }

        public boolean checkLength() {

            if (getContent().length() > 5) {
                setContent("sorry");
                return false;
            }
            return true;
        }

        public String getContent() {
            return content;
        }

        public void setContent(String content) {
            this.content = content;
        }
    }
}

結果

a:false, list:[test, sorry, tetetete, tetetete, tetetete]
b:false, list:[test, sorry, tetetete, tetetete, tetetete]
c:false, list:[test, sorry, sorry, sorry, sorry]
d:false, list:[test, sorry, sorry, sorry, sorry]

結果考察

  • A: allMatchの短絡評価によって、2要素目のみsorryが代入されて、そのあとの要素は処理されないまま。
  • B: StreamAPIのmap()を挟んだとしてもallMatch()の手中にいるため、残念ながらAと同じ結果になる
  • C: 一度新規にListを生成しているため、効率は悪いが全要素に対して正しく処理が行われている
  • D: こちらは新規にListを生成していないのでcよりも効率がいいはず。。。

以上からallMatchの正しい使い方を理解する必要があることがわかる

個人的にはallMatchを使用する場合はListに対してなんらかの確認を行いたい場合のみがいと思う。今回のようにデータに破壊をもたらす場合、または何か特別な処理を行ってその結果を集計する場合などは CあるいはDの方式でやるのがよいかと思われる

[2017/10/23 09:40追記]
また、当然空リストに対してのallMatchはtrueなので気をつけよう。Dの方式でも同様の結果になる

##ちなみに
lombok使えば楽になるので皆さん使いましょう。

MyProject.java

import lombok.Data;
import lombok.val;

import java.util.Arrays;
import java.util.stream.Collectors;

public class MyProject {

    public static void main(String[] args) {

        val baseStrList = Arrays.asList("test", "tetetete", "tetetete", "tetetete", "tetetete");
        val listA = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        val listB = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        val listC = baseStrList.stream().map(TestData::new).collect(Collectors.toList());
        val listD = baseStrList.stream().map(TestData::new).collect(Collectors.toList());

        val a = listA.stream().allMatch(TestData::checkLength);
        val b = listB.stream().map(TestData::checkLength).allMatch(e -> e);
        val list3Temp = listC.stream().map(TestData::checkLength).collect(Collectors.toList());
        val c = list3Temp.stream().allMatch(e -> e);


        val d = listD.size() == listD.stream().filter(TestData::checkLength).count();

        System.out.println("a:" + a + ", list:" + listA.toString());
        System.out.println("b:" + b + ", list:" + listB.toString());
        System.out.println("c:" + c + ", list:" + listC.toString());
        System.out.println("d:" + d + ", list:" + listD.toString());
    }
    
    @Data
    public static class TestData {
        public String content = "fefe";

        @Override
        public String toString() {
            return content;
        }

        public TestData(String a) {
            this.content = a;
        }

        public boolean checkLength() {

            if (getContent().length() > 5) {
                setContent("sorry");
                return false;
            }
            return true;
        }
    }
}
3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?