Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should JDK 9 not allow Lambda Expression instantiation where final fields are referenced in the overridden method?

I've been working with the new Eclipse Neon and some of my code started to give me errors straight away.
This was strange to me at first, but then I found here that the Neon ECJ(Eclipse Java Compiler) adopts the attitude of the JDK 9 early release compiler.
I do not encounter the same issue that is in that link, but rather another that I will explain here.

Issue with Lambda Expression declarations as fields

Here is a test class that gives me a compilation error in Eclipse Neon, the JDK 9 compiler, and the JDK 8 compiler (Not previous versions of Eclipse though).

public class Weird
{
    private final Function<String, String> addSuffix =
        text -> String.format( "%s.%s", text, this.suffix );

    private final String suffix;

    public Weird( String suffix )
    {
        this.suffix = suffix;
    }
}

Given the code above, the errors at line 4 for suffix are:

╔══════════╦═══════════════════════════════════════════════╗
║ Compiler ║                     Error                     ║
╠══════════╬═══════════════════════════════════════════════╣
║ ECJ      ║ Cannot reference a field before it is defined ║
║ JDK 9    ║ error: illegal forward reference              ║
╚══════════╩═══════════════════════════════════════════════╝

Now see what happens with the same class if I move the suffix field declaration before the the addSuffix declaration.

public class Weird
{
    private final String suffix;

    private final Function<String, String> addSuffix =
        text -> String.format( "%s.%s", text, this.suffix );

    public Weird( String suffix )
    {
        this.suffix = suffix;
    }
}

Given the code above, the errors at line 6 for suffix are:

╔══════════╦════════════════════════════════════════════════════════════╗
║ Compiler ║                           Error                            ║
╠══════════╬════════════════════════════════════════════════════════════╣
║ ECJ      ║ The blank final field suffix may not have been initialized ║
║ JDK 9    ║ error: variable suffix might not have been initialized     ║
╚══════════╩════════════════════════════════════════════════════════════╝

          Should Java 9 behave this way?

This worked perfectly fine in JDK 8; seems like a strange thing to suddenly enforce. Especially considering that there are already compile-time checks in place to ensure final fields are instantiated correctly.
Therefore, by the time the function addSuffix is ever accessed, there would need to be a value in place for suffix (null or otherwise is another story).

I'll also note that I've tried the following code, which compiles fine with JDK9 and ECJ:

public class Weird
{
    private final String suffix;

    private final Function<String, String> addSuffix =
        new Function<String, String>()
        {
            @Override
            public String apply( String text )
            {
                return String.format( "%s.%s", text, suffix );
            }
        };

    public Weird( String suffix )
    {
        this.suffix = suffix;
    }
}

It appears that in JDK 9, there is a big difference between anonymous class declarations and Lambda expressions. So in this case where we get a compiler error, at least the ECJ is accurately in mimicking the JDK 9 compiler.


Issue with Stream & Generics

This one really surprised me, because I cannot think of why the compiler would interpret this any differently than what the Generic in the code indicates:

public class Weird
{
    public void makePDFnames( String [] names )
    {
        final List<String> messages = Arrays.asList( "nice_beard", "bro_ski" );

        final List<String> components = messages.stream()
            .flatMap( s -> Stream.of( s.split( "_" ) ) )
            .collect( Collectors.toList() );
    }
}

This code gives these errors:

╔══════════╦═══════════════════════════════════════════════════════════════════════╗
║ Compiler ║                                 Error                                 ║
╠══════════╬═══════════════════════════════════════════════════════════════════════╣
║ ECJ      ║ Type mismatch: cannot convert from List<Serializable> to List<String> ║
║ JDK 9    ║ NO ERROR. Compiles fine!                                              ║
╚══════════╩═══════════════════════════════════════════════════════════════════════╝

In light of this information, it appears in this case, the ECJ is at fault for not properly mimicking the JDK 9 and is just an Eclipse bug.

like image 339
aaiezza Avatar asked Feb 10 '17 20:02

aaiezza


People also ask

What are the two conditions required for using a lambda function in a stream?

In order to match a lambda to a single method interface, also called a "functional interface", several conditions need to be met: The functional interface has to have exactly one unimplemented method, and that method (naturally) has to be abstract.

How does lambda expression work in Java?

A lambda expression is a short block of code which takes in parameters and returns a value. Lambda expressions are similar to methods, but they do not need a name and they can be implemented right in the body of a method.

Is lambda expression an object in Java?

Yes, any lambda expression is an object in Java. It is an instance of a functional interface. We have assigned a lambda expression to any variable and pass it like any other object.

How do you convert this method into a lambda expression?

To convert an anonymous method to a lambda expressionMove to the anonymous method you want to convert. From the Refactor menu of the VisualAid choose To Lambda. Telerik® JustCode™ will replace the anonymous method with a lambda expression.


1 Answers

Firstly, if you read that bug report you linked, ECJ doesn't "adopt the attitude" of the JDK 9 compiler. Both compilers had a bug, one of which is fixed in JDK 9, the other in Neon.

The lambda field fails to compile for me in both Eclipse Mars and Java 8. And it makes perfect sense, since it potentially violates the immutability guarantee of final fields. What is surprising is that the anonymous subclass compiles successfully. Consider this example:

public static class Weird
{
    private final String suffix;

    private final Function<String, String> addSuffix = new Function<String, String>() {
        @Override
        public String apply(String text) {
            return String.format( "%s.%s", text, suffix );
        }
    };

    public final String s = addSuffix.apply("1");

    public static void main(String[] args) {
        System.out.println(new Weird("p").s);
        // 1.null (!!)
    }
}

I suspect the above may be a bug in both compilers.

As for the stream error, the same code compiles in Java 8 as well. So it's likely just another ECJ bug, nothing to do with Java 9.

Possibly related:

  • Java: Why no warning when referencing a field before it is defined?
  • Reference to the final field from lambda expression
like image 68
shmosel Avatar answered Oct 08 '22 15:10

shmosel