开发者

Java Generics: Question regarding type capture and generated inference using generic methods

开发者 https://www.devze.com 2023-04-08 17:13 出处:网络
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.

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:

  1. Why clause (1) didn't produce <Object>? Since <Object> works as shown in clause (2), why <? extends Object> being produce instead?
  2. why clause (3) produce <Object> instead of <? extends Object>?
  3. Since clause (4) uses the same variable, why 2 different type capture generated eventhough the parameter used is of the same variable d?


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<?>

0

精彩评论

暂无评论...
验证码 换一张
取 消