Search

Categories

Links

Referers

Inference and Compound Types

Aug 20 2004, 07:48:42 PM PDT »Java»Language Comments [3]
Keith Lea was kind enough to provide me with this example:
    List<Class<?>> classes =
        Arrays.asList(String.class, Boolean.class);

This small example does not compile, but Keith had figured out that changing it helped:

    List<? extends Class<?>> classes =
        Arrays.asList(String.class, Boolean.class);

Of cause, Keith would never have written the first example if he had read my previous entry on "Why is the Capture of a Wildcard Incompatible with Itself?".  However, the compiler still complains, though not as loudly:

warning: [unchecked] unchecked generic array creation of type java.lang.Class<
    ? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<
        ? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<?>>>[]
for varargs parameter
        Arrays.asList(String.class, Boolean.class);
                     ^
1 warning

As any good programmer, Keith was concerned about this warning.  Anyone should strive to remove warnings from their programs.

So what is all that goo in the warning?  Why would the compiler claim that this is unchecked?

First, notice that the compiler wants to create an array of type:

java.lang.Class<
     ? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<
         ? extends java.lang.Object&java.io.Serializable&java.lang.Comparable<?>>>[]

We don't need to see java.lang and java.io.  Also, Object is redundant in these types so let's clean it up:

Class<? extends Serializable&Comparable<? extends Serializable&Comparable<?>>>[]

Now that's almost easy to read.  But where does this strange type come from?  The answer is inference.  Inference is new to Java but has been around for a long time in other programming languages such as Standard ML.  Take a closer look at the java.util.Arrays.asList method declaration:

public static <T>List<T> asList(T... a);

This method creates a java.util.List view of an array.  The method uses some of the new features of Java: generic methods and varargs.  Varargs is the easy one to understand: I you write ... after the last type in a method declaration, the compiler will accept a variable number of arguments (hence the name varargs).  The compiler will then create an array of these arguments an pass that on to the method as if it was declared as:

public static <T> List<T> asList(T[] a);

This explains why the compiler is creating an array.  But what about the element type of that array?  Notice the <T> before the actual method declaration.  This is a type variable.  Since we want to be able to create a List view of any array, we are asking the compiler to figure out what the actual type is.  That process is called inference.  Inference is mostly done without looking at the expected result type (otherwise the inference would be really complicated when the expected result type is itself inferred).  All this means that the compiler must find a type T such that:

Class<String> <: T and Class<Boolean> <: T

Also we want to be as precise as possible.  One candidate for T is Class<?> but that is somewhat imprecise.  String and Boolean has more in common than that.  E.g., they both implement the interfaces Serializable and Comparable.  So a more precise candidate would be:<

Class<? extends Serializable & Comparable>

The & above is called a compound type and should be considered as an anonymous class that implements both interfaces.

But wait a minute...  Isn't Comparable a generic interface itself?  It is and we are looking for what Boolean and String have in common:

Boolean implements Comparable<Boolean>
String implements Comparable<String>

Let us improve our candidate for T:

Class<? extends Serializable & Comparable<? extends Serializable & Comparable>>

Still no good.  We need parameters for the Comparable interface.  At this point the compiler gives up and says "I don't know":

Class<? extends Serializable & Comparable<? extends Serializable & Comparable<?>>>

The most precise solution would actually have been:

Class<? extends X> where X extends Serializable & Comparable<? extends X>

But the compiler is not quite clever enough to handle this type yet...

But this inferred type is generic and arrays of generic types are quite troublesome (could be a topic for a new blog entry).  Thus the compiler will give a warning each time an array of a generic type is created.  However, unbounded wildcards are quite benign, so the compiler will not complain about array types such as Class<?>.  Incidentally, this was actually the type Keith was looking for, but the compiler cannot use that information unless told explicitly:

    List<? extends Class<?>> classes =
        Arrays.<Class<?>>asList(String.class, Boolean.class);

Now we give an explicit type parameter for T so the compiler doesn't need to infer any types.  An array of Class<?> quite benign, so the warning disappears.

Post a Comment:
Comments are closed for this entry.
Comments:

How about the following example:
<code>
        List<? extends Class<? extends Number>> classes =
        Arrays.<Class<? extends Number>>asList(Integer.class, Double.class);    
</code>
What's wrong with this?

Posted by Vlad Patryshev on December 07, 2004 at 10:11 PM PST #

Repeating Vlad Patryshev's example:
List<? extends Class<? extends Number>> classes =
        Arrays.<Class<? extends Number>>asList(Integer.class, Double.class);   

Posted by Peter Ahe on December 07, 2004 at 10:32 PM PST #

The problem is the same. The compiler will have to create an array of this type: Class<? extends Number>[] Such an array is not safe.

Posted by Peter von der Ahé on February 21, 2005 at 03:42 PM PST #

Java is a trademark of Sun Microsystems, Inc.
Copyright © 2006,2007 Peter von der Ahé