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

4
@Florian AtomicInteger여기 에서 사용해야 합니다.
Rohit Jain

답변:


70

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

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스트림에 대한 작업으로 추출하여 약간의 작업을 수행했습니다. 다른 일반적인 부작용은 컬렉션에 항목을 추가하는 것입니다. 일반적으로 수집기를 사용하여 교체 할 수 있습니다. 그러나 당신이하려는 실제 작업이 무엇인지 모르면 더 구체적인 것을 제안 할 수 없습니다.


8
구현이 스트림에 대한 바로 가기를 사용하기 시작 peek하면 작동 이 중지 된다는 점에 유의해야합니다 . 즉에 문제가 결코 수 에드 스트림하지만 누군가가 ... 나중에 코드를 변경하는 경우 큰 놀라움을 만들 수 있습니다countSIZEDfilter
홀거

14
선언 final AtomicInteger i = new AtomicInteger(1);하고 람다 어딘가에 i.getAndAdd(1). 중지하고 예전 얼마나 좋았는지 기억하십시오 int i=1; ... i++.
aliopi

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

1
foreach컬렉션에 직접 간단하게 (streams없이) 이것은 충분히 좋은 접근 방법이라고 생각합니다. 감사 !!
Sabir Khan

이것은 병렬로 실행하지 않는 한 가장 간단한 솔루션입니다.
David DeMar

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

7
당신의 대답은 나를 혼란스럽게합니다. 두 개의 변수가 모두 runCount. 나는 당신이 그들 중 하나만 가질 계획이라고 생각하지만 어떤 것입니까?
Ole VV

1
runCount.getAndIncrement ()가 더 적합하다는 것을 알았습니다. 좋은 대답입니다!
kospol

2
AtomicInteger저를 도와하지만 난 그것을 init을 것입니다new AtomicInteger(0)
스테판 Höltker

1)이 코드는 컴파일되지 않습니다 : 스트림에는 long을 반환하는 터미널 작업이 없습니다. 2) 그렇더라도 'runCount'값은 항상 '1'이됩니다.-스트림에는 터미널 작업이 없으므로 peek () 람다 인수 호출되지 않음-System.out 라인은 실행 횟수를 표시하기 전에 증가시킵니다
Cédric

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.
Brad Cupit

1
정말? 그리고 왜?
lkahtz

1
동의합니다. 내가 lamda / stream에서 동시 작업을 수행하지 않는다는 것을 안다면 동시성을 충족하도록 설계된 AtomicInteger를 사용하고 싶은 이유는 무엇입니까? 잠금 등을 도입 할 수 있습니다. JDK는 잠금을 수행하지 않는 새로운 컬렉션 및 반복기를 도입했습니다. 성능 저하 기능 (예 : 잠금시 잠금이 많은 시나리오에서 필요하지 않은 경우)에 부담을주는 이유.
Volksman

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()및 사용하는 것입니다 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 에서 자세히 살펴보십시오.


3

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

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

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


2
지구에 무슨 일이 조금 더 설명을 들으 필요, 여기에 것입니다
알렉산더 밀스

기본적으로 익명 클래스의 필드를 증가시키고 검색합니다. 구체적으로 헷갈리는 부분을 말씀해 주시면 명확히하겠습니다.
shmosel

1
@MrCholo 이니셜 라이저 블록 입니다. 생성자 전에 실행됩니다.
shmosel

1
@MrCholo 아니요, 인스턴스 이니셜 라이저입니다.
shmosel

1
@MrCholo 익명 클래스는 명시 적으로 선언 된 생성자를 가질 수 없습니다.
shmosel

1

감소도 작동합니다. 이렇게 사용할 수 있습니다.

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

1

또 다른 대안은 Apache commons MutableInt를 사용하는 것입니다.

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.