web-dev-qa-db-ja.com

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

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

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

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

64
user4159962

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

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

reallyラムダ内からカウンターをインクリメントする必要がある場合、その典型的な方法は、カウンターをAtomicIntegerまたはAtomicLongにしてから、インクリメントの1つを呼び出すことです。その上のメソッド。

単一要素のintまたはlong配列を使用できますが、ストリームが並列で実行される場合は競合状態になります。

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

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

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

61
Stuart Marks

面倒な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
    });
27
Duke Spray
AtomicInteger runCount = 0L;
long runCount = myStream.stream()
    .filter(...)
    .peek(item -> { 
        foo();
        bar();
        runCount.incrementAndGet();
    });
System.out.println("The lambda ran " + runCount.incrementAndGet() + "times");
10
Leandruz

これを行う別の方法(操作が成功した場合など、場合によってのみカウントをインクリメントしたい場合に便利)は、mapToInt()および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=2 を参照してくださいfinalの使用に関する議論。

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

3
A. D.

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

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

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

3
shmosel

私にとっては、これでうまくいきました。うまくいけば誰かがそれを見つけてくれるといいのですが。

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)と同等です。

2
Jose Ripoll

should n't AtomicIntegerを使用します。本当に使用する正当な理由がない限り、使用しないでください。また、AtomicIntegerを使用する理由は、同時アクセスなどを許可することだけかもしれません。

問題が発生したとき;

Holderは、ラムダ内で保持およびインクリメントするために使用できます。そして、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");
1
Ahmet Orhan

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

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