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

## 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を流している対象のリスト長とを比較する

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

```java: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使えば楽になるので皆さん使いましょう。


```java: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;
        }
    }
}
```