ProgramingTip

람다 내부에서 지역 변수 수정

bestdevel 2020. 10. 16. 07:42
반응형

람다 내부에서 지역 변수 수정


에서 지역 변수를 수정하면 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를 참조 하십시오 .


AtomicInteger(또는 값을 사용하는 것 ) 의 대안 은 배열을 사용하는 것입니다.

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

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


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

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

나는 그것이 오래된 질문이라고 생각하고, 외부 변수가 최종적이라는 사실을 고려하여 해결 방법에 있다는 사실을 고려하여 해결 방법에 있습니다.

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

아마도 가장 우아하거나 가장 안전하지 않습니다.


컴파일러를 해결하기 위해 래핑 할 수없는 람다의 부작용은 권장합니다.

javadoc 을 인용 비용

스트림 작업에 대한 동작 권고 변수의 부작용은 종종 무의식적으로 상태 비 저장 요구 사항을 위반할 수 있으므로 권장되지 않습니다. forEach () 및 peek ()와 같은 소수의 스트림 작업은 측면을 통해서만 작동 할 수 있습니다. -효과; 조심해서 조심합니다.


나는 약간의 다른 문제가 있었다. forEach에서 지역 변수를 증가시키는 대신 개체를 지역 변수에 할당했습니다.

반복하려는 목록 (countryList)과 해당 목록 (foundCountry)에서 얻고 자하는 출력을 모두 래핑하는 비공개 내부 도메인 클래스를 정의하여이 문제를 해결했습니다. 그런 다음 Java 8 "forEach"를 사용하여 목록 필드를 반복하고 원하는 객체를 찾으면 해당 객체를 출력 필드에 할당합니다. 따라서 이것은 지역 변수 자체를 변경하지 않고 지역 변수의 필드에 값을 할당합니다. 나는 지역 변수 자체가 변경되지 않았기 때문에 컴파일러가 불평하지 않는다고 생각합니다. 그런 다음 목록 외부의 출력 필드에서 캡처 한 값을 사용할 수 있습니다.

도메인 개체 :

public class Country {

    private int id;
    private String countryName;

    public Country(int id, String countryName){
        this.id = id;
        this.countryName = countryName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCountryName() {
        return countryName;
    }

    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }
}

래퍼 개체 :

private class CountryFound{
    private final List<Country> countryList;
    private Country foundCountry;
    public CountryFound(List<Country> countryList, Country foundCountry){
        this.countryList = countryList;
        this.foundCountry = foundCountry;
    }
    public List<Country> getCountryList() {
        return countryList;
    }
    public void setCountryList(List<Country> countryList) {
        this.countryList = countryList;
    }
    public Country getFoundCountry() {
        return foundCountry;
    }
    public void setFoundCountry(Country foundCountry) {
        this.foundCountry = foundCountry;
    }
}

반복 작업 :

int id = 5;
CountryFound countryFound = new CountryFound(countryList, null);
countryFound.getCountryList().forEach(c -> {
    if(c.getId() == id){
        countryFound.setFoundCountry(c);
    }
});
System.out.println("Country found: " + countryFound.getFoundCountry().getCountryName());

래퍼 클래스 메서드 "setCountryList ()"를 제거하고 "countryList"필드를 최종적으로 만들 수 있지만 이러한 세부 정보를 그대로두고 컴파일 오류가 발생하지 않았습니다.


보다 일반적인 솔루션을 얻으려면 일반 Wrapper 클래스를 작성할 수 있습니다.

public static class Wrapper<T> {
    public T obj;
    public Wrapper(T obj) { this.obj = obj; }
}
...
Wrapper<Integer> w = new Wrapper<>(0);
this.forEach(s -> {
    s.setOrdinal(w.obj);
    w.obj++;
});

(이것은 Almir Campos가 제공 한 솔루션의 변형입니다).

특정 경우에 이것은 귀하의 목적 Integer보다 나쁘기 때문에 좋은 해결책이 아닙니다. int어쨌든이 해결책은 더 일반적이라고 생각합니다.

참고 URL : https://stackoverflow.com/questions/30026824/modifying-local-variable-from-inside-lambda

반응형