How does the Java compiler choose the runtime type for a parameterized type with multiple bounds?

  • A+
Category:Languages

I would like to understand better what happens when the Java compiler encounters a call to a method like the one below.

<T extends AutoCloseable & Cloneable> void printType(T... args) {     System.out.println(args.getClass().getComponentType().getSimpleName()); }  // printType() prints "AutoCloseable" 

It is clear to me that there is no type <T extends AutoCloseable & Cloneable> at runtime, so the compiler makes the least wrong thing it can do and creates an array with the type of one of the two bounding interfaces, discarding the other one.

Anyway, if the order of the interfaces is switched, the result is still the same.

<T extends Cloneable & AutoCloseable> void printType(T... args) {     System.out.println(args.getClass().getComponentType().getSimpleName()); }  // printType() prints "AutoCloseable" 

This led me to do some more investigation and see what happens when the interfaces change. It seems to me that the compiler uses some kind of strict order rule to decide which interface is the most important, and the order the interfaces appear in code plays no role.

<T extends AutoCloseable & Runnable>                             // "AutoCloseable" 
<T extends Runnable & AutoCloseable>                             // "AutoCloseable" 
<T extends AutoCloseable & Serializable>                         // "Serializable" 
<T extends Serializable & AutoCloseable>                         // "Serializable" 
<T extends SafeVarargs & Serializable>                           // "SafeVarargs" 
<T extends Serializable & SafeVarargs>                           // "SafeVarargs" 
<T extends Channel & SafeVarargs>                                // "Channel" 
<T extends SafeVarargs & Channel>                                // "Channel" 
<T extends AutoCloseable & Channel & Cloneable & SafeVarargs>    // "Channel" 

Question: How does the Java compiler determine the component type of a varargs array of a parameterized type when there are multiple bounds?

I'm not even sure if the JLS says anything about this, and none of the information I found by googling covers this particular topic.


Typically, when the compiler encounters a call to a parameterised method, it can infers the type (JSL 18.5.2) and can create a correctly typed vararg array in the caller.

The rules are mostly technical ways of saying "find all possible input types and check them" (cases like void, ternary operator, or lambda). The rest is common sense, such as using the nearest common base class. Example:

public class Test {    private static class A implements AutoCloseable, Runnable {       @Override public void close () throws Exception {}       @Override public void run () {} }    private static class B implements AutoCloseable, Runnable {       @Override public void close () throws Exception {}       @Override public void run () {} }    private static class C extends B {}     private static <T extends AutoCloseable & Runnable> void printType( T... args ) {       System.out.println( args.getClass().getComponentType().getSimpleName() );    }     public static void main( String[] args ) {       printType( new A() );          // A[] created here       printType( new B(), new B() ); // B[] created here       printType( new B(), new C() ); // B[] which is the common base class       printType( new A(), new B() ); // AutoCloseable[] - well...       printType();                   // AutoCloseable[] - same as above    } } 

JSL 18.2 dictates how to process the constrains for type inference, such as AutoCloseable & Channel is reduced to just Channel. But the rules does not help answer this question.

Getting AutoCloseable[] from the call may look weird, of course, because we can't do that with Java code. But in reality the actual type doesn't matter. For coding purpose, args is T[], where T is a "virtual type" that is both A and B (JSL 4.9).

JLS and the compiler just need to make sure its usages meet all constrains, and they'd know the logic is sound and there will be no type error (this is how Java generic is designed). Of course the compiler still need to make a real array, and for the purpose it creates a "generic array". Thus the warning "unchecked generic array creation" (JLS 15.12.4.2).

In other words, as long as you pass in only AutoCloseable & Runnable, and calls only Object, AutoCloseable, and Runnable methods in printType, the actual array type does not matter. In fact, printType's bytecodes would be the same, regardless of what kind of array is passed in.

Note that this is not type erasure (JSL 4.6), since there is no real type to erase to. JLS does not specify any real type to use in this case, and we have no control over it.

Even if we add args[0].run() to printType, printType() still new AutoCloseable[], not Runnable[]. printType's bytecode will check that args[0] is Runnable before calling run(), and never query its real class.

Since printType doesn't care the type, the result of getComponentType() doesn't matter. If you want to get the interfaces, try getGenericInterfaces() which returns an array.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: