lambda表达式中使用的变量应该是最终的或有效的最终
当我尝试使用calTz
时,它会显示此错误。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
答案 0 :(得分:47)
final
变量意味着它只能被实例化一次。
在Java中,你不能在lambda和匿名内部类中使用非final变量。
您可以使用旧的for-each循环重构代码:
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
即使我不了解这些代码的某些部分:
v.getTimeZoneId();
而不使用其返回值calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
,您不会修改最初传递的calTz
,并且您不会在此方法中使用它null
,为什么不将void
设置为返回类型?希望这些提示可以帮助您改进。
答案 1 :(得分:37)
虽然其他答案证明了这一要求,但他们并没有解释为什么存在这一要求。
JLS在§15.27.2中提到了原因:
对有效最终变量的限制禁止访问动态变化的局部变量,其捕获可能会引入并发问题。
为了降低漏洞风险,他们决定确保捕获的变量永远不会发生变异。
答案 2 :(得分:36)
从lambda中,你无法获得任何最终的参考。你需要从lamda外部声明一个最终的包装器来保存你的变量。
我已经添加了最终的'参考'对象作为这个包装器。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
final AtomicReference<TimeZone> reference = new AtomicReference<>();
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(reference.get()==null) {
reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
}
});
} catch (Exception e) {
//log.warn("Unable to determine ical timezone", e);
}
return reference.get();
}
答案 3 :(得分:30)
Java 8 有一个名为“Effectively final”变量的新概念。这意味着一个非最终的局部变量,其值在初始化后永远不会改变,称为“有效最终”。
引入这个概念是因为在 Java 8 之前,我们不能在anonymous class
中使用非最终的局部变量。如果您想访问anonymous class
中的本地变量,则必须使其成为最终版本。
引入lambda
时,此限制得到了缓解。因此需要制作局部变量final
如果它初始化后没有改变,因为lambda本身只是一个匿名类。
final
时, Java 8 都意识到声明局部变量lambda
的痛苦,并引入了这个概念,并且没有必要创建局部变量final
。因此,如果您发现anonymous class
的规则仍然没有更改,那么每次使用final
时,您都不必编写lambdas
个关键字。
我找到了一个很好的解释here
答案 4 :(得分:6)
在您的示例中,您可以使用简单的forEach
循环替换for
和lamdba,并自由修改任何变量。或者,可能会重构您的代码,以便您不需要修改任何变量。但是,我会完整地解释错误的含义以及如何解决它。
Java 8语言规范,§15.27.2:
使用但未在lambda表达式中声明的任何局部变量,形式参数或异常参数必须声明为final或者是有效的final(§4.12.4),否则会在尝试使用时发生编译时错误
基本上,您无法在lambda(或本地/匿名类)中修改局部变量(在本例中为calTz
)。要在Java中实现这一点,您必须使用可变对象并从lambda中修改它(通过最终变量)。这里可变对象的一个例子是一个元素的数组:
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
TimeZone[] result = { null };
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
...
result[0] = ...;
...
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return result[0];
}
答案 5 :(得分:0)
如果没有必要修改变量,那么这种问题的一般解决方法就是 提取使用lambda的代码部分,并在method-parameter上使用final关键字。
答案 6 :(得分:0)
lambda表达式中使用的变量应该是final或有效的final,但是您可以将值分配给final一个元素数组。
private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
try {
TimeZone calTzLocal[] = new TimeZone[1];
calTzLocal[0] = calTz;
cal.getComponents().get("VTIMEZONE").forEach(component -> {
TimeZone v = component;
v.getTimeZoneId();
if (calTzLocal[0] == null) {
calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}