[java] Java 8 : 람다 반복 횟수를 계산하는 선호하는 방법?

나는 종종 같은 문제에 직면합니다. 람다 외부에서 사용하기 위해 람다의 실행을 계산해야합니다. 예 :

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

문제는 runCount가 final이어야하므로 int가 될 수 없다는 것입니다. 그것은 불변이기 때문에 정수가 될 수 없습니다. 클래스 수준 변수 (예 : 필드)로 만들 수 있지만이 코드 블록에서만 필요합니다. 여러 가지 방법이 있다는 것을 알고 있습니다. 이것에 대해 선호하는 솔루션이 무엇인지 궁금합니다. AtomicInteger 또는 배열 참조 또는 다른 방법을 사용합니까?



답변

토론을 위해 예제를 다시 형식화하겠습니다.

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

실제로 람다 내에서 카운터를 증분해야하는 경우 일반적인 방법은 카운터를 AtomicInteger또는 AtomicLong로 만든 다음 증분 메서드 중 하나를 호출하는 것입니다.

단일 요소 int또는 long배열을 사용할 수 있지만 스트림이 병렬로 실행되는 경우 경쟁 조건이 있습니다.

그러나 스트림이으로 끝나는 forEach것은 반환 값이 없음을 의미합니다. 당신은 변경할 수 forEachA를 peek통해 항목을 통과하는, 다음을 수 :

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

이것은 다소 더 좋지만 여전히 약간 이상합니다. 그 이유는 것입니다 forEachpeek유일한 부작용을 통해 자신의 작업을 할 수 있습니다. Java 8의 새로운 기능 스타일은 부작용을 방지하는 것입니다. 카운터의 증분을 count스트림에 대한 작업으로 추출하여 약간의 작업을 수행했습니다. 다른 일반적인 부작용은 컬렉션에 항목을 추가하는 것입니다. 일반적으로 수집기를 사용하여 교체 할 수 있습니다. 그러나 당신이하려는 실제 작업이 무엇인지 모르면 더 구체적인 것을 제안 할 수 없습니다.


답변

번거로운 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
    });


답변

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


답변

당신은 당신이 사용하는 정말 좋은 이유가없는 당신이 일을 사용해서는 안, 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");


답변

나를 위해 이것은 트릭을 수행했으며 누군가가 유용하다고 생각하기를 바랍니다.

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)와 동일합니다.


답변

이를 수행하는 또 다른 방법 (작업이 성공한 경우와 같이 일부 경우에만 카운트를 증가시키려는 경우 유용함)은 다음과 같이 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++;
        });
    }
}

이 예제에서 하나의 메서드에서 카운터에 대한 클래스 변수를 사용하는 것은 아마도 의미가 없으므로 적절한 이유가없는 한주의 할 것입니다. final가능한 경우 클래스 변수를 유지하면 스레드 안전성 등의 측면에서 도움이 될 수 있습니다 ( 사용에 대한 논의는 http://www.javapractices.com/topic/TopicAction.do?Id=23 참조 final).

람다가 작동하는 방식에 대한 더 나은 아이디어를 얻으려면 https://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood 에서 자세히 살펴보십시오.


답변

로컬로만 필요하기 때문에 필드를 생성하지 않으려면 익명 클래스에 저장할 수 있습니다.

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

이상 해요, 알아요. 그러나 임시 변수를 로컬 범위에서 벗어나게합니다.