Unexpected error using lambdas in Java 8

17,908

Although the error message is pretty clear about what is happening (the compiler thinks the final variable "panel" is being called before it is instantiated), the variable will not be called until the button generates an action, and how we can´t say when the action will happen, the code should compile.

You should consider the fact that compilers follow formal rules and don’t have your knowledge. Especially, a compiler can’t know that the method addActionListener does not invoke the actionPerformed method immediately. It also has no idea about the visibility of the button which determines when actionPerformed might be called.

The formal behavior has a specification. There you find the following points:

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

and

15.27.2. Lambda Body

Unlike code appearing in anonymous class declarations, the meaning of names and the this and super keywords appearing in a lambda body, along with the accessibility of referenced declarations, are the same as in the surrounding context (except that lambda parameters introduce new names).

In your code, the meaning of names within the lambda body, read of panel, is the same as in the surrounding context which is the constructor. In that context, the rule that “every blank final field must have a definitely assigned value when any access of its value occurs” applies.

And, yes, that’s different from inner class definitions. The specification explicitly states that.

Share:
17,908
Fabiano
Author by

Fabiano

Updated on June 19, 2022

Comments

  • Fabiano
    Fabiano almost 2 years

    I'm using Java 8 Update 20 32 bits, Maven 3.2.3, Eclipse Luna Build id: 20140612-0600 32 bits.

    After starting using lambdas, some classes in my projects started to report compilation errors in maven (mvn compile).

    These errors appears only when I use lambdas. If I switch back to anonymous classes, the errors are gone.

    I can reproduce the error with a simple test case:

    package br;
    
    import java.awt.Button;
    import java.awt.Panel;
    
    public class Test {
    
        private final Button button;
        private final Panel panel;
    
        public Test() {
            button = new Button();
            button.addActionListener(event -> {
                System.out.println(panel);
            });
            panel = new Panel();
        }
    }
    

    I compile it this way:

    mvn clean;mvn compile
    

    And I get this error:

    [ERROR] /C:/Users/fabiano/workspace-luna/Test/src/main/java/br/Test.java:[14,44] variable panel might not have been initialized
    

    Although the error message is pretty clear about what is happening (the compiler thinks the final variable panel is being called before it is instantiated), the variable will not be called until the button generates an action, and how we can't say when the action will happen, the code should compile. Indeed, it compiles as it should if I don't use lambdas:

    package br;
    
    import java.awt.Button;
    import java.awt.Panel;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    public class Test {
    
        private final Button button;
        private final Panel panel;
    
        public Test() {
            button = new Button();
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(panel);
                }
            });
            panel = new Panel();
        }
    }
    

    I noticed two other strange things related to this problem:

    1. Eclipse don't report this error when it auto-compile the class. Eclipse is using the same JDK as maven to compile the class.
    2. If I use maven to compile the class using anonymous classes, then I change the class to use lambdas and compile it using maven again, it doesn't report the error. In this case it just reports the error again if I use mvn clean followed by mvn compile.

    Can someone help me to fix this problem? Or try to reproduce this problem?

    • mkrakhin
      mkrakhin over 9 years
      First of all, you shouldn't add listener during instance construction. This leads to reference leakage and considered as a very bad practice in concurrent programming. Now, about your final fields. It's obviously initialized after you create listener referencing to this panel.
    • SimY4
      SimY4 over 9 years
      Why can't you just initialize panel field before adding action listener? Panel field is final, it MUST be initialized in your constructor anyways.
    • weston
      weston over 9 years
      @mkrakhin got a reference for that? I can't see this causing a leak. And even if it does, why would in constructor or in another method make a difference?
    • mkrakhin
      mkrakhin over 9 years
      Because final fields freezing occurs only at the end of instance construction.
    • Giulio Franco
      Giulio Franco over 9 years
      The code is safe in this case, at least as long as the Button isn't displayed (thus becoming capable of being clicked) during the constructor (which should not happen anyway, since Swing isn't thread-safe)
    • Fabiano
      Fabiano over 9 years
      @SimY4, i can. I just got curious because i started to get compilation errors in maven which was not shown in Eclipse, and my code used to work (and still works) using anonymous classes. Thanks
  • Fabiano
    Fabiano over 9 years
    @Holger, thanks for your answer, now it's clear for me that lambdas have a different behavior compared to anonymous classes. I tought the compiler just 'replaced' the lambda for the respective anonymous class under the hood and the behavior should be the same.
  • a better oliver
    a better oliver over 9 years
    My bad. You're right. I'd argue that because of the way lamdbas work under the hood a referenced field (like panel) is in fact accessed at the time the lamdba is created.
  • Holger
    Holger over 9 years
    @zeroflagL: that’s implementation dependent, the decision of the compiler, to be more exactly. Test with current javac revealed that it’s not accessing panel at creation time but reading it like any non-final field. But other compilers may do it differently.
  • Tony BenBrahim
    Tony BenBrahim almost 8 years
    Somehow this worked for me in Eclipse, and did not fail until I used Maven to compile