This is a follow-up to my previous question but since the previous thread was a long one, i decided to start another thread pertaining to the almost same topic.
public class GenericMethodInference {
static <T> void test1(T t1, T t2) {}
static <T> void test3(T t1, List <T> t2) {}
static <T> void test4(List <T> t1, List <T> t2) {}
public static void main(String [] args) {
List <Object> c = new LinkedList<Object>();
List <? extends Object> d = new ArrayList<Integer>();
List e = new ArrayList<Integer>();
test1("Hello", new Integer(1)); // ok clause (1)
GenericMethodInference.<Object>test1("Hello", new Integer(1)); // ok clause (2)
test3("Hello", c); // ok clause (3)
test4(d,d) // clause (4) Error due to different type capture generated
}
Note: If you move your cursor over each clause, you will see the inference being generated and displayed on Eclipse:
a. Clause (1) will produce <? extends Object> test1 <? extends Object, ? extends Object>
b. Clause (2) will produce exactly what's defined in the actual type parameter
c. Clause (3) will produce <Object> test3 <Object, List <Object>>
Questions:
To use Java generics effectively, you must consider the following restrictions: Cannot Instantiate Generic Types with Primitive Types. Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
In order to use a generic type we must provide one type argument per type parameter that was declared for the generic type. The type argument list is a comma separated list that is delimited by angle brackets and follows the type name. The result is a so-called parameterized type.
Generics add that type of safety feature. We will discuss that type of safety feature in later examples. Generics in Java are similar to templates in C++. For example, classes like HashSet, ArrayList, HashMap, etc., use generics very well.
Why clause (1) didn't produce <Object>? Since <Object> works as shown in clause (2), why <? extends Object> being produce instead?
This is the best question out of the three. My thinking is that the compiler/Eclipse doesn't want to assume that Object
is necessarily the type T
that is inferred between String
and Integer
, so it plays it safe. As @bringer128 pointed out, String
and Integer
also both implement Serializable
and Comparable
- so these types are also candidates for the inferred type of the method.
It's worth noting that the following code gives the compiler error "illegal start of type":
GenericMethodInference.<? extends Object>test1("Hello", new Integer(1));
This is because it's invalid to specify a wildcard as a method's type parameter. So the fact you're seeing that in the tooltip has to do with a subtlety of the compiler's/Eclipse's facility to report this information - it has determined only that T
is within its bounds, not what it is.
Remember that Java's implementation of generics is solely for the convenience/sanity of programmers. Once compiled into bytecode, type erasure will have gotten rid of any notion of T
. So in its checking, the compiler only needs to ensure that a valid T
can be inferred, but not necessarily what it is.
why clause (3) produce <Object> instead of <? extends Object>?
Because in this case, the fact that a List<Object>
is passed in where a List<T>
is expected tells the compiler that T
is exactly Object
.
Since clause (4) uses the same variable, why 2 different type capture generated eventhough the parameter used is of the same variable d?
It isn't safe for the compiler to assume that d
actually refers to the same object, even between evaluating parameters. For example:
test4(d,(d = new ArrayList<String>()));
In this case, a List<Integer>
would be passed into the first parameter, and an List<String>
into the second - both from d
. Since this scenario is possible, it's easier for the compiler to play it safe.
The test1() case is actually quite sinister. see JLS3 15.12.2.7.
We are not supposed to know the details of type inference - in most cases intuition coincides with the algorithm. Alas that's not always the case, as in the seemingly trivial test1() example.
The constraints we have is T :> String
and T :> Integer
( ":>
" means super type)
This leads to T=lub(String,Integer)
, lub
means "least upper bound".
Since String <: Comparable<String>
and Integer <: Comparable<Integer>
, this leads to lci({Comparable<String>, Comparable<Integer>})
, which yields Comparable<? extends lub(String,Integer)>
, i.e. Compable<? extends T>
In the end, we have T = Serializable & Compable<? extends T>
, a self referenced definition! Spec calls it "infinite type":
It is possible that the process above yields an infinite type. This is permissible, and Java compilers must recognize such situations and represent them appropriately using cyclic data structures.
Let's find out how javac represents it: (javac 7)
static <T> T test1(T t1, T t2) {}
public static void main(String[] args)
{
Void x = test1("Hello", new Integer(1));
}
error: incompatible types
required: Void
found: INT#1
where INT#1,INT#2 are intersection types:
INT#1 extends Object,Serializable,Comparable<? extends INT#2>
INT#2 extends Object,Serializable,Comparable<?>
That doesn't seems right; it's not really recursive; it appears that javac detects recursion in lub()
and gives up, resulting a less specific type Comparable<?>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With