我正在玩Java 8并且遇到了一个基本场景,它说明了修复一个编译错误导致另一个编译错误的问题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,我不想这样做,或者不使用lambda表达式。我错过了其他任何解决方案吗?
答案 0 :(得分:17)
错误即将发生,因为您的result
列表无效final
,这是它在lambda中使用的必要条件。一种选择是在if
条件内声明变量,在{outside}之外声明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);
}
答案 1 :(得分:10)
使用input!= 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
}
}
答案 2 :(得分:2)
forEach(...)
对Stream
的每个元素应用操作。你真的不希望这样,你想要一个产生Stream
单个输出的List<String>
“消费者”。
幸运的是,这些在当前框架中被视为Collector
,而Collectors.toList()
完全符合您的要求。
List<String> duplicate = input.stream().collect(Collectors.toList());
答案 3 :(得分:0)
通过将结果设为final并将null赋值放在else块中,您可以保留方法的当前结构并解决&#39;你的捕获22。
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;
}
答案 4 :(得分:0)
你可以这样做
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;
}
绕过它。
答案 5 :(得分:0)
它的存在阻止了引入涉及局部变量的新类多线程错误。
Java中的局部变量到目前为止还不受竞争条件和可见性问题的影响,因为只有执行声明它们的方法的线程才能访问它们。但是lambda可以从创建它的线程传递到另一个线程,因此如果由第二个线程评估的lambda被赋予了改变局部变量的能力,那么这种免疫力将会丢失。
即使能够从不同的线程读取可变局部变量的值,也会引入同步或使用volatile的必要性,以避免读取陈旧数据。
答案 6 :(得分:0)
在这里,我总结了lambda表达式修改方法局部变量的一些通用解决方案。
Lambda表达式实际上是一个简洁形式的匿名内部类。当我们在本地方法中使用它们时,我们应该考虑几个约束:
lambdas表达式(匿名内部类)无法修改周围方法link here的局部变量,它们必须是final或java 8 effectively final
与实例变量不同,局部变量不会获取默认值,如果您想使用或返回它们,则必须先将它们初始化
当这两个约束碰撞时(如果在lambdas修改周围方法中的局部变量的方法中定义lambda),我们就会遇到麻烦
解决方案:
解决方案1:不要修改lambda中的方法局部变量或改为使用实例变量
解决方案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;
}
}