Java 8 catch 22 с лямбда-выражениями и эффективно конечный
Я играю с Java 8 и нахожу базовый сценарий, который иллюстрирует catch 22, где исправление одной ошибки компиляции вызывает другую ошибку компиляции. Сценарий (это просто пример, упрощенный от чего-то более сложного):
public static List<String> catch22(List<String> input) {
List<String> result = null;
if (input != null) {
result = new ArrayList<>(input.size());
input.forEach(e -> result.add(e)); // compile error here
}
return result;
}
Я получаю ошибку компиляции:
Локальная переменная, определенная в охватывающей области, должна быть окончательной или окончательной окончательной
Если я изменил первую строку на:
List<String> result;
Я получаю ошибку компиляции в последней строке:
Результат локальной переменной не может быть инициализирован
Кажется, что единственный подход здесь - предварительно инициализировать мой результат в ArrayList, который я не хочу делать, или не использовать лямбда-выражения. Не хватает ли другого решения?
Ответы
Ответ 1
Ошибка возникает, потому что ваш result
список не эффективен final
, который является требованием для его использования в лямбда. Один из вариантов - объявить переменную внутри условия if
и return null;
снаружи. Но я не думаю, что это была бы хорошая идея. Ваш текущий метод не делает ничего полезного. Было бы гораздо разумнее вернуть из него пустой список.
Сказав, что все, я бы сказал, поскольку вы играете с Java 8, используйте Optional
вместе с потоками:
public static List<String> catch22(List<String> input) {
return Optional.ofNullable(input)
.orElse(new ArrayList<String>())
.stream().collect(Collectors.toList());
}
И если вы хотите вернуть null
, я, вероятно, изменю свой метод на:
public static List<String> catch22(List<String> input) {
if (input == null) return null;
return input.stream().collect(Collectors.toList());
// Or this. B'coz this is really what your code is doing.
return new ArrayList<>(input);
}
Ответ 2
Вставьте объявление внутри блока с помощью ввода!= null. Пример:
public static List<String> catch22(List<String> input) {
if (input != null) {
List<String> result;
result = new ArrayList<>(input.size());
input.forEach(e -> result.add(e)); // compile error here
return result;
} else {
return null; // or empty list or something that suits your app
}
}
Ответ 3
forEach(...)
применяет операцию для каждого элемента Stream
. Вы действительно этого не хотите, вы хотите Stream
"потребитель", который производит один вывод List<String>
.
К счастью, это считается Collector
в текущей структуре, а Collectors.toList()
делает именно то, что вы хотите.
List<String> duplicate = input.stream().collect(Collectors.toList());
Ответ 4
Сделав окончательный результат и поместив нулевое присвоение в блок else, вы сможете сохранить текущую структуру вашего метода и "решить" ваш catch22.
public static List<String> catch22(List<String> input) {
final List<String> result;
if (input != null) {
result = new ArrayList<>(input.size());
input.forEach(e -> result.add(e));
} else {
result = null;
}
return result;
}
Ответ 5
Вы можете сделать это
public static List<String> catch22(List<String> input) {
List<String> result = null;
if (input != null) {
result = new ArrayList<>(input.size());
List<String> effectivelyFinalResult = result;
input.forEach(e -> effectivelyFinalResult.add(e)); // No compile error here
}
return result;
}
чтобы обойти его.
Ответ 6
Его присутствие препятствует внедрению нового класса многопоточных ошибок с использованием локальных переменных.
Локальные переменные в Java до сих пор не застрахованы от условий гонки и видимости, потому что они доступны только для потока, выполняющего метод, в котором они объявлены. Но лямбда может быть передана из потока, который создал ее в другом потоке, и поэтому иммунитет будет потерян, если лямбда, оцененная вторым потоком, получит возможность мутировать локальные переменные.
Даже возможность считывать значение изменяемых локальных переменных из другого потока может привести к необходимости синхронизации или использования летучих, чтобы избежать чтения устаревших данных.
Ответ 7
Здесь я обобщил некоторые общие решения для lambdas выражений, модифицирующих локальные переменные метода.
Lambdas Выражения фактически являются анонимными внутренними классами в краткой форме. Когда мы используем их в локальных методах, мы должны учитывать несколько ограничений:
-
выражения lambdas (анонимный внутренний класс) не могут изменять локальные переменные окружающего метода ссылка здесь, они должны быть окончательными или в java 8 окончательно
-
В отличие от переменных экземпляра локальные переменные не получают значений по умолчанию, и если вы хотите их использовать или вернуть, вы должны сначала их инициализировать
Когда эти два ограничения сталкиваются (в случае определения lambdas в методе, где lambdas модифицирует локальную переменную в окружающем методе), мы попадаем в проблему
решения:
решение 1: не изменять локальную переменную метода в lambdas или вместо этого использовать переменные экземпляра
solution 2: выполните некоторые трюки, например скопируйте локальную переменную в другую и передайте это lambdas:
public static List<String> catch22(List<String> input) {
List<String> result = null;
if (input != null) {
result = new ArrayList<>(input.size());
List<String> effectivelyFinalResult = result;
input.forEach(e -> effectivelyFinalResult.add(e));
}
return result;
}
или ограничить область локальных переменных, чтобы вы не попали в проблему, чтобы не инициализировать их:
public static List<String> catch22(List<String> input) {
if (input != null) {
List<String> result; // result gets its value in the lambdas so it is effectively final
result = new ArrayList<>(input.size());
input.forEach(e -> result.add(e));
return result;
} else {
return null;
}
}