Лямбда-выражения работают как обратные вызовы.В тот момент, когда они передаются в коде, они «хранят» любые внешние значения (или ссылки), которые им необходимы для работы (как если бы эти значения были переданы в качестве аргументов при вызове функции. Это просто скрыто от разработчика).В первом примере вы можете обойти эту проблему, сохранив k
в отдельной переменной, например d:
for (int k = 0; k < 10; k++) {
final int d = k
new Thread(() -> System.out.println(d)).start();
}
Фактически final
означает, что в приведенном выше примере вы можете оставить 'final 'ключевое слово out, потому что d
фактически является окончательным, поскольку оно никогда не изменяется в своей области действия. Циклы
For
работают по-разному.Это итеративный код (в отличие от обратного вызова).Они работают в своей области видимости и могут использовать все переменные в своем стеке.Это означает, что кодовый блок цикла for
является частью внешнего блока кода.
Что касается выделенного вами вопроса:
Усовершенствованный цикл for
не работает с обычным индексом-счетчик, по крайней мере, не напрямую.Усовершенствованные циклы for
(над массивами) создают скрытый итератор.Вы можете проверить это следующим образом:
Collection<String> mySet = new HashSet<>();
mySet.addAll(Arrays.asList("A", "B", "C"));
for (String myString : mySet) {
if (myString.equals("B")) {
mySet.remove(myString);
}
}
Приведенный выше пример вызовет исключение ConcurrentModificationException.Это связано с тем, что итератор заметил, что базовая коллекция изменилась во время выполнения.Однако в вашем примере внешний цикл создает «окончательную» переменную arg
, на которую можно ссылаться в лямбда-выражении, поскольку значение захватывается во время выполнения.
Предупреждение захвата 'неэффективно-окончательные 'значения - более или менее просто предосторожность в Java, потому что в других языках (например, JavaScript) это работает по-другому.
Таким образом, компилятор может теоретически перевести ваш код, захватить значение ипродолжайте, но при этом придется хранить это значение иначе, и вы, вероятно, получите неожиданные результаты.Поэтому команда, разрабатывающая лямбда-выражения для Java 8, правильно исключила этот сценарий, исключив его за исключением.
Если вам когда-либо понадобится изменить значения внешних переменных в лямбда-выражениях, вы можете объявить одноэлементный массив:
String[] myStringRef = { "before" };
someCallingMethod(() -> myStringRef[0] = "after" );
System.out.println(myStringRef[0]);
Или используйте Atomic , чтобы сделать его поточно-ориентированным.Однако в вашем примере это, вероятно, вернет «до», поскольку поток, скорее всего, будет выполнен после выполнения println.