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

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

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

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

Q&A

解決済

3回答

2399閲覧

【Java】効率的なロジックはどちらか

masa105

総合スコア26

Java

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

0グッド

0クリップ

投稿2017/02/25 10:01

実装1、2のような繰り返し処理があるとします。この繰り返し処理はある条件に合致すると処理を終了します。
この場合に効率的な実装は1、2のどちらだと思いますか。個人的にはStreamの使う実装2の方が非効率だと感じます。(必ず10000の変数が生成されてしまうため)

Stream APIが登場してから「forは使うな!」というような記事を多くみかけ、はたして本当に使うべきではないのか疑問に思った次第です。
※並列化は考えません。

Java

1//実装1 2public void hoge(){ 3 for(long x = 1; x < 10000; x++){ 4 if(条件xxxx){ 5 return; 6 } 7 8 return; 9} 10 11// 実装2 12public void fuga(){ 13 LongStream.range(1, 10000).forEach(x -> { 14 if(条件xxxx){ 15 return; 16 } 17 }); 18 19 return; 20}

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

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

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

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

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

guest

回答3

0

ベストアンサー

Java 9からはtakeWhile()が使えます。

Java

1import java.util.stream.LongStream; 2 3public class StreamWhile { 4 public static void main(String[] args) throws Exception { 5 hogeFor(); 6 hogeStream(); 7 hogeStreamTakeWhile(); 8 } 9 10 public static void hogeFor() { 11 for (long x = 1; x < 10000; x++) { 12 if (x > 10) { 13 return; 14 } 15 System.out.println(x); 16 } 17 } 18 19 public static void hogeStream() { 20 LongStream.range(1, 10000).forEach(x -> { 21 if (x > 10) { 22 return; 23 } 24 System.out.println(x); 25 }); 26 } 27 28 // Java 9 から使える takeWhile を使用 29 public static void hogeStreamTakeWhile() { 30 LongStream.range(1, 10000).takeWhile(x -> x <= 10).forEach(x -> { 31 System.out.println(x); 32 }); 33 } 34}

三つのメソッドの出力は同じです。条件チェックが動く回数はhogeFor()が11回に対して、hogeStream()が10000回も動作すると言うことが問題だと言うことだと思います。しかし、takeWhile()を使ったhogeStreamTakeWhile()は同じく11回に抑えられます。

ただし、このメソッドはJava 9からです。2月25日現在、Java 9はまだベータ版です。Java 9の正式リリースは2017年7月27日の予定です。あと5ヶ月ほどですが、気長に待ってください。


なお、とても個人的な見解ですが、Java 8から追加されたStreamは使えば使うほど、あれが無い、これが無いと不満が募るものです。Optionalやラムダ式も追加されましたが、同様です。今回、Java 9でtakeWihle()が追加されることになりましたが、未だにzip()がありませんし、そもそもタプルがありません。Map.Entry<K,V>をペア代わりに使うとかのテクニックが堂々と述べられるとか、どこかおかしいです。Javaで関数型プログラミングを本格的にするには、痒いところに手が微妙に届かない、というとても残念な作りになっています。「forを使うな、Streamを使え!」というのであれば、「Javaを使うな、Scalaを使え!」と言った方がマシと私個人は思っています。

投稿2017/02/25 11:27

raccy

総合スコア21735

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

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

masa105

2017/02/25 13:37

ご回答ありがとうございます。 Java9からtakeWhile()というメソッドが追加されるのですね!
masa105

2017/02/25 13:52

>hogeStream()が10000回も動作すると言うことが問題だと言うことだと思います。 えぇ~そうなんですね…。知りませんでした。勉強になります!
raccy

2017/02/25 23:06

なにやら勘違いしていそうですが、LongStream.range(1, 10000)としただけで10000個の整数を作っているわけではありませんよ。forの時と同じで、ループで回るときに一個一個作って行くという動作をします。takeWhileで途中で止めてあとは見ないとしたら、残りは生成されません。ただ、とめなかったら、最後まで生成されます。 それと、ラムダ式内のreturnはラムダ式自体の終了だけなので、全体のメソッドが終了するわけでは無いですよ。
masa105

2017/02/26 04:35

ご指摘ありがとうございます。大きな勘違いをしておりました…。 forEach()の場合に10000回チェック処理が動く理由も理解しました!ありがとうございます。
guest

0

個人的にはStreamの使う実装2の方が非効率だと感じます。(必ず10000の変数が生成されてしまうため)

java のコンパイラもライブラリもそんなにバカではありません(と、私は信じてます)。
for 文だとその中の処理は順次にしか実行できませんが、ストリーミングであれば、パイプライン処理ができる可能性があります。

for (long x = 1; x < 10000; x++) {
処理1
処理2
処理3
}

となっている場合、 x=1 の処理3が終わるまで x=2 の処理1は始まりませんが、

LongStream.range(1, 10000)
.処理1
.処理2
.処理3

となっていれば、 x=1 の処理1と同時に(場合によって) x=2 の処理1が始められます。

たとえば、処理3がデータベースへの問い合わせだったりする場合、前者では、x=1 に対するデータベースサーバから応答が返ってくるまでCPUは待つしかありませんが、後者であれば他のことができるかもしれません。
したがって、どちらかというと、ストリーミングのほうが最適化の余地があるものと思われます。

ただし、parallel() を呼んで、並列モードにしないと、並列実行されません。
以下にパイプラインを並列実行する例を示します。

java

1import java.util.stream.LongStream; 2import java.util.Random; 3class test { 4 private static void safeSleep(int t) { 5 try{Thread.sleep(t);} catch(Exception e){} 6 } 7 public static void main(String argv[]) { 8 Random rnd = new Random(); 9 LongStream.range(1, 10000).parallel() 10 .map(x -> { 11 int t = rnd.nextInt(10); 12 System.out.println("process1 x=" + x + " sleep " + t + "ms"); 13 safeSleep(t); 14 return x; 15 }) 16 .map(x -> { 17 int t = rnd.nextInt(10); 18 System.out.println("process2 x=" + x + " sleep " + t + "ms"); 19 safeSleep(t); 20 return x; 21 }) 22 .forEach(x -> { 23 int t = rnd.nextInt(10); 24 System.out.println("process3 x=" + x + " sleep " + t + "ms"); 25 safeSleep(t); 26 return; 27 }); 28 } 29}

投稿2017/02/25 12:40

編集2017/02/25 23:08
mit0223

総合スコア3401

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

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

masa105

2017/02/25 13:25

ご回答ありがとうございます。 >java のコンパイラもライブラリもそんなにバカではありません(と、私は信じてます)。 LongStream.range(1, 10000)とした時点で10000個の変数を生成していると思うのですが、私の認識が誤っていますか。 >for 文だとその中の処理は順次にしか実行できませんが、ストリーミングであれば、パイプライン処理>ができる可能性があります。 処理1、処理2は中間操作、処理3は終端操作になるので必ずシーケンシャルにしか動作しないのではないでしょうか。
mit0223

2017/02/25 23:03

> LongStream.range(1, 10000)とした時点で10000個の変数を生成していると思うのですが、私の認識が誤っていますか。 「10000個の変数の生成」というのが10000個の変数の領域をメモリ上に確保しているという意味であれば、間違っていると思います。ストリームのマニュアル https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/package-summary.html で、「ステートレスな中間操作のみを含むパイプラインは、順次、並列のいずれであっても、最小限のデータ・バッファリングで単一パスで処理できます。」というくだりがあります。これは、ステートレス操作であれば、データを全部バッファリングしたりしないということを意味しています。このあたりの事情は for 文で書いても同じなので、ストリームのほうが効率が悪いとは言えないと思います。 > 処理1、処理2は中間操作、処理3は終端操作になるので必ずシーケンシャルにしか動作しないのではないでしょうか。 はい、parallel() を呼ばないと並列実行にはなりませんね。サンプル追加しておきます。
masa105

2017/02/26 04:34

大きな勘違いをしていました…。 サンプルありがとうございます。参考にいたします!
guest

0

自分は「forは使うな!」が意味していることはおそらく「streamで充分すっきり書けるようになったことを知らずになんでもかんでもforループを使ってないですか?もしそうならstreamも知っておいた方がいいですよ」という程度のことだと思っています。

自分はこのループがボトルネックになるほどクリティカルな場面では前者を選択します。例えばこのループがnanosecoundレベルの効率を求めるようなケースです。そうとは限らないところなら

java

1LongStream.range(1, 10000) 2 .filter(predicate) 3 .findFirst() 4 .ifPresent(lv -> ...);

なんてコードを書きたくなることもあります。単にreturnとかbreakとかcontinueをかかなくてもすむならそうしようかなという程度なのでその辺りは好みかも知れません。

Streamがfor文にとってかわれるほどの記述能力を持っているかというとそういう訳でもありませんが世の中に大量にあるfor文の中の多くにStreamで書いた方がわかりやすいとプログラマーが感じるものが多いというのも事実だと思います。要するに適宜使い分ければよいと思います。

ちょっと横道かも知れませんがJavaのstreamは少々機能に見劣りする点がありtake/dropがない(java9でようやく入るみたいですが)とかそもそもStream<long>じゃなくてLongStreamなのかい!などの不満があるので「今のところは自然に使えるところに使う」のでいいのかなぁなんて思います。

投稿2017/02/25 10:51

KSwordOfHaste

総合スコア18394

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

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

KSwordOfHaste

2017/02/25 13:22

質問の10000回パイプラインが動いてしまう点についてあさっての方向の回答しかできてませんでした。raccyさんが的確な回答をしてくださったので他にいうこともありません。 take/drop的な機能に限らずこのあたりやはり中途半端感は強いですね・・・
masa105

2017/02/25 13:59

ご回答ありがとうございます。 >自分は「forは使うな!」が意味していることはおそらく「streamで充分すっきり書けるようになった>ことを知らずになんでもかんでもforループを使ってないですか?もしそうならstreamも知っておいた>方がいいですよ」という程度のことだと思っています。 確かにそう捉えた方がしっくりきますね。私はfor=悪!みたいに捉えてしまっていました... 逆にStreamの悪い面もしっかりと理解して、場面場面で適切な実装を選択できるようにしていきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問