LoginSignup
17
29

More than 3 years have passed since last update.

Java8/9 初心者向け:Stream APIのハマりポイントと対応方法

Last updated at Posted at 2018-03-12

Java8から実装されたStream APIは非常に便利な機能ではあるものの、
Stream特有の処理にハマってしまうことが多いので、対応策を記載する

1.そもそもStreamの呼び出しがわからない

基本的に、下記のパターンだけ覚えていれば十分
・CollectionからStreamに変換する
・配列&可変長引数からStreamに変換する
・IntStreamの呼び出し

※Collection→Streamはインスタンスメソッド、配列→Streamはstaticメソッドを使う

基本的なStream呼び出し
// 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.知らなくて発生しがちなハマりポイント

Stream始めたて時に発生しがちな事象
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

2.1 :IllegalStateExceptionが発生...Streamが再利用できない

Streamは、処理後に必ず別の型に変換(終端処理)しなければならない
ListのようにStreamの状態で置いておく・再利用することは不可能

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などの処理はまとめて処理できる。

SummaryStatisticsの活用
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を使用する

IntStreamに変換する例
// mapToIntでIntStreamに変換する→IntStreamが特別に持つメソッドを使用する
Stream.of(1, 2, 3).mapToInt(e -> e).sum();

対応2(基本型以外の場合):StreamのメソッドかCollectorを利用して処理を行う

Decimalで加算する例
// パターン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から配列に変換する

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を処理しなければならない例
// エラー・・・[型の不一致: Optional<BigDecimal> から BigDecimal には変換できません]
BigDecimal first = Stream.of(BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN).findFirst();

対応:Optionalから中身を取り出すメソッドを付ける

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);
17
29
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
17
29