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

查看:1208
本文介绍了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)
    }
}

(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 是合适的)。 仍然有意义。


但现在我将 Runnable / strong>使用 lambda表达式指定汉堡箭头版本:

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
    }
}

t跟随。这里我再次收到警告。我知道编译器不像通常的初始化一样处理lambda表达式,它不会替换它的长版本。但是,为什么这会影响我在 Runnable run()方法中运行代码部分的事实c $ c> object?我仍然可以在之前执行初始化 我调用 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)更复杂
。当方法引用
表达式在::
分隔符之前有一个表达式(而不是一个类型)时,该表达式被立即计算。
评估的结果存储,直到相应的函数
接口类型的方法被调用
;在这一点上,结果被用作调用的
目标引用。这意味着::分隔符之前的表达式
仅在程序
遇到方法引用表达式时求值,并且不会在
上对函数接口类型的后续调用进行重新求值。 p>

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(); 
};

Imagine 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.

code> 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编译器目前为您的snippet。

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

但是,Eclipse编译器并不捕获 obj 的值, 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天全站免登陆