Java8から実装されたStream APIは非常に便利な機能ではあるものの、
Stream特有の処理にハマってしまうことが多いので、対応策を記載する
1.そもそもStreamの呼び出しがわからない
基本的に、下記のパターンだけ覚えていれば十分
・CollectionからStreamに変換する
・配列&可変長引数からStreamに変換する
・IntStreamの呼び出し
※Collection→Streamはインスタンスメソッド、配列→Streamはstaticメソッドを使う
// Collection(List/Set等)の場合
Collection<BigDecimal>list = List.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN);
Stream<BigDecimal>stream = list.stream();
// 配列の場合
BigDecimal[] array = new BigDecimal[] {BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN};
Stream<BigDecimal>stream = Stream.of(array);
// 可変長引数の場合(配列の場合と同じメソッドでOK)
Stream<BigDecimal>stream = Stream.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN);
// intのループ処理を行う場合 ※ for(int i=0; i<=100 i++)...と同等の処理
IntStream stream = IntStream.rangeClosed(0, 100);
2.知らなくて発生しがちなハマりポイント
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
2.1 :IllegalStateExceptionが発生...Streamが再利用できない
Streamは、処理後に必ず別の型に変換(終端処理)しなければならない
ListのようにStreamの状態で置いておく・再利用することは不可能
Stream<String>stream = Stream.of("","","");
// 何らかの処理を行うと、Streamが閉じてしまう(回避方法はない)
stream.collect(Collectors.toCollection(ArrayDeque::new));
// すでに閉じているStreamは再利用できない
stream.count();
// スタックトレース
//Exception in thread "main" java.lang.IllegalStateException: stream has already been //operated upon or closed
// at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
// at java.base/java.util.stream.ReferencePipeline.count(ReferencePipeline.java:538)
// at main.TestFunc.main(TestFunc.java:11)
対応1:変数や戻り値にStreamを使わない。Collection(List等)かMapを使い、Streamに毎回変換する
// 何かのコレクションで変数を置いてから...
List<BigDecimal> list = List.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN);
// リストに変換する
Deque<BigDecimal> deque = list.stream().collect(Collectors.toCollection(ArrayDeque::new));
// 件数を数える。各処理ごとにStreamを作成する
long count = list.stream().count();
対応2:(特殊例)Average・Sum・Countなどの処理はまとめて処理できる。
List<BigDecimal> list = List.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN);
// SummaryStatisticsに変換する。なお、DecimalSummaryStatisticsは存在しない
IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(BigDecimal::intValueExact));
// 一度のStream処理から、複数の結果を得られる
summary.getMax();
summary.getAverage();
summary.getSum();
2.2 Streamに型を限定したメソッドがない
例えば、Stream<Integer>に対してsum()するメソッドがない。
// このようなメソッドがない
Stream.of(1,2,3).sum();
対応1(基本型の場合):基本型専用の特殊Streamを使用する
// mapToIntでIntStreamに変換する→IntStreamが特別に持つメソッドを使用する
Stream.of(1, 2, 3).mapToInt(e -> e).sum();
対応2(基本型以外の場合):StreamのメソッドかCollectorを利用して処理を行う
// パターン1:初期値0で、Stream内のDecimal値を全て足す処理=合計を求める処理を行う。従来のfor文に近い処理方法
Stream.of(1, 2, 3).map(BigDecimal::valueOf).reduce(BigDecimal.ZERO,BigDecimal::add);
// パターン2:Collectorを準備しておき、それを使用する。何度も再利用したい場合に有効
Collector<BigDecimal, ?, BigDecimal> sum = Collectors.reducing(BigDecimal.ZERO, BigDecimal::add);
Stream.of(1, 2, 3).map(BigDecimal::valueOf).collect(sum);
Stream.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN).collect(sum);
2.3 配列・List・Mapへの戻し方がわからない
基本的に、下記のパターンだけ覚えていれば十分
・StreamからCollectionに変換する
・Streamから配列に変換する
// List(指定なし)の場合
List<BigDecimal> list = Stream.of(BigDecimal.ZERO, BigDecimal.ONE).limit(1).collect(Collectors.toList());
// その他コレクションの場合
Deque<BigDecimal> deque = Stream.of(BigDecimal.ZERO, BigDecimal.ONE).limit(1).collect(Collectors.toCollection(LinkedList::new));
// 配列の場合
BigDecimal[] array = Stream.of(BigDecimal.ZERO, BigDecimal.ONE).limit(1).toArray(BigDecimal[]::new);
2.4 Optionalの処理方法がわからない
FindFirstメソッドでは、結果がnullかもしれないため、Optionalで囲ってプログラマーにnull時の処理を定義するよう促している。
なお、Optionalの状態では中身(下例ではBigDecimal)に対して処理(BigDecimal.add()など)をすることはできない
今のところ、Stream以外ではこのような処理方式は行われていないのでハマりやすい
// エラー・・・[型の不一致: Optional<BigDecimal> から BigDecimal には変換できません]
BigDecimal first = Stream.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN).findFirst();
対応:Optionalから中身を取り出すメソッドを付ける
// 要素がないことも想定している場合(今回は、要素がなければ0とする)
BigDecimal first = Stream.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN).findFirst().orElse(BigDecimal.ZERO);
// 異常が発生しない限り要素なしにならない場合(NoSuchElementExceptionをスローする)
BigDecimal first = Stream.<BigDecimal>of().findFirst().get();
// 異常が発生しない限り要素なしにならない場合(指定した例外をスローする)
BigDecimal first = Stream.<BigDecimal>of().findFirst().orElseThrow(IllegalStateException::new);