Java 8 Iterable.forEach()とforeachループ


466

次のうちどれがJava 8でより良い実践ですか?

Java 8:

joins.forEach(join -> mIrc.join(mSession, join));

Java 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

私はラムダで「単純化」できるforループをたくさん持っていますが、それらを使用することには本当に何か利点がありますか?パフォーマンスと読みやすさが向上しますか?

編集

また、この質問をより長い方法に拡張します。あなたはラムダから親関数を返すこともブレークすることもできないことを知っています、そしてそれらを比較するときにこれも考慮に入れられるべきですが、他に考慮すべきことはありますか?


10
実際のパフォーマンス上の利点はありません。最初のオプションはFPから着想を得たものです(一般に、「コードを表現するためのより「明確」で「明確」な方法のように話されるウィッチ)。実際には-これはむしろ「スタイル」の質問です。
ユージーンロイ2013年

5
@Dwb:この場合、それは関係ありません。forEachは、並列またはそのようなものとして定義されていないため、これら2つは意味的に同等です。もちろん、forEachの並列バージョンを実装することもできます(標準ライブラリにすでに存在している場合もあります)。そのような場合、ラムダ式構文は非常に便利です。
AardvarkSoup 2013年

8
@AardvarkSoup forEachが呼び出されるインスタンスは、ストリーム(lambdadoc.net/api/java/util/stream/Stream.html)です。並列実行を要求するには、joins.parallel()。forEach(...)を作成します
mschenk74 '19年

13
joins.forEach((join) -> mIrc.join(mSession, join));の「簡素化」本当にfor (String join : joins) { mIrc.join(mSession, join); }?の種類を非表示にするために、句読点の数を9から12に増やしましたjoin。実際に行ったことは、2つのステートメントを1行に入力することです。
トム・ホーティン-タックライン2013

7
考慮すべきもう1つのポイントは、Javaの限定された変数キャプチャ機能です。Stream.forEach()を使用すると、ローカル変数をキャプチャして最終的にするため、ローカル変数を更新できません。つまり、forEachラムダでステートフルな動作を実行できます(クラス状態変数の使用などの醜い準備ができていない場合)。
RedGlyph 2014

回答:


511

を使用することをお勧めしますfor-eachKeep It Simple、Stupidの原則に違反することに加えforEach()て、new-fangled には少なくとも以下の欠点があります。

  • final以外の変数は使用できません。したがって、次のようなコードはforEachラムダに変換できません。

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
  • チェックされた例外を処理できません。ラムダは、チェックされた例外をスローすることを実際に禁止されてConsumerいませんが、宣言しないなどの一般的な機能的インターフェイス。したがって、チェック済み例外をスローするコードは、それらをtry-catchまたはでラップする必要がありますThrowables.propagate()。しかし、それを行ったとしても、スローされた例外がどうなるかは必ずしも明確ではありません。それは内臓のどこかに飲み込まれる可能性がありますforEach()

  • 制限されたフロー制御returnラムダのA はcontinuefor-eachのaと同じですが、に相当するものはありませんbreak。戻り値、短絡、またはフラグの設定などを行うことも困難です(最終変数なしのルールに違反していなかった場合は、少し軽減されます)。「これは単なる最適化ではなく、一部のシーケンス(ファイル内の行の読み取りなど)に副作用があるか、シーケンスが無限である可能性があると考える場合に重要です。」

  • 並列実行される可能性があります。これは、最適化する必要があるコードの0.1%を除くすべてにとって恐ろしい、恐ろしいことです。(ロック、揮発性、および従来のマルチスレッド実行の他の特に厄介な側面を使用していない場合でも)並列コードは熟慮する必要があります。バグを見つけるのは難しいでしょう。

  • パフォーマンスを傷つけるかもしれない JITは、特に今ラムダが新規であることを、無地のループと同程度に+()ラムダをforEachのを最適化することはできませんので、。「最適化」とは、ラムダ(小さい)を呼び出すオーバーヘッドではなく、最新のJITコンパイラがコードの実行時に実行する高度な分析と変換を意味します。

  • 並列処理が必要な場合は、ExecutorServiceを使用する方がはるかに高速で、それほど難しくありません。ストリームは両方とも自動(読み取り:問題についてあまり知らない)であり、特殊化された(読み取り:一般的なケースでは非効率的)並列化戦略(fork-join再帰分解)を使用します。

  • ネストされた呼び出し階層と、禁じられている並列実行のために、デバッグをより混乱させます。デバッガーは周囲のコードからの変数の表示に問題がある可能性があり、ステップスルーなどは期待どおりに機能しない可能性があります。

  • ストリームは一般に、コーディング、読み取り、デバッグがより困難です。実際、これは複雑な「流暢な」API全般に当てはまります。複雑な単一ステートメントの組み合わせ、ジェネリックの多用、および中間変数の欠如は、混乱を招くエラーメッセージを生成し、デバッグを妨げます。「このメソッドにはタイプXのオーバーロードがありません」の代わりに、「タイプをめちゃくちゃにしたが、どこにどのようにしているかわからない」というエラーメッセージが表示されます。同様に、コードを複数のステートメントに分割し、中間値を変数に保存する場合と同じくらい簡単に、デバッガーをステップ実行して調べることはできません。最後に、コードを読んで、実行の各段階でのタイプと動作を理解することは簡単ではないかもしれません。

  • 親指の痛みのように飛び出します。Java言語にはすでにfor-eachステートメントがあります。なぜそれを関数呼び出しに置き換えるのですか?式のどこかに副作用を隠すことを勧めるのはなぜですか?扱いにくいワンライナーを奨励するのはなぜですか?通常のfor-eachと新しいforEach willy-nillyを混在させるのは悪いスタイルです。コードはイディオム(繰り返しのためにすぐに理解できるパターン)で話す必要があり、使用するイディオムが少ないほどコードが明確になり、使用するイディオムを決定するのに費やす時間が少なくなります(私のような完璧主義者にとっては大きな時間の浪費!) )。

ご覧のとおり、理にかなった場合を除いて、私はforEach()の大ファンではありません。

特に不快なのは、Stream実装されておらずIterable(実際にはmethodがあるにもかかわらずiterator)、forEach()でのみfor-eachで使用できないことです。でストリームをイテラブルにキャストすることをお勧めし(Iterable<T>)stream::iteratorます。より良い代替手段は、の実装を含む、Stream APIの多くの問題を修正するStreamExを使用することですIterable

forEach()はいえ、次の場合に役立ちます。

  • 同期リストを原子的に反復します。これより前は、で生成されたリストは、Collections.synchronizedList()getやsetなどに関してアトミックでしたが、反復時にスレッドセーフではありませんでした。

  • 並列実行(適切な並列ストリームを使用)。これにより、問題がStreamsとSpliteratorsに組み込まれているパフォーマンスの仮定と一致する場合は、ExecutorServiceを使用するよりも数行のコードを節約できます。

  • 同期されたリストのように、反復を制御することから利益を得る特定のコンテナー(これは、人々がより多くの例を提示できない限り、これは主に理論的ですが)

  • forEach()メソッド参照引数(つまり、list.forEach (obj::someMethod))を使用して、単一の関数をより明確に呼び出す。ただし、チェック例外、より困難なデバッグ、およびコードの記述時に使用するイディオムの数の削減に関するポイントに留意してください。

参考にした記事:

編集:ラムダの元の提案のいくつか(http://www.javac.info/closures-v06a.htmlなど)は、私が言及した問題のいくつかを解決したようです(もちろん、独自の複雑さを追加しています)。


95
「式のどこかに副作用を隠すことを勧めるのはなぜですか?」間違った質問です。関数型forEachは、関数型のスタイル、つまり副作用のない式の使用を促進するためにあります。あなたがforEach副作用でうまくいかない状況に遭遇した場合、あなたはあなたがその仕事に適切なツールを使用していないという感覚を得るべきです。それから簡単な答えは、それはあなたの気持ちが正しいからです。そのため、for-eachループに留まってください。クラシックforループは非推奨になりませんでした…
Holger

17
@Holger forEach副作用なしでどのように使用できますか?
Aleksandr Dubinsky

14
わかりました、私は十分に正確ではありませんでした。forEach副作用を意図した唯一のストリーム操作ですが、サンプルコードのような副作用のためではありませんreduce。カウントは典型的な操作です。原則として、ローカル変数を操作するすべての操作を保持するか、従来のforループで制御フロー(例外処理を含む)に影響を与えることをお勧めします。私が思う最初の質問に関して、問題は誰かがストリームforのソースに対する単純なループで十分であるストリームを使用するという事実から生じます。forEach()のみ機能するストリームを使用する
Holger

8
@Holger forEach適切な副作用の例は何ですか?
Aleksandr Dubinsky

29
各アイテムを個別に処理し、ローカル変数を変更しようとしないもの。たとえば、アイテム自体の操作または印刷、ファイルへの書き込み/送信、ネットワークストリームなど。フィルタリング、マッピング、削減、検索、および(ある程度は)収集は、ストリームの優先操作です。forEachは、既存のAPIとリンクするのに便利です。もちろん、並列操作の場合も同様です。これらはforループでは機能しません。
Holger

169

操作を並行して実行できる場合は、利点が考慮されます。(http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and-内部および外部反復に関するセクションを参照してください)

  • 私の観点からの主な利点は、ループ内で実行される処理の実装を、それが並列で実行されるか順次で実行されるかを決定する必要なく定義できることです。

  • ループを並行して実行したい場合は、次のように書くことができます

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    スレッド処理などのために追加のコードを記述する必要があります。

注:私の回答では、java.util.Streamインターフェースを実装する結合を想定しました。joinがjava.util.Iterableインターフェースのみを実装している場合、これは当てはまりません。


4
彼が参照しているオラクルエンジニアのスライド(blogs.oracle.com/darcy/resource/Devoxx/…)では、これらのラムダ式内の並列処理については触れていません。並列処理は、ラムダに実際には関係しないmap&などの一括コレクションメソッド内で発生する可能性がありますfold
Thomas Jungblut 2013年

1
ここでは、OPのコードが自動並列化のメリットを享受するようには思えません(特に、並列化が保証されていないためです)。私たちは「mIrc」が何であるかを本当に知りませんが、「join」は実際には順序が狂って実行できるもののようには見えません。
ユージーンロイ2013年

11
Stream#forEachIterable#forEach同じではありません。OPはについて尋ねていIterable#forEachます。
gvlasov 2014年

2
質問が出されてから回答が更新されるまでの間に仕様に変更があったため、UPDATEXスタイルを使用しました。答えの歴史がなければ、さらに混乱するだろうと私は思った。
mschenk74

1
の代わりにjoinsが実装されている場合、この回答が無効である理由を誰かに説明してもらえますか?私が読んだいくつかのことから、OPは実行可能であり、実装されている場合IterableStreamjoins.stream().forEach((join) -> mIrc.join(mSession, join));joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));joinsIterable
Blueriver

112

この質問を読むIterable#forEachと、ラムダ式と組み合わせて、従来のfor-eachループを作成するためのショートカット/置換であるという印象を得ることができます。これは単に真実ではありません。OPからのこのコード:

joins.forEach(join -> mIrc.join(mSession, join));

されていない書き込みのショートカットとして意図

for (String join : joins) {
    mIrc.join(mSession, join);
}

そして、確かにこのように使用すべきではありません。代わりに、それは書くためのショートカットとして意図されています(正確には同じではありません

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

そして、それは次のJava 7コードの代替としてです。

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

上記の例のように、ループの本体を関数型インターフェースに置き換えると、コードがより明確になります。つまり、(1)ループの本体は周囲のコードと制御フローに影響を与えない、(2)ループの本体は、周囲のコードに影響を与えることなく、関数の別の実装に置き換えることができます。外部スコープの非final変数にアクセスできないことは、関数/ラムダの不足ではなく、従来のfor-eachループのセマンティクスからセマンティクスを区別する機能ですIterable#forEach。の構文Iterable#forEachに慣れると、コードに関するこの追加情報をすぐに取得できるため、コードが読みやすくなります。

従来のfor-eachループは、Javaでは確かに(「使いすぎ」という用語の「ベストプラクティス」を回避するために)良い習慣を維持します。しかし、これは悪い習慣や悪いスタイルと見なされるべきではないという意味ではありません。仕事をするのに適切なツールを使用することは常に良い習慣であり、これには、従来のfor-eachループをと混合することも含まれます。Iterable#forEachIterable#forEach

のマイナス面Iterable#forEachについてはこのスレッドで既に説明しているので、いくつかの理由を説明します。なぜ使用したいのでしょうかIterable#forEach

  • コードをより明示的にするには:上記のように、状況によってはコードをより明示的で読みやすくするIterable#forEach ことができます。

  • コードの拡張性と保守性を高めるには:関数をループの本体として使用すると、この関数を別の実装に置き換えることができます(戦略パターンを参照)。たとえば、ラムダ式をメソッド呼び出しに簡単に置き換えることができます。これは、サブクラスによって上書きされる場合があります。

    joins.forEach(getJoinStrategy());

    次に、機能インターフェースを実装する列挙型を使用してデフォルトの戦略を提供できます。これにより、コードの拡張性が高まるだけでなく、ループの実装がループ宣言から切り離されるため、保守性も向上します。

  • コードをよりデバッグしやすくするにはループの実装を宣言から分離すると、デバッグをより簡単にすることもできますif(DEBUG)System.out.println()。これは、でメインコードを煩雑にせずに、デバッグメッセージを出力する専用のデバッグ実装を用意できるためです。デバッグ実装は、たとえば実際の関数実装を装飾するデリゲートである可能性があります。

  • パフォーマンスが重要なコードを最適化するには:このスレッドの一部のアサーションとは対照的に、少なくともArrayListを使用して「-client」モードでHotspotを実行している場合は、従来のfor-eachループよりも優れたパフォーマンスをすでに提供Iterable#forEach ています。このパフォーマンスの向上はわずかであり、ほとんどのユースケースでは無視できますが、この追加のパフォーマンスによって違いが生じる場合があります。たとえば、ライブラリのメンテナは、既存のループ実装の一部をで置き換える必要がある場合、評価を望みIterable#forEachます。

    この説明を事実で裏付けるために、私はCaliperでいくつかのマイクロベンチマークを行いました。これがテストコードです(gitの最新のCaliperが必要です):

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    そしてここに結果があります:

    「-client」を指定して実行するとIterable#forEach、ArrayListに対する従来のforループより優れていますが、配列を直接反復するよりも低速です。「-server」で実行すると、すべてのアプローチのパフォーマンスはほぼ同じになります。

  • 並列実行のオプションのサポートを提供するにはストリームIterable#forEachを使用して並列で機能インターフェースを実行する可能性が重要な側面であることはすでにここで述べられています。は、ループが実際に並列で実行されることを保証しないため、これをオプション機能と見なす必要があります。リストをで反復することにより、次のように明示的に言う:このループ並列実行をサポートしますが、それに依存しません。繰り返しますが、これは機能であり、赤字ではありません!Collection#parallelStream()list.parallelStream().forEach(...);

    並列実行の決定を実際のループ実装から遠ざけることにより、コード自体に影響を与えることなく、コードのオプションの最適化を許可できます。これは良いことです。また、デフォルトの並列ストリーム実装がニーズに合わない場合でも、独自の実装を提供することを妨げるものはありません。たとえば、基になるオペレーティングシステム、コレクションのサイズ、コアの数、およびいくつかの設定に応じて、最適化されたコレクションを提供できます。

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    ここでの良い点は、ループの実装がこれらの詳細を知っていたり、気にする必要がないことです。


5
このディスカッションには興味深い見方があり、いくつかのポイントを挙げています。それらに対処しようとします。ループ本体の性質に関するいくつかの基準を切り替えて、forEachそれにfor-each基づいて切り替えることを提案します。そのようなルールに従うための知恵と規律は、優れたプログラマーの特徴です。そのような規則は彼の悩みの種でもあります、なぜなら彼の周りの人々はそれらに従うか、または同意しないので。たとえば、チェックされた例外とチェックされていない例外を使用します。この状況はさらに微妙です。しかし、本体が「サラウンドコードやフロー制御に影響を与えない」場合は、それを関数としてより適切に除外していませんか?
Aleksandr Dubinsky 14

4
アレクサンドルの詳細なコメントをありがとう。But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?。はい、これは私の意見の多くの場合に当てはまります-関数としてこれらのループを除外することは自然な結果です。
Balder 2014

2
パフォーマンスの問題について-ループの性質に大きく依存すると思います。私が取り組んでいるプロジェクトIterable#forEachでは、パフォーマンスの向上のために、Java 8以前と同様の関数スタイルのループを使用しています。問題のプロジェクトには、ゲームループに似た1つのメインループがあり、クライアントは関数としてループ参加者をプラグインできる未定義のネストされたサブループがあります。このようなソフトウェア構造は、から大きな恩恵を受けIteable#forEachます。
Balder 2014

7
「コードはイディオムで話す必要があり、使用されるイディオムが少ないほどコードが明確になり、どのイディオムを使用するかを決定するために費やす時間が少なくなります」という私の批評の最後に文があります。C#からJavaに切り替えたとき、私はこの点に深く感謝し始めました。
Aleksandr Dubinsky 14

7
それは悪い議論です。あなたはそれを使ってあなたが望むものを正当化することができます:なぜあなたはforループを使うべきではないのですか?whileループは十分であり、それはイディオムが1つ少ないからです。gotoがそれ以上のことをすべて実行できるのに、ループ、スイッチ、またはtry / catchステートメントを使用する理由。
Tapichu 2014年

13

forEach()反復可能オブジェクトは、標準の反復子の方法とは対照的に、その要素を反復する最良の方法を知っているため、for-eachループよりも高速に実装できます。したがって、違いは内部ループまたは外部ループです。

たとえば、次のArrayList.forEach(action)ように簡単に実装できます。

for(int i=0; i<size; i++)
    action.accept(elements[i])

多くの足場を必要とするfor-eachループとは対照的に

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

ただし、を使用して2つのオーバーヘッドコストを考慮する必要もありますforEach()。1つはラムダオブジェクトを作成し、もう1つはラムダメソッドを呼び出します。それらはおそらく重要ではありません。

さまざまなユースケースの内部/外部反復の比較については、http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/も参照してください。


8
イテラブルが最良の方法を知っているのに、イテレーターが知らないのはなぜですか?
mschenk74 2013年

2
本質的な違いはありませんが、イテレータインターフェースに準拠するには追加のコードが必要です。
ZhongYu 2013年

1
@ zhong.j.yu Collectionを実装する場合は、とにかくIterableも実装します。そのため、「不足しているインターフェイスメソッドを実装するためのコードをさらに追加する」という点では、コードのオーバーヘッドはありません。mschenk74が言ったように、可能な限り最良の方法でコレクションを反復処理する方法を知るためにイテレーターを微調整できない理由は何もないようです。イテレータの作成にはオーバーヘッドがかかる可能性があることには同意しますが、真剣に、それらは通常非常に安価であり、コストがゼロであると言えます...
Eugene Loy

4
たとえば、ツリーの反復:void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}、これは外部反復よりもエレガントであり、最適な同期方法を決定できます
ラチェットフリーク

1
おかしなことに、String.joinメソッドでの唯一のコメント(そう、間違った結合)は、「Arrays.streamのオーバーヘッドに見合わない要素の数」です。だから彼らは優雅なforループを使う。
トムホーティン-タックライン2013

7

TL; DRList.stream().forEach()最速でした。

ベンチマークの反復からの結果を追加する必要があると感じました。私は非常にシンプルなアプローチ(ベンチマークフレームワークなし)を採用し、5つの異なる方法をベンチマークしました。

  1. クラシック for
  2. 古典的なforeach
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

テスト手順とパラメータ

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

このクラスのリストは繰り返し処理され、その一部doIt(Integer i)がすべてのメンバーに適用されます(毎回異なるメソッドを介して)。Mainクラスで、テスト済みのメソッドを3回実行して、JVMをウォームアップします。次に、反復メソッドごとにかかる時間を合計して、テストメソッドを1000回実行します(を使用System.nanoTime())。それが終わったら、その合計を1000で割ります。これが結果、平均時間です。例:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

これをi5 4コアCPUで実行し、Javaバージョン1.8.0_05を使用しました

クラシック for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

実行時間:4.21ミリ秒

古典的なforeach

for(Integer i : list) {
    doIt(i);
}

実行時間:5.95 ms

List.forEach()

list.forEach((i) -> doIt(i));

実行時間:3.11 ms

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

実行時間:2.79 ms

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

実行時間:3.6ミリ秒


23
どのようにしてそれらの数値を取得しますか?ベンチマークのどのフレームワークを使用していますか?何も使用せず、単純System.out.printlnにこのデータを単純に表示する場合、すべての結果は役に立ちません。
Luiggi Mendoza、2015

2
フレームワークはありません。使用しますSystem.nanoTime()。答えを読むと、それがどのように行われたかがわかります。これは相対的な質問なので、それを見て役に立たないとは思いません。他の方法と比較して、特定の方法がどれだけうまく機能したかは気にしません。
Assaf、2015年

31
そして、それが優れたマイクロベンチマークの目的です。このような要件を満たしていないため、結果は役に立ちません。
Luiggi Mendoza

6
代わりにJMHについて知ることをお勧めします。これはJava自体に使用されているものであり、正しい数値を取得するために多大な労力を費やし
projects

1
@LuiggiMendozaに同意します。これらの結果が一貫しているか有効であることを知る方法はありません。神は、特に繰り返しの順序、サイズなどに応じて、さまざまな結果を報告し続けるベンチマークの数を知っています。
mmm 2016年

6

コメントを少し拡張する必要があると思います...

パラダイム\スタイルについて

それがおそらく最も注目すべき側面です。FPは、副作用を回避できるために人気がありました。これは質問とは関係がないので、これからどのような長所と短所を得ることができるかについては詳しく説明しません。

ただし、Iterable.forEachを使用した反復はFPに触発されたものであり、むしろJavaにFPを追加した結果だと言えます(皮肉なことに、純粋なFPでforEachを使用することはほとんどありません。副作用)。

結局のところ、それはあなたが現在書いている味\スタイル\パラダイムの問題だと言えるでしょう。

並列処理について。

パフォーマンスの観点から見ると、foreach(...)よりもIterable.forEachを使用することによる顕著なメリットは約束されていません。

Iterable.forEachの公式ドキュメントによると:

すべての要素が処理されるか、アクションが例外をスローするまで、反復時に要素が発生する順序で、Iterableのコンテンツに対して所定のアクションを実行します。

...つまり、暗黙的な並列処理がないことをドキュメントはかなり明確にしています。1つ追加すると、LSP違反になります。

現在、Java 8で約束されている「並列コレクション」がありますが、それらを使用するには、より明示的にそれらを使用するために特別な注意を払う必要があります(たとえば、mschenk74の回答を参照)。

ところで、この場合はStream.forEachが使用され、実際の作業が並行して行われることが保証されていません(基になるコレクションによって異なります)。

更新:それほど明白ではなく、一目で少し引き延ばされているかもしれませんが、スタイルと読みやすさの観点には別の側面があります。

まず第一に-プレーンな古いforloopはプレーンで古いものです。誰もがすでにそれらを知っています。

次に、より重要なことですが、おそらくIterable.forEachを1行のラムダでのみ使用する必要があります。「体」が重くなると、読みにくくなる傾向があります。ここから2つのオプションがあります-内部クラス(yuck)を使用するか、プレーンな古いforloopを使用します。同じコードベースでさまざまな手段やスタイルが行われている同じもの(コレクションの反復)を見ると、人々はしばしば苛立ちますが、これは事実のようです。

繰り返しますが、これは問題である場合とそうでない場合があります。コードに取り組んでいる人々に依存します。


1
並列処理では、新しい「並列コレクション」は必要ありません。それは、(collection.stream()を使用して)分離ストリームを要求したか、(collection.parallelStream()を使用して)並列ストリームを要求したかによって異なります。
JBニゼット2013年

ドキュメントによると、@ JBNizet Collection.parallelStream()は、コレクションの実装が並列ストリームを返すことを保証しません。私は実際、いつこれが起こるのか疑問に思っていますが、おそらくこれはコレクションに依存しています。
ユージーンロイ2013年

同意した。コレクションにも依存します。しかし、私の要点は、並列のforeachループがすべての標準コレクション(ArrayListなど)で既に利用可能であったということです。「並行コレクション」を待つ必要はありません。
JBニゼット2013年

@JBNizetはあなたの意見に同意しますが、そもそも私が「並行コレクション」によって私が意図したものではありません。私は、Java 8で追加されたCollection.parallelStream()を、ほとんど同じように機能するScalaの概念との類似性から「並列コレクション」として参照しています。また、JSRのビットでそれがどのように呼ばれているかはわかりませんが、このJava 8機能に同じ用語を使用するいくつかの論文を見ました。
ユージーンロイ2013年

1
:最後の段落のためにあなたは、関数リファレンスを使用することができますcollection.forEach(MyClass::loopBody);
ラチェットフリーク

6

最も喜ばしい機能forEachの制限の1つは、チェック済み例外のサポートの欠如です。

考えられる回避策の 1つは、ターミナルforEachを単純な古いforeachループに置き換えることです。

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

以下は、ラムダとストリーム内のチェックされた例外処理に関する他の回避策とともに最も人気のある質問のリストです。

例外をスローするJava 8 Lambda関数?

Java 8:Lambda-Streams、例外付きのメソッドでフィルター

Java 8ストリーム内からCHECKED例外をスローするにはどうすればよいですか?

Java 8:ラムダ式での必須チェック例外処理。なぜ必須ではなく、必須ではないのですか?


3

1.7拡張forループに対するJava 1.8 forEachメソッドの利点は、コードの記述中にビジネスロジックのみに集中できることです。

forEachメソッドはjava.util.function.Consumerオブジェクトを引数として取るので、いつでも再利用できる別の場所にビジネスロジックを置くのに役立ちます。

以下のスニペットを見てください、

  • ここでは、コンシューマークラスからのAcceptクラスメソッドをオーバーライドする新しいクラスを作成しました。ここで、反復よりも多くの機能を追加できます。

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.