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

2

8

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.

Mausoleum answered 10/2, 2017 at 20:49 Comment(8)
For the first example JDK 1.8.0_65 gives compilation errorVivienviviene
@Vivienviviene you are right. JDK 8 actually requires the this keyword. I'll edit the question now. However, in the anonymous class declaration, you cannot use this because then that would of course refer to the instance you are creating.Mausoleum
@aauezza and it also requires that suffix should be initializedVivienviviene
In other words, in JDK 8 it will be compiled only if you will remove final modifier from suffix fieldVivienviviene
@Andremoniy, that is not accurate. I am looking at a forward reference of a final field as I type this. It compiles just fine. In fact, shmosel nailed it with those referenced question: docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.3.3 Here is the spec to verify that this compilation is quite possible.Mausoleum
It may be possible, but I can not compile it under JDK 1.8.0_65Vivienviviene
... You are right! I just tried the same code from a command line and it won't compile! How in the heck then does it not only appear to compile with EJC, but the also runs just fine in Eclipse? Does the EJC actually create valid bytecode that can run?Mausoleum
I'm not spec in ECJ, sorryVivienviviene
L
7

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:

Leveller answered 10/2, 2017 at 21:24 Comment(3)
Interesting. I have no problem at all with the lambda expression in Eclipse Mars when I use this.suffix.Mausoleum
The comments on my question above show that this is because of the ECJ. So at the end of the day, I'm sure this here is the answer, and it means I should probably change my code to perhaps use a private accessor. Thanks for the help!Mausoleum
The stream example was bugs.eclipse.org/508834 which has already been fixed for 4.7M5 and back ported for the up-coming 4.6.3 release.Syndactyl
L
0

I'm not sure if this dissolve this problem, but in fact final field members must be initialized by using the constructor or the assignment operator before you can compile the code:

class A
{
     private final String suffix = null;
}
Launceston answered 3/5, 2018 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.