带有lambda表达式和final的Java8Catch 22

带有lambda表达式和final的Java8Catch 22,java,java-8,Java,Java 8,我正在玩Java8,并遇到了一个基本场景,该场景演示了catch 22,其中修复一个编译错误会导致另一个编译错误。场景(这只是一个从更复杂的内容简化而来的示例): 公共静态列表catch22(列表输入){ 列表结果=空; 如果(输入!=null){ 结果=新的ArrayList(input.size()); input.forEach(e->result.add(e));//此处编译错误 } 返回结果; } 我得到一个编译错误: 在封闭范围中定义的局部变量结果必须是final或final 如果

我正在玩Java8,并遇到了一个基本场景,该场景演示了catch 22,其中修复一个编译错误会导致另一个编译错误。场景(这只是一个从更复杂的内容简化而来的示例):

公共静态列表catch22(列表输入){
列表结果=空;
如果(输入!=null){
结果=新的ArrayList(input.size());
input.forEach(e->result.add(e));//此处编译错误
}
返回结果;
}
我得到一个编译错误:

在封闭范围中定义的局部变量结果必须是final或final

如果我将第一行更改为:

List<String> result;
列表结果;
最后一行出现编译错误:

局部变量结果可能尚未初始化


这里似乎唯一的方法是将结果预初始化为ArrayList,我不想这样做,或者不使用lambda表达式。我是否缺少任何其他解决方案?

使用输入将声明推到块内!=无效的例如:

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
    }
}
公共静态列表catch22(列表输入){
如果(输入!=null){
列出结果;
结果=新的ArrayList(input.size());
input.forEach(e->result.add(e));//此处编译错误
返回结果;
}否则{
返回null;//或空列表或适合您的应用程序的内容
}
}

出现错误是因为您的
结果
列表不是有效的
最终
,这是lambda中使用它的要求。一个选项是在
if
条件中声明变量,并
返回null外部。但我认为这不是个好主意。你目前的方法没有任何成效。从中返回一个空列表会更有意义

话虽如此,我想说,既然您正在使用Java 8,请在这里与streams一起使用:

public static List<String> catch22(List<String> input) {
    return Optional.ofNullable(input)
            .orElse(new ArrayList<String>())
            .stream().collect(Collectors.toList());
}

通过使结果为最终结果并将空赋值放在else块中,您可以保持方法的当前结构并“解决”您的问题

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;
}
公共静态列表catch22(列表输入){
最终列表结果;
如果(输入!=null){
结果=新的ArrayList(input.size());
input.forEach(e->result.add(e));
}否则{
结果=空;
}
返回结果;
}
forEach(…)
流的每个元素应用一个操作。你真的不想这样,你想要一个
“消费者”,它产生一个
列表
的单一输出

幸运的是,在当前的框架中,它们被认为是
Collector
s,而
Collector.toList()
正是您想要的

List<String> duplicate = input.stream().collect(Collectors.toList());
List duplicate=input.stream().collect(Collectors.toList());
您可以这样做

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;
}
公共静态列表catch22(列表输入){
列表结果=空;
如果(输入!=null){
结果=新的ArrayList(input.size());
有效列出最终结果=结果;
input.forEach(e->effectivefinalresult.add(e));//此处没有编译错误
}
返回结果;
}

为了避开它

它的存在防止了一类新的涉及局部变量的多线程错误的引入

到目前为止,Java中的局部变量不受竞争条件和可见性问题的影响,因为只有执行声明它们的方法的线程才能访问它们。但是lambda可以从创建它的线程传递到另一个线程,因此,如果第二个线程评估的lambda能够变异局部变量,那么免疫性就会丧失


即使能够从不同线程读取可变局部变量的值,也会引入同步或使用volatile的必要性,以避免读取过时的数据。

这里,我总结了lambdas表达式修改方法局部变量的一些通用解决方案

Lambdas表达式实际上是一种简洁形式的匿名内部类。当我们在局部方法中使用它们时,我们应该考虑几个约束:

  • lambdas表达式(匿名内部类)不能修改周围方法的局部变量,它们必须是final或在Java8中

  • 与实例变量不同,局部变量不获取默认值,如果要使用或返回它们,必须先初始化它们

当这两个约束冲突时(在lambdas修改周围方法中的局部变量的方法中定义lambdas的情况下),我们会遇到麻烦

解决方案:

解决方案1:不要修改lambdas中的方法局部变量,或者改用实例变量

解决方案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;
}
公共静态列表catch22(列表输入){
列表结果=空;
如果(输入!=null){
结果=新的ArrayList(input.size());
有效列出最终结果=结果;
input.forEach(e->effectivefinalresult.add(e));
}
返回结果;
}
或者限制局部变量的作用域,这样您就不会因为没有初始化它们而陷入麻烦:

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; 
}
} 
公共静态列表catch22(列表输入){
如果(输入!=null){
List result;//result在lambda中获取其值,因此它实际上是最终的
结果=新的ArrayList(input.size());
input.forEach(e->result.add(e));
返回结果;
}否则{
返回null;
}
} 

oth,此方法的调用方不会乐意在每次需要访问返回列表时检查null。做正确的事情,返回一个空列表,而不是返回null。您只是将值从
输入
传输到
结果
?@RohitJain在这个简化的假示例中,是的。结果不需要是最终的。它必须是有效的最终结果。回答得好-感谢您展示了两种不同的有趣方法。在第一个示例中,即使
input
是passe
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; 
}
}