[java] 람다 내부에서 지역 변수 수정

에서 지역 변수 수정 forEach 컴파일 오류가 발생합니다.

표준

    int ordinal = 0;
    for (Example s : list) {
        s.setOrdinal(ordinal);
        ordinal++;
    }

Lambda 사용

    int ordinal = 0;
    list.forEach(s -> {
        s.setOrdinal(ordinal);
        ordinal++;
    });

이 문제를 해결하는 방법을 아십니까?



답변

래퍼 사용

어떤 종류의 래퍼도 좋습니다.

자바 8 이상 , 사용 어느 쪽 AtomicInteger:

AtomicInteger ordinal = new AtomicInteger(0);
list.forEach(s -> {
  s.setOrdinal(ordinal.getAndIncrement());
});

… 또는 배열 :

int[] ordinal = { 0 };
list.forEach(s -> {
  s.setOrdinal(ordinal[0]++);
});

10 + 자바 :

var wrapper = new Object(){ int ordinal = 0; };
list.forEach(s -> {
  s.setOrdinal(wrapper.ordinal++);
});

노트 : 병렬 스트림을 사용하는 경우 매우주의하십시오. 예상 한 결과를 얻지 못할 수도 있습니다. Stuart와 같은 다른 솔루션은 이러한 경우에 더 적합 할 수 있습니다.

이외의 유형 int

물론 이것은 int. 래핑 유형을 AtomicReference또는 해당 유형의 배열 로 변경하기 만하면 됩니다. 예를 들어를 사용하는 경우 String다음을 수행하십시오.

AtomicReference<String> value = new AtomicReference<>();
list.forEach(s -> {
  value.set("blah");
});

배열 사용 :

String[] value = { null };
list.forEach(s-> {
  value[0] = "blah";
});

또는 Java 10 이상 :

var wrapper = new Object(){ String value; }
list.forEach(s->{
  wrapper.value = "blah";
});


답변

이것은 XY 문제에 상당히 가깝습니다. . 즉, 질문은 본질적으로 람다에서 캡처 된 지역 변수를 변경하는 방법입니다. 그러나 실제 작업은 목록의 요소에 번호를 매기는 방법입니다.

내 경험상 80 % 이상의 시간에 람다 내에서 캡처 된 로컬을 변경하는 방법에 대한 질문이 있습니다. 더 나은 진행 방법이 있습니다. 일반적으로 여기에는 감소가 포함되지만이 경우 목록 인덱스에서 스트림을 실행하는 기술이 잘 적용됩니다.

IntStream.range(0, list.size())
         .forEach(i -> list.get(i).setOrdinal(i));


답변

외부에서 람다로 값을 전달하고 가져 오지 않으면 람다 대신 일반 익명 클래스를 사용하여 수행 할 수 있습니다.

list.forEach(new Consumer<Example>() {
    int ordinal = 0;
    public void accept(Example s) {
        s.setOrdinal(ordinal);
        ordinal++;
    }
});


답변

람다 외부에서 사용 된 변수는 (암묵적으로) 최종적이어야하므로 다음과 같은 것을 사용해야합니다. AtomicInteger 하거나 자신의 데이터 구조를 작성해야합니다.

https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables를 참조
하십시오 .


답변

Java 10을 사용 var하는 경우 다음을 사용할 수 있습니다 .

var ordinal = new Object() { int value; };
list.forEach(s -> {
    s.setOrdinal(ordinal.value);
    ordinal.value++;
});


답변

AtomicInteger(또는 값을 저장할 수있는 다른 객체) 의 대안 은 배열을 사용하는 것입니다.

final int ordinal[] = new int[] { 0 };
list.forEach ( s -> s.setOrdinal ( ordinal[ 0 ]++ ) );

그러나 Stuart의 답변을 참조하십시오 . 귀하의 사건을 처리하는 더 좋은 방법이있을 수 있습니다.


답변

나는 그것이 오래된 질문이라는 것을 알고 있지만, 외부 변수가 최종적이어야한다는 사실을 고려하여 해결 방법에 익숙하다면 간단히 다음과 같이 할 수 있습니다.

final int[] ordinal = new int[1];
list.forEach(s -> {
    s.setOrdinal(ordinal[0]);
    ordinal[0]++;
});

가장 우아하거나 가장 정확하지 않을 수도 있지만 그렇게 될 것입니다.