Изменение локальной переменной в forEach
приводит к ошибке компиляции:
Нормальный
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
С лямбдой
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Есть идеи, как это решить?
Ответы:
Используйте обертку
Любая обертка хороша.
В Java 8+ используйте
AtomicInteger
:... или массив:
С Java 10+ :
Примечание: будьте очень осторожны, если используете параллельный поток. Вы можете не получить ожидаемый результат. Другие решения, такие как решение Стюарта, могут быть более адаптированы для этих случаев.
Для типов, отличных от
int
Конечно, это все еще верно для типов, отличных от
int
. Вам нужно только изменить тип упаковкиAtomicReference
на массив этого типа. Например, если вы используете aString
, просто сделайте следующее:Используйте массив:
Или с Java 10+:
источник
int[] ordinal = { 0 };
? Вы можете это объяснить. Спасибоclass MyClass$1 { int value; }
В обычном классе это означает, что вы создаете класс с частной переменной пакета с именемvalue
. Это то же самое, но на самом деле класс анонимен для нас, а не для компилятора или JVM. Java скомпилирует код, как если бы он былMyClass$1 wrapper = new MyClass$1();
. И анонимный класс становится просто другим классом. В конце концов, мы просто добавили синтаксический сахар поверх него, чтобы сделать его читабельным. Кроме того, этот класс является внутренним классом с закрытым для пакета полем. Это поле можно использовать.Это довольно близко к проблеме XY . То есть, вопрос, который задается, по сути, заключается в том, как изменить захваченную локальную переменную из лямбды. Но настоящая задача состоит в том, как пронумеровать элементы списка.
По моему опыту, более чем в 80% случаев возникает вопрос о том, как изменить захваченный локальный объект из лямбды, есть лучший способ продолжить. Обычно это включает сокращение, но в этом случае хорошо применима техника запуска потока по индексам списка:
источник
list
будетRandomAccess
списокЕсли вам нужно только передать значение извне в лямбду, а не получить его, вы можете сделать это с помощью обычного анонимного класса вместо лямбда:
источник
Поскольку используемые переменные извне lamda должны быть (неявно) окончательными, вы должны использовать что-то вроде
AtomicInteger
или написать свою собственную структуру данных.См. Https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
источник
Если вы используете Java 10, вы можете использовать
var
для этого:источник
Альтернативой
AtomicInteger
(или любому другому объекту, способному хранить значение) является использование массива:Но посмотрите на ответ Стюарта : может быть, есть лучший способ разобраться с вашим делом.
источник
Я знаю, что это старый вопрос, но если вам удобно обходной путь, учитывая тот факт, что внешняя переменная должна быть окончательной, вы можете просто сделать это:
Может быть, не самый элегантный или даже самый правильный, но сойдет.
источник
Вы можете обернуть это, чтобы обойти компилятор, но помните, что побочные эффекты в лямбдах не приветствуются.
Процитировать javadoc
источник
У меня была немного другая проблема. Вместо увеличения локальной переменной в forEach мне нужно было присвоить объект локальной переменной.
Я решил это, определив частный внутренний класс домена, который охватывает как список, который я хочу перебирать (countryList), так и результат, который я надеюсь получить из этого списка (foundCountry). Затем, используя Java 8 «forEach», я перебираю поле списка и, когда объект, который мне нужен, найден, я назначаю этот объект полю вывода. Таким образом, это присваивает значение полю локальной переменной, не изменяя саму локальную переменную. Я считаю, что, поскольку сама локальная переменная не изменяется, компилятор не жалуется. Затем я могу использовать значение, записанное в поле вывода, вне списка.
Объект домена:
Объект-оболочка:
Повторять операцию:
Вы можете удалить метод класса оболочки "setCountryList ()" и сделать поле "countryList" окончательным, но я не получал ошибок компиляции, оставив эти детали как есть.
источник
Чтобы получить более общее решение, вы можете написать общий класс Wrapper:
(это вариант решения, данного Альмиром Кампосом).
В конкретном случае это не лучшее решение, так как
Integer
оно хуже, чемint
для вашей цели, в любом случае, я думаю, это решение является более общим.источник
Да, вы можете изменять локальные переменные изнутри лямбда-выражений (как показано в других ответах), но вы не должны этого делать. Лямбды предназначены для функционального стиля программирования, а это означает: отсутствие побочных эффектов. То, что вы хотите делать, считается плохим стилем. Это также опасно в случае параллельных потоков.
Вы должны либо найти решение без побочных эффектов, либо использовать традиционный цикл for.
источник