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.orElse
或optStr.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 中分配一个变量的主要内容,如果未能解决你的问题,请参考以下文章