10

I have the following code:

package test;

import java.util.stream.IntStream;

public class A {
    public static void main(String[] args) {
        IntStream.range(0, 10).mapToObj(n -> new Object() {
            int i = n;
        }).mapToInt(o -> o.i).forEachOrdered(System.out::println);
    }

}

This code works fine when compiled with javac 1.8.0_101, and produces the number 0 to 9 as expected.

But when I use this code in eclipse, it tells me that at o.i:

i cannot be resolved or is not a field

And producing an error when executing this:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    i cannot be resolved or is not a field

    at test.A.main(A.java:9)

Why do I need to use javac to compile this code?
And how do I get eclipse to behave?

Edit:

I did some tests and it works in ecj as long as I don't create the instance in a lambda:

package test;

import java.util.Optional;
import java.util.function.Supplier;

public class B {
    public static void main(String[] args) {

        // This works fine:
        System.out.println(new Object() {
            int j = 5;
        }.j);

        // This also
        System.out.println(trace(new Object() {
            int j = 5;
        }).j);

        // Also no problem
        System.out.println(unwrapAndTrace(Optional.of(new Object() {
            int j = 5;
        })).j);

        // Lambdas work:
        System.out.println(((Supplier & Serializable) () -> new Object()).get()); 

        // This doesn't work.
        System.out.println(invokeAndTrace(() -> new Object() {
            int j = 5;
        }).j);
    }

    public static <T> T trace(T obj) {
        System.out.println(obj);
        return obj;
    }

    public static <T> T invokeAndTrace(Supplier<T> supplier) {
        T result = supplier.get();
        System.out.println(result);
        return result;
    }

    public static <T> T unwrapAndTrace(Optional<T> optional) {
        T result = optional.get();
        System.out.println(result);
        return result;
    }


}
Johannes Kuhn
  • 14,778
  • 4
  • 49
  • 73
  • I know, I could use `Integer`, but that's not the point. And `i` is visible - not private, same package. But if you like, you can of course make `i` public. Doesn't matter. – Johannes Kuhn Jun 23 '18 at 01:59
  • When you create a new Object() class, the variable i isn't part of the base Object class. It isn't a class variable. Create a new class maybe called B with the class variable i as an int in the B class. Then you can assign the n as you pass it in. Not sure why it compiled before however it seems as though the base class Object doesn't have any class variables in it for you to assign to. It would seem that the instance that you are creating of object has int i only for the scope of the braces. Basically int i is a local method variable. – Dale Jun 23 '18 at 03:20
  • 1
    What is version of eclipse?? Kepler and prev - work with java 1.7, so it wouldn't compile. You need Luna or latest (Mars) – Serge Breusov Jun 23 '18 at 04:27
  • Eclipse can compile lambdas, no problem there. If I change `o.i` to `123` then Eclipse will compile it. – Johannes Kuhn Jun 23 '18 at 07:25
  • Maybe default language level is set to 1.6 (aka Java 6) or 1.7 (Java 7), so lambdas are not legal – Jacek Cz Jun 23 '18 at 11:59
  • Nope, lambdas work fine. I'm using `Eclipse IDE for Java Developers Version: Oxygen.3a Release (4.7.3a) Build id: 20180405-1200` - Should be recent enough. – Johannes Kuhn Jun 23 '18 at 12:02
  • 2
    @SergeBreusov, for the records: while Luna indeed introduced support for Java 8, none of the versions you mention are recent in 2018. Indeed, Oxygen.3a is the latest released version -- until next week when Photon will be published. – Stephan Herrmann Jun 23 '18 at 13:47

1 Answers1

8

This is a bug in ecj, recently reported also as Bug 535969.

In a nutshell: to avoid a hard technical problem, the compiler drops the anonymous class during type inference, replacing it with its super class (in specific situations, not always). With this, the result of mapToObj() is seen as Stream<Object> where indeed the anonymous class should be used. Original assessment, that this information loss would be OK (because nobody can mention the anonymous class) is proven wrong by the example in this question.

EDIT: The bug has been fixed via the pre-existing report Bug 477894

Stephan Herrmann
  • 7,963
  • 2
  • 27
  • 38
  • Compiler bug? Well... I was always told "It's not the compilers fault, it's your fault. The compiler is battle tested and it is far more likely that you did something wrong". Yeah, sure. – Johannes Kuhn Jun 23 '18 at 18:24
  • I believe the bottom of my question makes a good test case. BTW, will Photon fix it? – Johannes Kuhn Jun 23 '18 at 18:29
  • It's fixed, just too late for Photon, but don't worry, the next release is just 3 months away. – Stephan Herrmann Jun 24 '18 at 22:05
  • Thanks for the follow up. I'll look forward to do that kind of thing (C# can do that for a while - and with `var` things get even crazier). – Johannes Kuhn Jun 25 '18 at 09:34