Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

generics

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?
like image 890
yapkm01 Avatar asked Sep 28 '11 04:09

yapkm01


People also ask

What are the restrictions are considered to use Java Generics effectively explain in detail?

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.

How do I restrict a generic type in Java?

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.

How do you provide a parametrized type for a generic?

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.

What is Java Generics with examples?

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.


2 Answers

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.

like image 168
Paul Bellora Avatar answered Sep 19 '22 08:09

Paul Bellora


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

like image 21
irreputable Avatar answered Sep 18 '22 08:09

irreputable