13

I have the following code which compiles successfully:

import java.lang.String;
import java.util.List;
import java.util.Arrays;

interface Supplier<R> {
    Foo<R> get();
}

interface Foo<R> {
    public R getBar();
    public void init();  
}

public class Main {

    static private <V> void doSomething(final Supplier<? extends List<? extends V>> supplier) {
    // do something
    }

    static public void main(String[] args) {
        doSomething(new Supplier<List<Object>>(){
           @Override
           public Foo<List<Object>> get() {
               return new Foo<List<Object>>(){
                   @Override
                   public List<Object> getBar() {
                       return null;
                   }
                   @Override
                   public void init() {
                      // initialisation
                   }
               };
            }
       });
    }
}

However, if I convert the Supplier to the following lambda expression the code does not compile anymore:

doSomething(() -> new Foo<List<Object>>(){
    @Override
    public List<Object> getBar() {
        return null;
    }
});

The compiler error is:

Main.java:22: error: method doSomething in class Main cannot be applied to given types;
    doSomething(() -> new Foo<List<Object>>(){
    ^
  required: Supplier<? extends List<? extends V>>
  found: ()->new Fo[...]; } }
  reason: cannot infer type-variable(s) V
    (argument mismatch; bad return type in lambda expression
      <anonymous Foo<List<Object>>> cannot be converted to Foo<List<? extends V>>)
  where V is a type-variable:
    V extends Object declared in method <V>doSomething(Supplier<? extends List<? extends V>>)

If I change the declaration of the supplier to Supplier<? extends List<V>>, both variants compile successfully.

I compile the code with a Java 8 compiler.

Why does the code with the lambda not compile, although it is equivalent to the non-lambda version? Is this a known/intended limitation of Java or is it a bug?

Bruno Cadonna
  • 1,348
  • 7
  • 11
  • In Java 10 it seems to be compiling with diamond operator `() -> new Foo<>() {...}` (I am not sure if it compiles on Java 8, or if it does what you want). – Pshemo Sep 21 '18 at 14:25
  • In Java 8, it does not compile with the diamond operator. The compiler gives me the following error: `cannot infer type arguments for Foo` – Bruno Cadonna Sep 21 '18 at 14:55

3 Answers3

4

If I use:

doSomething(() -> () -> null);

It just works fine and all types are correctly inferred by the compiler.

If I try yo do:

doSomething(() -> () -> 1);

Compilation fails, and this is correct, because the doSomething method expects a Supplier<? extends List<? extends V>> argument and () -> () -> 1 isn't.

And if I do:

doSomething(() -> () -> Arrays.asList(1, 2, 3));

It works as expected.

So there's no need to cast anything here, just using lambdas and letting the compiler do its work is fine.


EDIT:

And if I do this:

doSomething(() -> new Foo<List<? extends Object>>() {
    @Override
    public List<? extends Object> getBar() {
        return null;
    }
});

It compiles without error.

So bottom line, the problem is that the compiler thinks that List<Object> is not the same as a List<? extends Object>, and when you're using lambda expressions, it just complains about it (wrongly). It doesn't complain with anonymous inner classes, though, so it all indicates this is a bug.

fps
  • 33,623
  • 8
  • 55
  • 110
  • Apparently, I chose my minimal example too minimal. In the real code that is affected by this problem, the `Foo` interface has more than one method. Thus, you cannot use the second lambda. I am sorry for the misleading example. I adapted the minimal example to match better the code it comes from. Anyways, this behavior is even stranger. The compiler has no problems to infer the type variables with two consecutive lambdas but fails with one lambda. – Bruno Cadonna Sep 21 '18 at 15:35
  • 2
    @Federico of course those thing are "the same" and not, at the same time. One is a Producer only, so may be the compiler somehow does something around that... Still, looks like a bug, since it compiles in java-10 and above with `<>` – Eugene Sep 21 '18 at 16:22
  • 1
    @Eugene [Here's the article](https://beginnersbook.com/2018/05/java-9-anonymous-inner-classes-and-diamond-operator/). There they explain that diamond has been improved for Java 9 to support instantiation of anonymous inner classes. So I think that diamond improvements have nothing to do with this. This is just a bug in Java 8 regarding type inference with lambdas. – fps Sep 21 '18 at 16:51
  • 1
    @FedericoPeraltaSchaffner now if only we could find the relevsnt bug... – Eugene Sep 21 '18 at 17:06
  • @Eugene Yeah, migrating this bug to a minimum example is a challenge, besides maybe the bug is already reported... But it has to do specifically with the `? extends V` part in `Supplier extends List extends V>>` of the `doSomething` method, i.e. bounded type parameters VS lambda expressions. – fps Sep 21 '18 at 17:25
  • OK, now that we somehow agree, that this is strange behavior, the following question arises. Why does this limitation in the implementation exists? Is it a bug or is it due to performance or something else? – Bruno Cadonna Sep 22 '18 at 10:44
  • 1
    @Bruno I'd say it's just a bug. – fps Sep 23 '18 at 04:15
2

In this particular situation an explicit cast can help:

doSomething((Supplier<List<Object>>) () -> new Foo<List<Object>>() {
                       ^
    @Override
    public List<Object> getBar() {
        return null;
    }
});

Or, even simpler:

doSomething((Supplier<List<Object>>) () -> (Foo<List<Object>>) () -> null);

doSomething(() -> () -> null);

Sometimes, Java compiler can not infer the type that matches your intent, therefore you can specify it explicitly. One more example without casting (take a look at the left-hand side of a declaration):

Supplier<List<Object>> supplier = () -> new Foo<List<Object>>() {
    @Override
    public List<Object> getBar() {
        return null;
    }
};

doSomething(supplier);

At the same time, when you write:

static <V> void doSomething(final Supplier<? extends List<? extends V>> supplier) {
}

doSomething(() -> new Foo<List<Object>>() {
    @Override
    public List<Object> getBar() {
        return null;
    }
});

the expected return type in the lambda expression is:

Foo<List<? extends V>>

which is not the same as the actual:

Foo<List<Object>>

The compiler tells you about that in the output:

reason: cannot infer type-variable(s) V
    argument mismatch; bad return type in lambda expression
        <anonymous Foo<List<Object>>> cannot be converted to Foo<List<? extends V>>
Oleksandr Pyrohov
  • 14,685
  • 6
  • 61
  • 90
  • 1
    Your solution works. However, I do not understand why an explicit cast is needed here. The compiler has all information to infer the type variables. Furthermore, it would be interesting to understand in which cases an explicit cast is needed. Or is it just a matter of trial and error? – Bruno Cadonna Sep 21 '18 at 15:03
2

The issue is caused by using ? extends V with List in doSomething method definition, and but when invoke method, you are using new Foo<List<Object>> directly.

In there, List<? extends Object> is not equal to List<Object>, Since ? extends Object is covariance with type Object, for example, you can not put Object type element into List<? extends Object>, because compiler can't infer what's the type should be ? extends Object.

So for your example, you can try to fix it by directly using new Foo<List<? extends Object>>(), maybe like:

    doSomething(() -> new Foo<List<? extends Object>>() {
        @Override
        public List<Object> getBar() {
            return null;
        }
    });

And for why use the anonymous class can work in here, try to decompile the anonymous class,

class Main$1$1 implements Foo<java.util.List<java.lang.Object>> 
...
final class Main$1 implements SupplierT<java.util.List<java.lang.Object>> {
...

as you can see, it's overriding the ? extends V as Object type.

but for lambda, it will not generate the corresponding anonymous class, for example:

class Main$1 implements SupplierT<java.util.List<java.lang.Object>>

the above anonymous class will not generate, and lambda will use invokedynamic instruction directly call, as:

   0: invokedynamic #5,  0              // InvokeDynamic #0:get:()LSupplierT;

So it's still try to infer ? extends V, it will cause compile fails.

Reference:

https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html

or this example:

Is List a subclass of List? Why are Java generics not implicitly polymorphic?

chengpohi
  • 14,064
  • 1
  • 24
  • 42
  • But why does it work without lambda then? If this is a matter of incompatible generics, it should also not work there, right? – Bruno Cadonna Sep 21 '18 at 15:47
  • 1
    @Bruno, good call, the key point is: **lambda** will not generate the anonymous class, it can not infer type. see more in update, hope it's clear – chengpohi Sep 21 '18 at 16:22
  • 1
    `doSomething(() -> new Foo>() { ... });` also works fine. – Oleksandr Pyrohov Sep 21 '18 at 17:05
  • @Oleksandr, I think this is because `?` wildcard explicitly extends `Object`, so if you change `? extends Object` to other type like `? extends BigInteger`, it should fail. – chengpohi Sep 21 '18 at 17:45
  • @chengpohi read this answer twice, I doubt it answers the question and it is misleading, at best – Eugene Sep 21 '18 at 20:10
  • @chengpohi, since the anonymous class works, it means that it is a valid program. Apparently, lambdas can only replace anonymous classes in a subset of valid programs. Since I could not find any rule about when a lambda can replace an anonymous class, it has nothing to do with the language but rather with its implementation. Now it would be interesting to know why this limitation in the implementation exists. Is it a bug or has it to do with performance or something else? – Bruno Cadonna Sep 22 '18 at 10:42