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.

Paul R
  • 208,748
  • 37
  • 389
  • 560
aaiezza
  • 1,297
  • 3
  • 11
  • 21
  • 2
    For the first example JDK 1.8.0_65 gives compilation error – Andremoniy Feb 10 '17 at 21:33
  • @Andremoniy 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. – aaiezza Feb 10 '17 at 21:39
  • 1
    @aauezza and it also requires that `suffix` should be initialized – Andremoniy Feb 10 '17 at 21:43
  • In other words, in JDK 8 it will be compiled only if you will remove `final` modifier from `suffix` field – Andremoniy Feb 10 '17 at 21:44
  • @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: https://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. – aaiezza Feb 10 '17 at 21:46
  • It may be possible, but I can not compile it under JDK 1.8.0_65 – Andremoniy Feb 10 '17 at 21:49
  • ... 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? – aaiezza Feb 10 '17 at 21:51
  • 1
    I'm not spec in ECJ, sorry – Andremoniy Feb 10 '17 at 21:52

2 Answers2

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:

Community
  • 1
  • 1
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • Interesting. I have no problem at all with the lambda expression in Eclipse Mars when I use `this.suffix`. – aaiezza Feb 10 '17 at 21:27
  • 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! – aaiezza Feb 10 '17 at 22:09
  • 3
    The stream example *was* https://bugs.eclipse.org/508834 which has already been fixed for 4.7M5 and back ported for the up-coming 4.6.3 release. – Stephan Herrmann Feb 11 '17 at 14:31
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;
}
Martin Wantke
  • 4,287
  • 33
  • 21