当前位置: 首页 > 面试题库 >

Java“空白的final字段可能尚未初始化”匿名接口与Lambda表达式

艾飞宇
2023-03-14
问题内容

我最近遇到了错误消息“空白的最终字段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在使用之前进行检查是适当的)。
还是有道理的。

但是现在,我Runnable使用 lambda表达式将 实例 的创建缩短为 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编程。我已经在每种方法中添加了注释,以解释它们应该做什么(根据分配)。我将我所知道的添加到了存根(这是我在研究学校提供的javadoc之后创建的)。我的问题不是几个函数,我知道testWord和setWord中有错误,但是我自己解决。我的问题是关于这条线的: 这行是由学校提供的,因此我必须假设它是正确的,我在任何地方都找不到关于常量字段值INITIAL的任何文档,因此,

  • 问题内容: 这个 自我回答的问题是由于变量“snackbar”可能尚未初始化而引起的。我认为还有更多细节,最好与该特定问题分开添加。 为什么以下代码无法编译? 编译错误: 问题答案: 发生这种情况的原因是实现匿名类的方式。如果对代码稍作更改然后反编译,则可以看到以下内容: 即,使匿名类引用不同的局部变量。现在可以编译了;我们可以使用进行反编译并查看匿名类的接口: (是Java内部引用匿名类的名称)

  • 我正在研究Euler Problem 9项目,其中说明: 毕达哥拉斯三元组是由三个自然数组成的集合 例如,3^2 4^2=9 16=25=52。 确实存在一个毕达哥拉斯三重态,其bc=1000。查找产品abc。 以下是我到目前为止所做的: 当我运行代码时,会出现以下错误: 注意:我的每个变量(a、b和c)都有不同的行号。 我想当我声明a、b和c为整数时,如果不赋值,默认值是0。 即使不是这样,在我

  • 问题内容: 我有一个方法创建一个,另一个方法更改字符串 我的编译器说它“可能尚未初始化”。 有人可以解释吗? 问题答案: 变量可能尚未初始化 在内部定义方法时,必须在其中初始化程序的每个变量中必须先使用一个值的地方。 同样重要的是,您的代码将永远无法正常运行,因为Java中的字符串是不可变的,因此您无法编辑字符串,因此应更改方法。 我将您的代码更改为类似的内容,但是我认为您的编辑方法应该做另一件事

  • 问题内容: 当我尝试编译时: 我得到这些错误: 在我看来,我在方法的顶部初始化了它们。怎么了 问题答案: 你声明了它们,但没有初始化它们。初始化它们是将它们设置为等于一个值: 因为未初始化变量,但在循环中增加了变量(例如),因此会收到错误消息。 Java原语具有默认值,但如下一位用户所述 当声明为类成员时,它们的默认值为零。局部变量没有默认值

  • 所以我已经在结果中声明了相关字符串的值,但是它仍然说我的变量“result”可能还没有初始化。 我正在尝试实现与此类似的输出。任何人都可以帮我吗?谢谢!