Java“空白的final字段可能尚未初始化”匿名接口与Lambda表达式
我最近遇到了错误消息“空白的最终字段obj可能尚未初始化”。
如果您尝试引用可能尚未分配值的字段,通常就是这种情况。示例类:
public class Foo { private final Object obj;
public Foo() {
obj.toString(); // error (1)
obj = new Object();
obj.toString(); // just fine (2)
}
}
我使用Eclipse。在该行中,(1)
我得到了错误,在该行中,(2)
一切正常。
接下来,我尝试obj
在构造函数内部创建的匿名接口中访问。
public class Foo { private Object obj;
public Foo() {
Runnable run = new Runnable() {
public void run() {
obj.toString(); // works fine
}
};
obj = new Object();
obj.toString(); // works too
}
}
这也行得通,因为obj
在创建界面的那一刻我无法访问。我也可以将实例传递到其他地方,然后初始化对象obj
,然后运行接口。(但是,null
在使用之前进行检查是适当的)。
使用 burger-arrow版本:
public class Foo { private final Object obj;
public Foo() {
Runnable run = () -> {
obj.toString(); // error
};
obj = new Object();
obj.toString(); // works again
}
}
这是我无法再关注的地方。在这里,我再次得到警告。我知道,编译器不会像通常的初始化那样处理lambda表达式,它不会“将其替换为长版本”。但是,为什么这会影响以下事实:run()
在Runnable
对象创建时我没有在方法中运行代码部分?
在 调用 之前,
我仍然可以进行初始化run()
。因此从技术上讲,这里可能不会遇到NullPointerException
。(尽管也最好在null
这里进行检查。但这是另一个约定。)
我犯了什么错误?lambda的处理方式有何不同,以至于它影响我的对象使用方式?
感谢您的进一步解释。
回答:
我无法使用Eclipse的编译器重现您最后一个案例的错误。
但是,我可以想象的Oracle编译器的理由如下:在lambda中,obj
必须在声明时捕获的值。也就是说,在lambda主体中声明它时必须对其进行初始化。
但是,在这种情况下,Java应该捕获Foo
实例的值而不是obj
。然后,它可以obj
通过(初始化的)Foo
对象引用进行访问并调用其方法。Eclipse编译器就是这样编译您的代码。
这在规范中有所暗示,在这里:
方法参考表达式评估的时间比lambda表达式(第15.27.4节)要复杂。当方法引用表达式的::分隔符之前具有表达式(而不是类型)时,将立即对该子表达式求值。
;此时,结果将用作调用的目标参考。这意味着::分隔符之前的表达式仅在程序遇到方法引用表达式时才被评估,并且不会在后续对功能接口类型的调用时被重新评估。
类似的事情发生了
Object obj = new Object(); // imagine some local variableRunnable run = () -> {
obj.toString();
};
想象一下obj
,当执行lambda表达式代码时,它是一个局部变量,将obj
被求值并生成一个引用。此引用存储在Runnable
创建的实例的字段中。当run.run()
被调用时,例如使用存储的参考值。
如果obj
未初始化,则不会发生这种情况。例如
Object obj; // imagine some local variableRunnable run = () -> {
obj.toString(); // error
};
Lambda无法捕获的值obj
,因为它尚无值。它实际上等效于
final Object anonymous = obj; // won't work if obj isn't initializedRunnable run = new AnonymousRunnable(anonymous);
...
class AnonymousRunnable implements Runnable {
public AnonymousRunnable(Object val) {
this.someHiddenRef = val;
}
private final Object someHiddenRef;
public void run() {
someHiddenRef.toString();
}
}
这就是Oracle编译器当前如何运行您的代码片段。
但是,Eclipse编译器不是捕获的值obj
,而是捕获this
(Foo
实例的)值。它实际上等效于
final Foo anonymous = Foo.this; // you're in the Foo constructor so this is valid reference to a Foo instanceRunnable run = new AnonymousRunnable(anonymous);
...
class AnonymousRunnable implements Runnable {
public AnonymousRunnable(Foo foo) {
this.someHiddenRef = foo;
}
private final Foo someHiddenFoo;
public void run() {
someHiddenFoo.obj.toString();
}
}
这很好,因为您假设Foo
实例在run
调用时已完全初始化。
以上是 Java“空白的final字段可能尚未初始化”匿名接口与Lambda表达式 的全部内容, 来源链接: utcz.com/qa/414244.html