Java“空白的最终字段可能尚未初始化"匿名接口与 Lambda 表达式 [英] Java "The blank final field may not have been initialized" Anonymous Interface vs Lambda Expression

查看:21
本文介绍了Java“空白的最终字段可能尚未初始化"匿名接口与 Lambda 表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近遇到错误消息空白的最终字段 obj 可能尚未初始化".

如果您尝试引用可能尚未分配给值的字段,通常就是这种情况.示例类:

I've recently been encountering the error message "The blank final field obj may not have been initialized".

Usually this is the case if you try to refer to a field that is possibly not assigned to a value yet. Example class:

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.

I use Eclipse. In the line (1) I get the error, in the line (2) everything works. So far that makes sense.

Next I try to access obj within an anonymous interface I create inside the constructor.

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 是合适的).还是有道理的.

但现在我通过使用lambda表达式Runnable实例的创建时间缩短为汉堡箭头版本:

This works, too, since I do not access obj in the moment I create the interface. I could also pass my instance to somewhere else, then initialize the object obj and then run my interface. (However it would be appropriate to check for null before using it). Still makes sense.

But now I shorten the creation of my Runnable instance to the burger-arrow version by using a lambda expression:

public class Foo {
    private final Object obj;
    public Foo() {
        Runnable run = () -> {
            obj.toString(); // error
        };
        obj = new Object();
        obj.toString(); // works again
    }
}

这里是我无法再关注的地方.在这里,我再次收到警告.我知道编译器不像通常的初始化那样处理 lambda 表达式,它不会用长版本替换它".但是,为什么这会影响在创建 Runnable 对象时我没有在我的 run() 方法中运行代码部分的事实?我仍然可以在调用 run() 之前 进行初始化.所以从技术上讲,这里可能不会遇到 NullPointerException.(虽然最好也在这里检查 null .但这个约定是另一个主题.)

我犯了什么错误?lambda 的处理方式有何不同,以至于它会以这种方式影响我的对象使用?

感谢您的进一步解释.

And here is where I can't follow anymore. Here I get the warning again. I am aware that the compiler doesn't handle lambda expressions as usual initializations, it doesn't "replace it by the long version". However, why does this affect the fact that I do not run the code part in my run() method at creation time of the Runnable object? I am still able to do the initialization before I invoke run(). So technically it is possible not to encounter a NullPointerException here. (Though it would be better to check for null here, too. But this convention is another topic.)

What is the mistake I make? What is handled so differently about lambda that it influences my object usage the way it does?

I thank you for any further explanations.

推荐答案

我无法使用 Eclipse 的编译器重现您最终情况下的错误.

I can't reproduce the error for your final case with Eclipse's compiler.

但是,我可以想象的 Oracle 编译器的推理如下:在 lambda 内部,必须在声明时捕获 obj 的值.也就是说,它必须在 lambda 体内声明时进行初始化.

However, the reasoning for the Oracle compiler I can imagine is the following: inside a lambda, the value of obj must be captured at declaration time. That is, it must be initialized when it is declared inside the lambda body.

但是,在这种情况下,Java 应该捕获 Foo 实例的值而不是 obj.然后它可以通过(初始化的)Foo 对象引用访问obj 并调用它的方法.这就是 Eclipse 编译器如何编译您的代码.

But, in this case, Java should capture the value of the Foo instance rather than obj. It can then access obj through the (initialized) Foo object reference and invoke its method. This is how the Eclipse compiler compiles your piece of code.

规范中暗示了这一点,此处:

This is hinted at in the specification, here:

方法引用表达式求值的时机比较复杂而不是 lambda 表达式(第 15.27.4 节).当一个方法引用表达式在 :: 之前有一个表达式(而不是类型)分隔符,该子表达式被立即计算.结果评估被存储直到对应函数的方法调用接口类型;在这一点上,结果被用作调用的目标引用.这意味着表达式:: 分隔符之前的值仅在程序遇到方法引用表达式,并且不会重新评估功能接口类型的后续调用.

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

类似的事情发生在

Object obj = new Object(); // imagine some local variable
Runnable run = () -> {
    obj.toString(); 
};

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

Imagine obj is a local variable, when the lambda expression code is executed, obj is evaluated and produces a reference. This reference is stored in a field in the Runnable instance created. When run.run() is called, the instance uses the reference value stored.

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

This cannot happen if obj isn't initialized. For example

Object obj; // imagine some local variable
Runnable run = () -> {
    obj.toString(); // error
};

lambda 无法捕获 obj 的值,因为它还没有值.它实际上等效于

The lambda cannot capture the value of obj, because it doesn't have a value yet. It's effectively equivalent to

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 编译器当前对您的代码段的行为方式.

This is how the Oracle compiler is currently behaving for your snippet.

然而,Eclipse 编译器不是捕获 obj 的值,而是捕获 this 的值(Foo 实例).它实际上等效于

However, the Eclipse compiler is, instead, not capturing the value of obj, it's capturing the value of this (the Foo instance). It's effectively equivalent to

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 被调用时已完全初始化.

Which is fine because you assume that the Foo instance is completely initialized by the time run is invoked.

这篇关于Java“空白的最终字段可能尚未初始化"匿名接口与 Lambda 表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆