IT이야기

내부 람다에서 로컬 변수 수정

cyworld 2022. 7. 2. 13:29
반응형

내부 람다에서 로컬 변수 수정

forEach가 발생하였습니다.

보통의

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

람다와 함께

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

이 문제를 해결할 방법이 있나요?

래퍼 사용

어떤 종류의 포장지라도 좋습니다.

Java 10+에서는 셋업이 매우 간단하므로 이 구성을 사용합니다.

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

Java 8+에서는 다음 중 하나를 사용합니다.

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

... 또는 어레이:

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

주의: 병렬 스트림을 사용할 경우 매우 주의해야 합니다.예상한 결과가 나오지 않을 수 있습니다.Stuart와 같은 다른 솔루션이 이러한 경우에 더 적합할 수 있습니다.

음음음 이외의 int

이 은 물, 음, 음, 음, 음, 음, other, other, other, other, other, other, other, other, other, other, other, other, other, other ,int.

예를 들어 Java 10+의 경우:

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

Java 8 또는 9를 사용할 수 없는 경우 위와 같은 종류의 구성을 사용합니다. 단,

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

... 또는 어레이:

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

이것은 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의 답변을 보세요. 당신의 사건을 처리할 더 나은 방법이 있을지도 모릅니다.

예, 람다 내부에서 로컬 변수를 수정할 수 있지만(다른 답변에 표시된 방식으로), 이 작업은 수행하지 않아야 합니다.람다는 기능적인 스타일의 프로그래밍용으로 제작되었으며, 이는 다음을 의미합니다.부작용은 없습니다.당신이 하고 싶은 것은 나쁜 스타일로 여겨진다.병렬 하천의 경우에도 위험합니다.

부작용이 없는 해결책을 찾거나 기존의 for loop을 사용해야 합니다.

Java 10을 사용하는 경우var그 경우:

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

컴파일러를 회피하기 위해 포장할 수 있지만 람다에 포함된 부작용은 권장되지 않습니다.

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」필드를 최종으로 할 수 있습니다만, 이러한 상세를 그대로 해 두는 컴파일 에러는 발생하지 않았습니다.

보다 일반적인 솔루션을 사용하기 위해 일반 래퍼 클래스를 작성할 수 있습니다.

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

반응형