it-swarm.com.ru

Модификация локальной переменной изнутри лямбда

Изменение локальной переменной в forEach приводит к ошибке компиляции:

Нормальный

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

С лямбда

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

Есть идеи, как решить эту проблему?

46
Patan

Используйте обертку

С Java 8+ :

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

С Java 10+ :

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

Если вам нужно только передать значение извне в лямбду, а не получить его, вы можете сделать это с помощью обычного анонимного класса вместо лямбды:

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

Это довольно близко к проблеме XY . То есть, вопрос, который задают, заключается в том, как преобразовать захваченную локальную переменную из лямбды. Но актуальная задача - как нумеровать элементы списка.

По моему опыту, в 80% случаев возникает вопрос о том, как мутировать захваченный локальный элемент из лямбды, есть лучший способ продолжить. Обычно это включает сокращение, но в этом случае техника запуска потока по индексам списка хорошо применяется:

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

Поскольку используемые переменные извне лямды должны быть (неявно) конечными, вы должны использовать что-то вроде AtomicInteger или написать свою собственную структуру данных.

См https://docs.Oracle.com/javase/tutorial/Java/javaOO/lambdaexpressions.html#accessing-local-variables .

7
flo

Альтернативой AtomicInteger (или любому другому объекту, способному хранить значение) является использование массива:

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

Но посмотрите ответ Стюарта : возможно, есть лучший способ разобраться с вашим делом.

3
zakmck

Я знаю, что это старый вопрос, но если вам удобен обходной путь, учитывая тот факт, что внешняя переменная должна быть конечной, вы можете просто сделать это:

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

Может быть, не самый элегантный или даже самый правильный, но это подойдет. 

2
Almir Campos

Если вы используете Java 10, вы можете использовать var для этого:

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

Вы можете обернуть его, чтобы обойти компилятор, но помните, что побочные эффекты в лямбдах не приветствуются. 

Процитировать Javadoc

Побочные эффекты в поведенческих параметрах для потоковых операций, как правило, не приветствуются, поскольку они часто могут привести к невольным нарушениям требования о безгражданстве Небольшое количество потоковых операций, таких как forEach () и peek (), могут работать только через побочные эффекты; они должны использоваться с осторожностью

0
user2153465

У меня была немного другая проблема. Вместо того, чтобы увеличивать локальную переменную в 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» окончательным, но я не получил ошибок компиляции, оставив эти данные как есть.

0
Steve T

Чтобы иметь более общее решение, вы можете написать общий класс 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++;
});

(это вариант решения, данного Альмиром Кампосом).

В конкретном случае это не очень хорошее решение, так как Integer хуже, чем int для вашей цели, в любом случае, я думаю, что это решение более общее.

0
luca.vercelli