Java 8:ラムダの反復をカウントするための好ましい方法は?


82

私はよく同じ問題に直面します。ラムダの外部で使用するために、ラムダの実行をカウントする必要があります。例えば:

myStream.stream().filter(...).forEach(item->{ ... ; runCount++);
System.out.println("The lambda ran "+runCount+"times");

問題は、runCountがfinalである必要があるため、intにすることはできないということです。不変であるため、整数にすることはできません。クラスレベルの変数(つまりフィールド)にすることもできますが、このコードブロックでのみ必要になります。私はさまざまな方法があることを知っています、私はこれのためにあなたの好ましい解決策は何ですか?AtomicIntegerまたは配列参照または他の方法を使用していますか?


7
@ Sliver2009いいえ、そうではありません。
Rohit Jain 2015年

4
@FlorianAtomicIntegerここで使用する必要があります。
Rohit Jain 2015年

回答:


70

議論のために、例を少し再フォーマットしましょう。

long runCount = 0L;
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount++; // doesn't work
    });
System.out.println("The lambda ran " + runCount + " times");

あなたがいる場合、実際にラムダ内からカウンタをインクリメントする必要があり、そうするための典型的な方法は、カウンタを作るANにあるAtomicIntegerか、AtomicLongそして、それにインクリメント方法のいずれかを呼び出します。

単一の要素intまたはlong配列を使用することもできますが、ストリームが並行して実行される場合、競合状態が発生します。

ただし、ストリームがで終わることに注意してくださいforEach。これは、戻り値がないことを意味します。をに変更するforEachpeek、アイテムが通過し、カウントされます。

long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
    })
    .count();
System.out.println("The lambda ran " + runCount + " times");

これはやや良いですが、それでも少し奇妙です。その理由は、ということですforEachし、peek唯一の副作用を経由して自分の仕事を行うことができます。Java 8の新しい機能スタイルは、副作用を回避することです。カウンターの増分をcountストリームの操作に抽出することで、その少しを実行しました。その他の典型的な副作用は、コレクションへのアイテムの追加です。通常、これらはコレクターを使用して置き換えることができます。しかし、あなたが実際にどのような仕事をしようとしているのかを知らなければ、これ以上具体的なことを提案することはできません。


8
実装がストリームのショートカットの使用を開始peekすると、動作を停止することに注意してください。これはedストリームの問題にはならないかもしれませんが、誰かが後でコードを変更した場合、大きな驚きを生み出す可能性があります…countSIZEDfilter
Holger 2015年

14
宣言final AtomicInteger i = new AtomicInteger(1);し、ラムダのどこかで使用しますi.getAndAdd(1)。立ち止まって、int i=1; ... i++かつての素晴らしさを思い出してください。
aliopi 2015年

2
JavaがIncrementable、を含む数値クラスなどのインターフェイスを実装し、などのAtomicInteger演算子を++派手な関数として宣言する場合、演算子のオーバーロードは必要なく、非常に読みやすいコードがあります。
SeverityOne

37

面倒なAtomicIntegerを同期する代わりに、代わりに整数配列を使用できます。配列へ参照に別の配列が割り当てられない限り(そしてそれがポイントです)、フィールドのは任意に変更できますが、最終的な変数として使用できます。

    int[] iarr = {0}; // final not neccessary here if no other array is assigned
    stringList.forEach(item -> {
            iarr[0]++;
            // iarr = {1}; Error if iarr gets other array assigned
    });

参照に別の配列が割り当てられないようにする場合は、iarrを最終変数として宣言できます。しかし、@ pisarukが指摘しているように、これは並行して機能しません。
themathmagician 2017

1
単純なforeach直接コレクション(ストリームなし)の場合、これで十分なアプローチだと思います。ありがとう!
サビールカーン

これは、物事を並行して実行していない限り、最も簡単なソリューションです。
David DeMar 2018

17
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
        runCount.incrementAndGet();
    });
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");

15
詳細を編集してください。コードのみの「これを試す」回答は、検索可能なコンテンツが含まれておらず、誰かが「これを試す」必要がある理由を説明していないため、お勧めできません。私たちはここで知識のリソースとなるように努力しています。
mogsdad 2016年

7
あなたの答えは私を混乱させます。両方ともという名前の2つの変数がありますrunCount。そのうちの1つだけにするつもりだったと思いますが、どれですか?
Ole VV

1
runCount.getAndIncrement()の方が適していることがわかりました。素晴らしい答えです!
kospol 2017年

2
AtomicInteger私を助けたが、私はでそれを初期化だろうnew AtomicInteger(0)
ステファンHöltker

1)このコードはコンパイルされません:ストリームにはlongを返すターミナル操作がありません2)たとえコンパイルされたとしても、 'runCount'値は常に '1'にな​​ります:-ストリームにはターミナル操作がないため、peek()lambda引数呼び出されることはありません-のSystem.outラインがそれを表示する前に、実行カウントをインクリメント
セドリック・

8

あなたはいけません、あなたが使用するために本当に良い理由がない限り、あなたは物事を使用してはならない、のAtomicIntegerを使用します。また、AtomicIntegerを使用する理由は、同時アクセスなどのみを許可するためである可能性があります。

あなたの問題になると;

ホルダーは、ラムダ内で保持およびインクリメントするために使用できます。そして、runCount.valueを呼び出すことで取得できます

Holder<Integer> runCount = new Holder<>(0);

myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        runCount.value++; // now it's work fine!
    });
System.out.println("The lambda ran " + runCount + " times");

JDKにはいくつかのHolderクラスがあります。これはそうjavax.xml.ws.Holderです。
ブラッドキューピット

1
本当に?なぜ?
lkahtz

1
同意します-lamda / streamで並行操作を行っていないことがわかっている場合、並行性に対応するように設計されたAtomicIntegerを使用する理由-ロックなどが発生する可能性があります。つまり、何年も前に、 JDKは、ロックを実行しなかった新しいコレクションとそのイテレータのセットを導入しました。多くのシナリオでロックが不要な場合のロックなど、パフォーマンスを低下させる機能で何かに負担をかけるのはなぜですか。
フォルクスマン

4

私にとって、これはトリックをしました、うまくいけば誰かがそれが役に立つと思うでしょう:

AtomicInteger runCount= new AtomicInteger(0);
myStream.stream().filter(...).forEach(item->{ ... ; runCount.getAndIncrement(););
System.out.println("The lambda ran "+runCount.get()+"times");

getAndIncrement()Javaドキュメントの状態:

VarHandle.getAndAddで指定されたメモリ効果を使用して、現在の値をアトミックにインクリメントします。getAndAdd(1)と同等です。


3

これを行う別の方法(操作が成功した場合など、場合によってはカウントをインクリメントするだけにしたい場合に便利です)は、mapToInt()andを使用してsum()次のようになります。

int count = myStream.stream()
    .filter(...)
    .mapToInt(item -> { 
        foo();
        if (bar()){
           return 1;
        } else {
           return 0;
    })
    .sum();
System.out.println("The lambda ran " + count + "times");

Stuart Marksが指摘したように、これは副作用を完全に回避しているわけではないため、まだやや奇妙です(何foo()をしbar()ているかによって異なります)。

また、ラムダの外部からアクセスできるラムダ内の変数をインクリメントする別の方法は、クラス変数を使用することです。

public class MyClass {
    private int myCount;

    // Constructor, other methods here

    void myMethod(){
        // does something to get myStream
        myCount = 0;
        myStream.stream()
            .filter(...)
            .forEach(item->{
               foo(); 
               myCount++;
        });
    }
}

この例では、1つのメソッドでカウンターにクラス変数を使用することはおそらく意味がないので、正当な理由がない限り、注意します。final可能であればクラス変数を保持することは、スレッドセーフなどの観点から役立ちます(使用に関する説明については、http://www.javapractices.com/topic/TopicAction.do?Id = 23を参照してくださいfinal)。

ラムダがそのように機能する理由をよりよく理解するために、https: //www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hoodに詳細があります。


3

ローカルでのみ必要なためにフィールドを作成したくない場合は、匿名クラスに格納できます。

int runCount = new Object() {
    int runCount = 0;
    {
        myStream.stream()
                .filter(...)
                .peek(x -> runCount++)
                .forEach(...);
    }
}.runCount;

奇妙な、私は知っています。ただし、一時変数はローカルスコープからも除外されます。


2
ここで何が起こっているのか、もう少し説明が必要です
AlexanderMills19年

基本的には、匿名クラスのフィールドをインクリメントおよび取得します。あなたが特に混乱していることを教えていただければ、明確にすることができます。
shmosel

1
@MrCholoこれは初期化ブロックです。コンストラクターの前で実行されます。
shmosel

1
@MrCholoいいえ、インスタンス初期化子です。
shmosel

1
@MrCholo匿名クラスは、明示的に宣言されたコンストラクターを持つことはできません。
shmosel

1

削減も機能します、このように使用できます

myStream.stream().filter(...).reduce((item, sum) -> sum += item);

1

もう1つの方法は、Apache CommonsMutableIntを使用することです。

MutableInt cnt = new MutableInt(0);
myStream.stream()
    .filter(...)
    .forEach(item -> { 
        foo();
        bar();
        cnt.increment();
    });
System.out.println("The lambda ran " + cnt.getValue() + " times");

0
AtomicInteger runCount = new AtomicInteger(0);

elements.stream()
  //...
  .peek(runCount.incrementAndGet())
  .collect(Collectors.toList());

// runCount.get() should have the num of times lambda code was executed
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.