JDK-8153577 : Type Inference Fails for Generic Return Type
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 8u73,9
  • Priority: P3
  • Status: Closed
  • Resolution: Not an Issue
  • Submitted: 2016-02-26
  • Updated: 2016-09-13
  • Resolved: 2016-09-13
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :


ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
Java selects the wrong method during type inference with generic-type returning objects.

See source code below.  The "get" method in class "A" returns a type that extends "A".  The main method then tries to assign the return value to a Collection, an interface.  This compiles but will obviously throw a ClassCastException at run time.  Any interface can be chosen, List, Map, anything.  It will still compile.  Basically the type inference mechanism is not checking if the return type can actually be assigned when an interface is concerned.

This would have caused a compile-time error in JDK 1.6 and below:

C:\Users\gendrok\Documents\NetBeansProjects\temp\src\Test.java:18: type parameters of <T>T cannot be determined; no unique maximal instance exists for type variable T with upper bounds java.util.Collection,Test.A
        Collection c1 = new A().get();

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the source code and run it.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The source code should be prevented from being compiled.
ACTUAL -
The source code is compiled successfully.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.lang.ClassCastException: Test$A cannot be cast to java.util.Collection
	at Test.main(Test.java)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Test {

    public static class A {
        public A() {
        }
        public <T extends A> T get() {
            return (T)this;
        }
    }
    
    public static void main(String[] args) {
        Collection c1 = new A().get();
        List l1 = new A().get();
        Set s1 = new A().get();
        Map m1 = new A().get();
    }

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Do not use generic-type returning methods.


Comments
This is not an issue - if the target type is an interface, the compiler sees two bounds: T <: Collection T <: A So, as per JLS 18.4: "Otherwise, where ��i has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk) (��5.1.10). " Hence, T = glb(Collection, A) = A & Collection Before JDK 7, the compiler had a bug so that glb was not always applied as per JLS - hence the spurious errors (see JDK-6785112).
13-09-2016

It is actually possible for there to be some type T that extends A that also implements any interface. Consider Collection for example. We have class A { <T extends A> T get() { ... } } Collection c = new A().get(); Suppose there is another class as follows: class B extends A implements Collection { ... } If get() were to return an instance of B, that would satisfy the type constraints, and the assignment to the Collection variable c would succeed. I'm not sure why this failed in Java 6 (I didn't try it) but it might be because that version was buggy. After all, it would fail to compile a piece of code that could reasonably succeed. This might also have something to do with the addition of target type inference in Java 8. The Java 6 compiler didn't do that, so it might simply have given up earlier. Reassigning to the compiler team for disposition.
13-09-2016

ILW=MMM=p3
06-04-2016

Test Result: ######### OS : Windows 7 64-bit JDK: 7uXX : Fail 8uXX : Fail 9ea : Fail output: ###### c:\ji\core-libs>java -version java version "9-ea" Java(TM) SE Runtime Environment (build 9-ea+111) Java HotSpot(TM) 64-Bit Server VM (build 9-ea+111, mixed mode) c:\ji\core-libs>javac Test.java Note: Test.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. c:\ji\core-libs>java Test Inner Constructor Exception in thread "main" java.lang.ClassCastException: Test$A (in module: Unnamed Module) cannot be cast to java.util.Collection (in module: java.base) at Test.main(Test.java:18) c:\ji\core-libs>
06-04-2016