Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java type inference: reference is ambiguous in Java 8, but not Java 7

Lets say we have 2 classes. An empty class Base, and a subclass of this class Derived.

public class Base {}  public class Derived extends Base {} 

Then we have a few methods in another class:

import java.util.Collection  public class Consumer {      public void test() {         set(new Derived(), new Consumer().get());     }      public <T extends Base> T get() {         return (T) new Derived();     }      public void set(Base i, Derived b) {         System.out.println("base");     }      public void set(Derived d, Collection<? extends Consumer> o) {         System.out.println("object");     }  } 

This compiles and runs successfully in Java 7, but does not compile in Java 8. The error:

Error:(8, 9) java: reference to set is ambiguous   both method set(Base,Derived) in Consumer and    method set(Derived,java.util.Collection) in Consumer match 

Why does work in Java 7, but not Java 8? How could <T extends Base> ever match Collection?

like image 796
Rohit Kumar Avatar asked Feb 11 '15 23:02

Rohit Kumar


People also ask

Does Java 7 support type inference?

Java SE 7 supports limited type inference for generic instance creation; you can only use type inference if the parameterized type of the constructor is obvious from the context.

What is type inference in Java 8?

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.

What is ambiguous in Java?

The ambiguities are those issues that are not defined clearly in the Java language specification. The different results produced by different compilers on several example programs support our observations.


1 Answers

The problem is that the type inference has been improved. You have a method like

public <T extends Base> T get() {     return (T) new Derived(); } 

which basically says, “the caller can decide what subclass of Base I return”, which is obvious nonsense. Every compiler should give you an unchecked warning about your type cast (T) here.

Now you have a method call:

set(new Derived(), new Consumer().get()); 

Recall that your method Consumer.get() says “the caller can decide what I return”. So it’s perfectly correct to assume that there could be a type which extends Base and implement Collection at the same time. So the compiler says “I don’t know whether to call set(Base i, Derived b) or set(Derived d, Collection<? extends Consumer> o)”.

You can “fix” it by calling set(new Derived(), new Consumer().<Derived>get()); but to illustrate the madness of your method, note that you can also change it to

public <X extends Base&Collection<Consumer>> void test() {     set(new Derived(), new Consumer().<X>get()); } 

which will now call set(Derived d, Collection<? extends Consumer> o) without any compiler warning. The actual unsafe operation happened inside the get method.

So the correct fix would be to remove the type parameter from the get method and declare what it really returns, Derived.


By the way, what irritates me, is your claim that this code could be compiled under Java 7. Its limited type inference with nested method calls leads to treating the get method in a nested invocation context like returning Base which can’t be passed to a method expecting a Derived. As a consequence, trying to compile this code using a conforming Java 7 compiler will fail as well, but for different reasons.

like image 122
Holger Avatar answered Oct 02 '22 09:10

Holger