javagenericsjava-7

Return intersection of generic types


I would like to have a function return an object that is guaranteed to implement two interfaces. The exact object is not necessarily known at compilation time. My code looks something like:

class HelloWorld {

  public interface A {}
  public interface B {}

  public static class C implements A, B {}
  public static class D implements A, B {}

  public static <T extends A & B> void g(T t) {}

  public static <T extends A & B> T f(boolean b) {
    if (b)
      return new C(); // Doesn't compile
    return new D(); // Doesn't compile
  }

  public static void main(String []args){
    g(f(true));
    g(f(false));
    <what_should_I_write_here> x = f(<user_inputted_boolean>); 
  }
}

When trying to compile I get the following error:

HelloWorld.java:13: error: incompatible types: C cannot be converted to T
return new C();
^
where T is a type-variable:
T extends A,B declared in method f(boolean)
HelloWorld.java:14: error: incompatible types: D cannot be converted to T
return new D();
^
where T is a type-variable:
T extends A,B declared in method f(boolean)

This doesn't work because you can't return two different types from a function and C and D are different types.

Is there any way to get the above code to compile?


Solution

  • You have a fundamental misunderstanding regarding type variables. When you declare a method like

    public static <T extends A & B> T f(boolean b) { … }
    

    you declaring a type variable T to which the caller may assign an actual type (or a type variable of the caller’s context). E.g., the caller may do the following:

    class SomethingCompletelyUnknownToF implements A,B {}
    
    SomethingCompletelyUnknownToF var = f(trueOrFalse);
    

    which the compiler will accept, because the type SomethingCompletelyUnknownToF used for T by the caller of f fulfills the constraint that the type must implement A or B. Of course, this will fail at runtime, because neither C nor D are assignable to SomethingCompletelyUnknownToF. In fact, it is impossible for f to fulfill this expectation of returning a specific type that it doesn’t even know.

    To summarize, a type variable is not a variable that the method may assign a type, a type variable is a placeholder for a type chosen by the caller.

    So the method signature

    public static <T extends A & B> void g(T t) { … }
    

    makes more sense as whatever actual type the caller chooses for T, it will fulfill the method’s expectation of implementing A and B when being passed as parameter. Of course, g can not expect it to be either, D or C, as it might be a completely unknown type implementing A and B.

    That said, there is no way in Java, to express that a return type extends two types (other than declaring a concrete type extending both). In case of RandomAccess, there is no need to bother anyway, as it has no consequences. Note that the JRE classes also never declare when a returned List is guaranteed to implement this marker interface. This is accommodated by never expecting it as parameter type either. Similarly, Serializable is never declared as return or parameter type anywhere.