Win a copy of Kotlin in Action this week in the Kotlin forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Replacing Anonymous Inner Class with "equivalent" Lambda Expression Causes Compile-Time Error  RSS feed

 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I wrote some code that included an ActionListener that was implemented in the pre-lambda style. Upon the suggestion of my NetBeans IDE, I converted it to use a lambda expression.

Whereupon, NetBeans flagged an error. I'm guessing this has something to do with the fact that lambdas aren't implemented as anonymous inner classes. Ordinarily, I'd crack the books and seek an answer, but I have a lot on my plate today and would be grateful if anyone who might already know would explain why this is the case.

Here's my code:



Why is there a risk that obj won't be initialized when the lambda code runs, but not when the anonymous inner class code runs. Is there some way the lambda might execute before the constructor returns, but the anonymous inner class's method can't?
 
Campbell Ritchie
Marshal
Posts: 55715
163
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Duplicating in the Java8 forum.

It looks to me that you have a final field which has not been initalised in the constructor (nor in an initialiser nor on declaration). That would be a compile‑time error, an hasn't actually got anything to do with the λ. I shall try your code finish my beer, and report back later.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Haven't I initialized it at Line 29?
 
Campbell Ritchie
Marshal
Posts: 55715
163
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
It obviously has something to do with the obj field not being initialised until after the λ is written. I got what appears to be the same error:-
Campbell's javac tool wrote:Init.java:26: error: variable obj might not have been initialized
            System.out.println(obj.toString());
Let's have a look at the JLS: I didn't find anything helpful in the section about constructors.
Let's try the section about λs. It says that local variables must be effectively final, but that doesn't help because you have already marked the variable final and it is a field not a local variable. The diagram about whether variables have been definitely assigned or not might help a bit. It says that effectively final local variables, etc., must be definitely assigned to before the λ is declared.
Maybe this part of the JLS will help. It says
JLS Chapter 16 wrote:Chapter 16. Definite Assignment

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.
An access to its value consists of the simple name of the variable (or, for a field, the simple name of the field qualified by this) occurring anywhere in an expression except as the left-hand operand of the simple assignment operator = (§15.26.1).
For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.
Similarly, every blank final variable must be assigned at most once; it must be definitely unassigned when an assignment to it occurs.
So every final field must be definitely assigned before it is used. In your code. you are not definitely assigning it until after its use in the λ. The concept of a λ with a variable (which I think constitutes a closure) is that the λ captures the variable with its value. So a λ requires the field be declared with a value before its declaration.

What an interesting question

The mystery is why you can get the anonymous class to compile. We know that local variables used in an anonymous class must be declared final (I think that was slightly relaxed in Java8). You are here apparently using a final field before it is assigned to. It must be that the anonymous class assumes the variable will have a value somewhere. If you are going to be strict about definite assignment, you would expect the anonymous class to produce a compiler error too.
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Campbell Ritchie wrote:If you are going to be strict about definite assignment, you would expect the anonymous class to produce a compiler error too.


Indeed I would, and here is a reason why:


This code compiles fine but, when the ActionListener is explicitly called from Line 29, the JVM delcares a NullPointerException at Line 23 because, in fact, obj has not then been initialized.

Kind of a double-standard being applied here, eh?
 
Stevens Miller
Bartender
Posts: 1444
30
C++ Java Netbeans IDE Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I'm going to stick my neck out a bit and guess that the real problem is not a double-standard, but a misleading error message. In the case of the anonymous inner class, it's true that obj might not be intialized before the ActionListener's actionPerformed, but it is not a certainty. As an inner class of Init, the anonymous subclass of ActionListener will have access to the outer class's fields, including obj, which might be initialized by the first time actionPerformed is called. But, the lambda is not an inner class of Init, so it won't have access to Init's fields. Being final, if obj has been initialized before the lambda is defined, the lambda can safely copy obj's value, secure in the knowledge that the copy will remain valid for the lifetime of the object. There being no way obj can change, the lambda doesn't need access to Init's fields.

This has me thinking the error message shouldn't be "might not have been initialized," but rather, "has not been initialized." I don't know if a warning for the anonymous inner class would even make sense. Even with final fields, you can get yourself into the same pickle without anonymous inner classes:

There are all kinds of ways that you can access an uninitialized final from within a constructor (further reason to avoid running much code in a constructor). To know if a final might be accessed before initialization would, in the extreme case, call for the compiler to solve The Halting Problem, I think. Apparently, you can use that fact to fool the compiler into compiling something just as flawed as what it won't let you compile:


This compiles fine, but throws another NullPointerException at Line 28 (when it is called from Line 31).

Guess we can only ask the compiler to save us from ourselves, just so much.
 
Campbell Ritchie
Marshal
Posts: 55715
163
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Stevens Miller wrote:. . . a misleading error message.  . . .
So I tried the code on Eclipse, which gives different error messages. Unfortunately not very different; it says
The blank final field obj might not have been initialized.
Sounds like something which require explanation. I think it is a case of a closure capturing the value of the field, so it ought to be initialised before reaching the closure. But the anonymous class simply needs the field to exist and can capture its value when its method is invoked.

Can't think of anything else to say on this topic. I don't know whether we shall get closer to resolution.
 
It is sorta covered in the JavaRanch Style Guide.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!