Bruno Cadonna
Bruno Cadonna

Reputation: 1418

Using lambda impedes inference of type variable

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?

Upvotes: 13

Views: 284

Answers (3)

Oleksandr Pyrohov
Oleksandr Pyrohov

Reputation: 16216

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>>

Upvotes: 2

fps
fps

Reputation: 34460

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.

Upvotes: 4

chengpohi
chengpohi

Reputation: 14217

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?

Upvotes: 2

Related Questions