Why does the compiler not enforce the return type value for a generic that extends an interface?

Viewed 68
public class Test {
  public static void main(String... args) {
    ClassB clazzB1 = getClassA(); // compile error
    ClassB clazzB2 = getInterfaceC1(); // compile error
    ClassB clazzB3 = getInterfaceC2(); // no compile error
  }
  
  public static <T extends ClassA> T getClassA() {
    return (T) new ClassA() {};
  }
  
  public static InterfaceC getInterfaceC1() {
    return new InterfaceC() {};
  }

  public static <T extends InterfaceC> T getInterfaceC2() {
    return (T) new InterfaceC() {};
  }

  private static abstract class ClassA {
    
  }
  
  private static class ClassB {
    
  }
}
public interface InterfaceC {
}

Why does getInterfaceC2() not give a compile error when assigned to clazzB3? It seems like it should and is something that I would like to have happen.

1 Answers

You can always do:

static class InterestingType extends ClassB implements InterfaceC {

}

And then have :

public static <T extends InterfaceC> T getInterfaceC2() {
    return (T)new InterestingType();
}

As such calling:

ClassB clazzB3 = getInterfaceC2();

would work. In your case it is obvious to you that it will break, but the compiler can't prove that (easily). As such, your example is allowed, but will fail at runtime.

EDIT

The compiler looks at this:

ClassB clazzB1 = getClassA();

and sees that getClassA() returns a T that is defined as T extends ClassA, where ClassA is a class. It also sees that this T is assigned to ClassB, where ClassB is a class also. So it has to create a certain type that will conform to both ClassA and ClassB, something like a theoretical :

T extends ClassA & ClassB

but that is an impossible type, since no one can do ... extends ClassA, ClassB (this is simply not allowed in java). So the compiler fails. As a matter of fact if you do :

javac  --debug=verboseResolution=all Test

you will see in the output, something like:

error: incompatible types: inference variable T has incompatible upper bounds ClassB,ClassA

On the other hand, when the compiler looks at this : ClassB clazzB3 = getInterfaceC2();, it sees that getInterfaceC2() returns:

T extends InterfaceC

and the result is assigned to ClassB, so it ends up with may be this type:

T extends ClassB & InterfaceC

which is perfectly valid. There can be such a type. I've shown you that you can create one as InterestingType, but as Holger pointed out, you could pass such a theoretical type yourself, via:

ClassB clazzB3 = Test.<InterestingType>getInterfaceC2();

this is called specifying a "type witness".

Related