Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
788 views
in Technique[技术] by (71.8m points)

constructor - Java "The blank final field may not have been initialized" Anonymous Interface vs Lambda Expression

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

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

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

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can bypass the problem by

        Runnable run = () -> {
            (this).obj.toString(); 
        };

This was discussed during lambda development, basically the lambda body is treated as local code during definite assignment analysis.

Quoting Dan Smith, spec tzar, https://bugs.openjdk.java.net/browse/JDK-8024809

The rules carve out two exceptions: ... ii) a use from inside of an anonymous class is okay. There is no exception for a use inside of a lambda expression

Frankly I and some other people thought the decision is wrong. The lambda only captures this, not obj. This case should have been treated the same as anonymous class. The current behavior is problematic for many legit use cases . Well, you can always bypass it using the trick above- fortunately definite assignment analysis is not too smart and we can fool it.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...