Java:在 lambda 中分配一个变量

Posted

技术标签:

【中文标题】Java:在 lambda 中分配一个变量【英文标题】:Java: Assign a variable within lambda 【发布时间】:2022-01-24 00:37:43 【问题描述】:

我不能在 Java 中做到这一点:

Optional<String> optStr = Optional.of("foo");
String result;
optStr.ifPresent(s -> result = s);

文档说 lambdas 中使用的变量必须是有效的 final。 那么如何从 lambda 中提取一些东西并将其存储到一个变量中呢?

实际上真正的用例更复杂。 我想用matcher.replaceAll一个接一个地将几个正则表达式应用于一个字符串。我在 forEach lambda 中执行此操作,并希望将中间结果存储在某处。

【问题讨论】:

您的整个用例是什么?你想用result做什么? 你可能想看看orElse / orElseGet 其实用例更复杂。 lambda 表达式较大,我想在 lambda 中分配某些变量 对于这个特定的示例,有一些明显的替代方案不涉及依赖突变拐杖。对于您想到的“更复杂”的示例(但尚未分享),这几乎肯定也是如此。 @BrianGoetz 真正的用例并不是什么秘密。请参阅已编辑的问题。 【参考方案1】:

答案很简单:你不能(直接)这样做。

一个非常难看的 hack 是改变一个外部对象而不是分配给一个变量:

Optional<String> optStr = Optional.of("foo");
StringBuilder sb = new StringBuilder();
optStr.ifPresent(s -> sb.append(s));
String result = sb.toString();

这是因为sb 在这里是effectively final

但我想强调一点,这可能不是你真正想要做的。

如果您想使用默认值,请改用optStr.orElseoptStr.orElseGet。 如果您只想映射存在的结果,请使用optStr.map

由于您没有提供整个用例,这些只是猜测,但关键是我真的不推荐上面的 sn-p 代码:它违背了函数式编程的概念(通过改变一个对象)。

【讨论】:

用例很多。例如,如果你想在 forEach lambda 中增加一个外部整数计数器,你就不能这样做。或者您只想存储循环计算的一些中间结果。您必须将它们封装在虚拟对象中。 @George 因为“在 forEach 中增加一个外部整数计数器”听起来很像过滤然后计数,这将是一种功能性的方式。话虽如此,您也可以创建自己的 MutableInteger 并增加该对象。你不能分配,但你可以变异。【参考方案2】:

如果代码

Optional<String> optStr = Optional.of("foo");
String result;
optStr.ifPresent(s -> result = s);

是合法的,它仍然没用,因为在调用ifPresent 之后变量result 不是definitely assigned。 Java 不允许读取仅有条件初始化的局部变量。因此,对于空的 Optional,您将需要 result 的替代值,例如:

Optional<String> optStr = Optional.of("foo");
String result=null;// or any other default value
optStr.ifPresent(s -> result = s);

但是,如果您已经定义了这样的默认/备用值,则可以使用为此目的而设计的方法:

Optional<String> optStr = Optional.of("foo");
String result=optStr.orElse(null /* or any other default value */);

当您说必须初始化多个变量时,这并不会改变这些变量在任何一种情况下都需要初始化的事实。此外,在传递给 Optional 的 lambda 表达式中执行因变量的初始化也没有任何好处,毕竟 Optional 只携带一个值。 获得该值之后,依赖于该值的所有内容都可以确定,独立于Optional

Optional<String> optStr = Optional.of("foo");
Type1 variable1;
Type2 variable2;
Type3 variable3;
if(optStr.isPresent()) 
    String s=optStr.get();
    // determine and assign variable1, variable2, variable3 based on s
 else 
    // assign defaults/fall-backs to variable1, variable2, variable3

请注意,即使您的变量已经预先初始化并且您想要改变它们,使用if(optional.isPresent()) /* local modifications */ 是最简单的方法。当你用 lambda 表达式替换这个习惯用法时,没有什么比这更好的了。

【讨论】:

我给出了 Optional 只是为了简单的说明。事实上,lambda 是任意的。可以是 forEach 或一些自定义实现。 @George:重点没有改变。您不能使用 lambda 表达式来改变局部变量。正确的做法取决于每个用例,但我们只能回答您实际提出的问题,而不是您脑海中的问题。 我已经用实际用例更新了这个问题。 嗯,没有什么真正重要的改变。在你原来的问题中,你想用 lambda 表达式替换 if 语句,现在你说你想用 lambda 表达式替换 for 循环。答案仍然成立。将命令式代码转换为使用 lambda 表达式无论如何都不是一种改进。 非常无用的答案,因为它出现在我搜索“在 java 中为 lambda 中的 loca 变量赋值”时。【参考方案3】:

另一种方式,类似于 Tunaki 所写的,是使用单单元格表:

Optional<String> optStr = Optional.of("foo");
String[] temp = new String[1];
optStr.ifPresent(s -> temp[0] = s);
String result = temp[0];

表格对象是最终的,改变的是它的内容。

编辑:虽然是一个警告 - 在使用这个 hacky 解决方案之前,请查看 OP 问题的其他答案,指出为什么使用这种解决方法是一个坏主意,并考虑它是否真的值得!

【讨论】:

我完全同意 Tunaki 的观点,事实上使用这种解决方法是个坏主意,我只是指出您可以通过其他方式“破解”它。 你说得对——我会根据 Tunaki 的警告对其进行编辑。【参考方案4】:

根据 Java 规范,当 lambda 在运行时传递到该上下文时,lambda 从周围的上下文中获取变量值作为最终值。

接受该 lambda 表达式(或功能接口)的代码段的设计者接受该接口实例\lambda,并相信其值不会被更改。为了严格地使 lambda 以这种方式忠实,Java 语言规范将这种访问本地上下文变量的条件保持为 final。

简而言之,lambda 不应该改变调用它的上下文的状态。

您可以使用数组来捕获值,但仍然不建议这样做,希望 java 在未来的修订版中检测到此类代码并显示警告。

【讨论】:

以上是关于Java:在 lambda 中分配一个变量的主要内容,如果未能解决你的问题,请参考以下文章

Java 中的堆和栈

Django:在模板中分配变量

在车把中分配一个变量

实例变量未在内存中分配

在mathematica中分配变量

如何在 IF 条件中分配一个变量,然后返回它?