質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

4回答

7445閲覧

StreamApiとforeachどっちを使ったほうがいい?

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2020/02/05 08:52

編集2020/02/05 08:53

下記のようにリストのリストを作成して、最後に一括して要素(例:999)を追加する処理です

java

1 public static void main(String args[]) { 2 ArrayList<ArrayList<Integer>> data = new ArrayList<ArrayList<Integer>>(); 3 4 for (int i=0; i<=3; i++) { 5 ArrayList<Integer> element = new ArrayList<Integer>(); 6 element.add(i*1); 7 element.add(i*2); 8 element.add(i*3); 9 10 data.add(element); 11 } 12 13 data.forEach(e->e.add(999)); 14 System.out.println(data); 15 }

結果

[[0, 0, 0, 999], [1, 2, 3, 999], [2, 4, 6, 999], [3, 6, 9, 999]]

ただ、いろいろ調べていく過程で

java

1data.stream().map(e->e.add(999));

のように、stream apiを使う書き方もありました。
ただ、こちらの書き方では、ArrayList<ArrayList<Integer>>へキャストすることができなかったので挫折しました。

【質問】
① stream api と foreachの違いはなんでしょうか?foreachではdataが書き換わってしまうという事ぐらいでしょうか?(stream api は新しいcollectionに対しての操作という認識)
② 上記のプログラムのように foreachで処理することに問題はあるでしょうか?
③ stream apiを使うとしたら、どのように元のListにキャストするのでしょうか?

よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

① stream api と foreachの違いはなんでしょうか?foreachではdataが書き換わってしまうという事ぐらいでしょうか?(stream api は新しいcollectionに対しての操作という認識)

凄く雑な言い方をするなら、
forEachメソッドは、拡張for文を使って記述するコレクションの要素への繰り返し操作を、
「for文の中身だけ」記述するやり方です。
for文の中身を記述するための型としてConsumerが定義されています。

一方Stream APIは、ベルトコンベアーか何かを使った流れ作業のベースを作り、
作業に必要なものを組み合わせて工場を作るようなイメージです。

forEachは各要素に逐次処理を行うだけですが、streamの場合はフィルタを掛けたり要素を変換したりといったことが比較的直観的に書けます。

両者ともに元のdataを書き換えることはありません。ただし、今回のように中身がArrayListのような可変オブジェクトである場合は、それが変更されることはありえます。

② 上記のプログラムのように foreachで処理することに問題はあるでしょうか?

全く問題ありません。先述の通り拡張for文でやってることと何ら変わりありません。

③ stream apiを使うとしたら、どのように元のListにキャストするのでしょうか?

「キャスト」という語を使うのは適切ではないです。先述の通りStreamはベルトコンベアーでの作業をしているような状態で、Streamそれ自体はコレクションではありません。最終的に適切な「終端操作」を行うことで結果を出します。
これの場合は、collectというメソッドを使ってコレクションにまとめます。

投稿2020/02/06 19:47

swordone

総合スコア20649

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

他の回答者とほぼ同じ内容ですがポストします。

準備

dataの初期化部分です。誰が書いてもあまり変わりません。for(...)ループでも同じ事ができます。

Java

1List<List<Integer>> data = null, data2 = null; 2Supplier<List<List<Integer>>> supplier = 3 () -> IntStream.rangeClosed(0,3) 4 .mapToObj(k -> Arrays.asList(1*k,2*k,3*k)) 5 .map(ArrayList::new) // to mutable list 6 .collect(Collectors.toList()); 7data = supplier.get();

forEach

① stream api と foreachの違いはなんでしょうか?

forEach()はCollectionとStreamの両方にあります。どちらも引数にConsumerを取ります。違いはThe Difference Between Collection.stream().forEach() and Collection.forEach()に書かれているとおり、Collectionが内部的にIteratorを使用するのに対してStreamはListを順にトラバースします。Iteratorを書き換えると処理順が変わります。この例でその必要はなさそうです。

Java

1data.forEach(e->e.add(999)); 2data.stream().forEach(e->e.add(999));

foreachではdataが書き換わってしまうという事ぐらいでしょうか?(stream api は新しいcollectionに対しての操作という認識)

data.stream().forEach()は元のデータを書き換える副作用があります(新しい出力を作らず終端処理で自己更新する)。(*swordoneさんのdata.forEach()は副作用ではないという意見に同意。以後、Stream処理が入力を汚すという意味で「副作用」を使います)

map/peek

さらに副作用のある例を2つ示します。map()は戻り値を返す必要があります。peek()は戻り値なしです。

Java

1data.stream().map(e -> {e.add(999);return e;}).collect(Collectors.toList()); 2data.stream().peek(e -> e.add(999)).collect(Collectors.toList());

③ stream apiを使うとしたら、どのように元のListにキャストするのでしょうか?

すでに(List<Integer>)のStreamになっているのでキャストできません。.collect(Collectors.toList())で、新しく外側のListを作ってもとの中身を格納します。

すべてを作り直す

中身をすべてコピーして新しく作るやり方です。入力はそのまま残り、副作用はありません。この方法だとdataを作る時にArrayList::newが不要になります。

Java

1data.stream() 2 .map(e -> Stream.concat(e.stream(),Stream.of(999)).collect(Collectors.toList())) 3 .collect(Collectors.toList());

② 上記のプログラムのように foreachで処理することに問題はあるでしょうか?

  • Stream/Collectionは、どちらも副作用がある
  • Streamは、filter/mapなどのメソッドチェーンが使えるので、データを加工するならStreamを使う
  • Collectionは、Iteratorを書き換えて、処理の順番を変えることができる

Streamの利用

Streamhは並行処理で利用することがあり、副作用が起きないようにimmutableなコレクションを扱うほうがよい。また、immutableにしておけば、誤ってコレクションを更新しても例外が発生する。

投稿2020/02/05 14:13

編集2020/02/07 04:40
xebme

総合スコア1081

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2020/02/07 04:44 編集

修正しました。Stream処理が入力を汚すという意味で「副作用」を使う。以下は修正漏れ。 修正前:Stream/Collectionは、どちらも副作用がある 修正後:Streamは副作用がある
guest

0

③ stream apiを使うとしたら、どのように元のListにキャストするのでしょうか?

dataの型を ArrayList<ArrayList<Integer>> にすることに拘るなら。

Java

1import java.util.ArrayList; 2import java.util.List; 3import java.util.stream.Collectors; 4import java.util.stream.IntStream; 5 6class Main { 7 public static void main(String args[]) { 8 ArrayList<ArrayList<Integer>> data = IntStream.rangeClosed(1, 3) 9 .mapToObj(i -> 10 new ArrayList<>(List.of(i*1, i*2, i*3, 999)) 11 ) 12 .collect(Collectors.toCollection(ArrayList::new)) 13 ; 14 15 System.out.println(data); 16 } 17}

① stream api と foreachの違いはなんでしょうか?foreachではdataが書き換わってしまうという事ぐらいでしょうか?(stream api は新しいcollectionに対しての操作という認識)

② 上記のプログラムのように foreachで処理することに問題はあるでしょうか?

修正: 論拠に乏しい記述であった為、修正します。詳細はコメントをご覧下さい。
Stream API にせよ List#forEach にせよ、副作用のある処理を書くには向かないように思います。
もちろん可能ではあるのですが、コードを読むときに少し引っ掛かりがあります。

Stream API は、副作用のある処理を書くのには向きません。
純朴なfor文で書いた方が素直に読めるのならそれが正解でしょう。

Java

1import java.util.ArrayList; 2import java.util.List; 3 4class Main { 5 public static void main(String args[]) { 6 ArrayList<ArrayList<Integer>> data = new ArrayList<>(); 7 for(int i = 1; i <= 3; ++i) { 8 data.add( 9 new ArrayList<>(List.of(i*1, i*2, i*3, 999)) 10 ); 11 } 12 13 System.out.println(data); 14 } 15}

あるいは

Java

1for(var row: data) { 2 row.add(999); 3} 4 5System.out.println(data);

投稿2020/02/05 13:17

編集2020/02/06 23:28
LouiS0616

総合スコア35658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swordone

2020/02/06 19:42

Iterator#forEachの引数Consumerには、Streamで使うときのようなステートレス性は要求されていないため、forEachの場合は問題ないかと思います。本質的に拡張for文を使うのと何ら変わりないですから。
LouiS0616

2020/02/06 23:24

確かにリファレンスを見ても私の主張に沿う記述はありませんでした。 それどころか『Consumerは他のほとんどの関数型インタフェースと異なり、副作用を介して動作することを期待されます。』と真逆のことが書いてあります。 (https://docs.oracle.com/javase/jp/13/docs/api/java.base/java/util/function/Consumer.html) 誤解を生まないよう回答に少し追記をしておきます。 ご指摘ありがとうございます。
xebme

2020/02/07 04:16

参考になりました。質問のforEachにフォーカスする場合はということで納得しました。 Cosumerを純粋関数型処理からみてどうか。Consumerを畳み込みの出力用バッファに適用するなら関数型処理全体では純粋性を保てるので問題ないでしょう。関数型処理でConsumerに限らず入力を汚すのならやはり「副作用」ですね。
guest

0

③ stream apiを使うとしたら、どのように元のListにキャストするのでしょうか?

ひとつの例です。

java

1 public static void main(String args[]) { 2 final List<ArrayList<Integer>> data = 3 IntStream.rangeClosed(0, 3) 4 .mapToObj(i -> 5 IntStream.rangeClosed(1, 3) 6 .mapToObj(num -> Integer.valueOf(i * num)) 7 .collect(Collectors.toList() 8 ) 9 ) 10 .map(ArrayList::new) 11 .collect(Collectors.toList()); 12 System.out.println(data); 13 14 data.forEach(it -> it.add(999)); 15 System.out.println(data); 16 }

① stream api と foreachの違いはなんでしょうか?foreachではdataが書き換わってしまうという事ぐらいでしょうか?(stream api は新しいcollectionに対しての操作という認識)

data.forEach(it -> it.add(999));は、data.stream().forEach(it -> it.add(999));の短縮形|省略形だと捉えてみてはどうでしょうか?forEach()メソッドもStreamAPIの一部だ、と。ただ、forEach()メソッドの中身をみると実際の実装は違うようですけれど。

② 上記のプログラムのように foreachで処理することに問題はあるでしょうか?

たとえば、StreamAPIを使うとparallel()を挟むだけで簡単に並列処理ができたりしますね(この程度の処理ではむしろ効率悪くなりますけど)。

java

1 final List<ArrayList<Integer>> data = 2 IntStream.rangeClosed(0, 3) 3 .parallel() 4 .mapToObj(i ->

余談ですが、StreamAPIを使い始めて、collectとか使わなくちゃいけなくて、なんかめんどうくさいなぁ、とおもいはじめてKotlinをみると、ラクそうにみえてくるとおもいます。

kotlin

1 fun main(args: Array<String>) { 2 val data: List<MutableList<Int>> = 3 (0..3).map { i -> 4 (1..3).map { num -> num * i }.toMutableList() 5 } 6 println(data) 7 8 data.forEach { it.add(999) } 9 println(data) 10 }

投稿2020/02/05 12:45

編集2020/02/05 13:26
shiketa

総合スコア3971

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問