THE Java™ Programming Language, Fourth Edition

(Jeff_L) #1

relies on type inference to establish the type of T:


String s1 = "Hello";
String s2 = passThrough(s1);


In this case, the argument type is inferred to be String, which is the static type of the variable s1. This
implies that the return type is also String. This is compatible with the assignment to s2 and so the
invocation is valid with an inferred type of String.


The following invocations of passThrough are also valid:


String s1 = "Hello";
Object o1 = passThrough(s1); // T => String
Object o2 = passThrough((Object) s1); // T => Object


In the first case the argument type is String and the return type is expected to be Object. The inferred
type for T is again String, implying that the return type is also String. This is compatible with
assignment to the Object variable o1, so the invocation is valid, with an inferred type of String. In the
second case the static type of the argument is Object (due to the cast) and so the inferred type is also
Object. This makes the return type Object, so again we have a compatible assignment and so a valid
invocation. In general, type inference has to find the most specific type in the set of types that satisfy the
constraints imposed by the type variablesa non-trivial exercise.


Type inference is based on the static type of the argument expressions that are being passed, not their dynamic
types, so the following won't compile:


String s1 = "Hello";
s1 = passThrough((Object) s1); // INVALID: won't compile


The static argument type of Object requires that T, as the return type of the method, be Object (or a
supertype if Object had one) and the String type of s1 requires that T be assignable to String. But
there is no such typeyou cannot assign an Object to a String reference. Of course, you can inform the
compiler that you know the returned object actually is a String by inserting an additional cast:


s1 = (String) passThrough((Object) s1);


The actual type inference process and the rules controlling it are extremely complex, but for the most part you
don't need to care what the actual inferred type is as long as the code compiles. For example, if a generic
method took two parameters of type T and you invoked it passing a List and a
List, then the inferred type could be List<?>. Inference is also used as part of determining
which method to invoke, as you will see shortly.


The inference process may result in simple types like Object or String, or more complex types for which
no single class or interface declaration existssuch as a type that is both Runnable and Cloneable. These
complex types are generally known as intersection types. For each constraint on a type variable there will be a
set of types that meet that constraint. The intersection of those sets contains the inferred type.

Free download pdf