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 variable

Runnable run = () -> {

obj.toString();

};

想象一下obj,当执行lambda表达式代码时,它是一个局部变量,将obj被求值并生成一个引用。此引用存储在Runnable创建的实例的字段中。当run.run()被调用时,例如使用存储的参考值。

如果obj未初始化,则不会发生这种情况。例如

Object obj; // imagine some local variable

Runnable run = () -> {

obj.toString(); // error

};

Lambda无法捕获的值obj,因为它尚无值。它实际上等效于

final Object anonymous = obj; // won't work if obj isn't initialized

Runnable 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,而是捕获thisFoo实例的)值。它实际上等效于

final Foo anonymous = Foo.this; // you're in the Foo constructor so this is valid reference to a Foo instance

Runnable 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

回到顶部