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

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

ただいまの
回答率

90.50%

  • Java

    13803questions

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

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

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 506

masa105

score 22

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

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

//実装1
public void hoge(){
    for(long x = 1; x < 10000; x++){
        if(条件xxxx){
            return;
        }

    return;
}

// 実装2
public void fuga(){
    LongStream.range(1, 10000).forEach(x -> {
        if(条件xxxx){
            return;
        }
    });

    return;
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+4

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

import java.util.stream.LongStream;

public class StreamWhile {
    public static void main(String[] args) throws Exception {
        hogeFor();
        hogeStream();
        hogeStreamTakeWhile();
    }

    public static void hogeFor() {
        for (long x = 1; x < 10000; x++) {
            if (x > 10) {
                return;
            }
            System.out.println(x);
        }
    }

    public static void hogeStream() {
        LongStream.range(1, 10000).forEach(x -> {
            if (x > 10) {
                return;
            }
            System.out.println(x);
        });
    }

    // Java 9 から使える takeWhile を使用
    public static void hogeStreamTakeWhile() {
        LongStream.range(1, 10000).takeWhile(x -> x <= 10).forEach(x -> {
            System.out.println(x);
        });
    }
}

三つのメソッドの出力は同じです。条件チェックが動く回数は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 22:37

    ご回答ありがとうございます。

    Java9からtakeWhile()というメソッドが追加されるのですね!

    キャンセル

  • 2017/02/25 22:52

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

    キャンセル

  • 2017/02/26 08:06

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

    キャンセル

  • 2017/02/26 13:35

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

    キャンセル

+1

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

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

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/25 22:22

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

    キャンセル

  • 2017/02/25 22:59

    ご回答ありがとうございます。

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

    キャンセル

+1

個人的には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() を呼んで、並列モードにしないと、並列実行されません。
以下にパイプラインを並列実行する例を示します。

import java.util.stream.LongStream;
import java.util.Random;
class test {
    private static void safeSleep(int t) {
        try{Thread.sleep(t);} catch(Exception e){}
        }
    public static void main(String argv[]) {
        Random rnd = new Random();
        LongStream.range(1, 10000).parallel()
        .map(x -> {
            int t = rnd.nextInt(10);
            System.out.println("process1 x=" + x + " sleep " + t + "ms");
            safeSleep(t);
            return x;
        })
        .map(x -> {
            int t = rnd.nextInt(10);
            System.out.println("process2 x=" + x + " sleep " + t + "ms");
            safeSleep(t);
            return x;
        })
        .forEach(x -> {
            int t = rnd.nextInt(10);
            System.out.println("process3 x=" + x + " sleep " + t + "ms");
            safeSleep(t);
            return;
        });
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/02/25 22:25

    ご回答ありがとうございます。

    >java のコンパイラもライブラリもそんなにバカではありません(と、私は信じてます)。
    LongStream.range(1, 10000)とした時点で10000個の変数を生成していると思うのですが、私の認識が誤っていますか。

    >for 文だとその中の処理は順次にしか実行できませんが、ストリーミングであれば、パイプライン処理>ができる可能性があります。
    処理1、処理2は中間操作、処理3は終端操作になるので必ずシーケンシャルにしか動作しないのではないでしょうか。

    キャンセル

  • 2017/02/26 08: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() を呼ばないと並列実行にはなりませんね。サンプル追加しておきます。

    キャンセル

  • 2017/02/26 13:34

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

    キャンセル

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

  • ただいまの回答率 90.50%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Java

    13803questions

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