Java 8使用lambda表达式捕获22并且有效地最终

时间:2014-04-01 05:58:16

标签: java java-8

我正在玩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表达式。我错过了其他任何解决方案吗?

7 个答案:

答案 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; 
}
}