• Post Reply
  • Bookmark Topic Watch Topic
  • New Topic

Type argument inference in Generics

 
Marvin Lew
Greenhorn
Posts: 11
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, there,

I seem to be confused by the type argument inference in Java generics. An example from the Java tutorial (http://java.sun.com/docs/books/tutorial/extra/generics/methods.html) demonstrates how the type argument inference works (I cut off a few lines to make the code shorter):

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // Correct
}
}

Integer[] ia = new Integer[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn); // T inferred to be Number


However, later in the tutorial it is shown that the following code has a compile-time error:

interface Sink<T> {
flush(T t);
}

public static <T> T writeAll(Collection<T> coll, Sink<T> snk) {
T last;
for (T t : coll) {
last = t;
snk.flush(last);
}
return last;
}
...
Sink<Object> s;
Collection<String> cs;
String str = writeAll(cs, s); // Illegal call.


The explanation given is that "the Collection element and the Sink element must be of the same type".

What I don't get is why the type argument inference isn't working here. It seems to me that the compiler could infer: writeAll(Collection<Object>, Sink<Object> .

I'd appreciate if anyone could give me some suggestion regarding this issue.
 
Santhosh Kumar
Ranch Hand
Posts: 242
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Generics uses something called 'formal type parameters', which are virtual type placeholders for actual types. For ex., in your example T is an virtual type and String is the actual type.

A class can make use of any number of format type parameters, and should be defined while declaring the class.

For ex.,



makes use of one formal parameter and



makes use of two formal type parameters.

Given formal type parameter always points to one actual type. So using the same formal type at any place, requires same actual type to be specified.

If I define Test<Object, String> newTest = new Test<Object, String>();, means wherever the type A is referred in the Test class, I can expect Object type and wherever B is referred, I can expect String type.

Please note that both virtual types can be mapped to same actual type as in, Test<String, String> newTest = new Test<String, String>();

Back to your example.



In this case, writeAll method expect two arguments of SAME actual type and returns the same actual type.

So it is violation of this contract if you execute below code, as it passes two different actual types (Object and String).



Hope I didn't make you more confused

[ July 08, 2008: Message edited by: Santhosh Kumar ]
[ July 08, 2008: Message edited by: Santhosh Kumar ]
 
Marvin Lew
Greenhorn
Posts: 11
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Kumar,

Thanks for your reply. I think I got the connection between the formal type parameter and the type of the actual argument. But it seems that the contract in method fromArrayToCollection() in the first example is broken. The method is declared as:

static <T> void fromArrayToCollection(T[] a, Collection<T> c)

And yet the following code is legal according to type parameter inference:

Integer[] ia = new Integer[100];
Collection<Number> cn = new ArrayList<Number>();
fromArrayToCollection(ia, cn);

Is there anything that I missed?

Thanks.
 
Henry Wong
author
Marshal
Pie
Posts: 20836
75
C++ Chrome Eclipse IDE Firefox Browser Java jQuery Linux VI Editor Windows
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Why shouldn't the code be legal? Why can't T be Number? Since Integer is-a Number, then the common type can be Number, right?

Henry
 
Marvin Lew
Greenhorn
Posts: 11
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Henry,

Yes, that piece of code is legal. But it leads to my question about the code in the second example that gives an error. The method writeAll() is declared as:

public static <T> T writeAll(Collection<T> coll, Sink<T> snk)

And it is called in the program:

String str = writeAll(cs, s);

cs is a collection of String (Collection<String> ) and s is declared as Sink<Object>. Since String is an Object, shouldn't the code above be legal if we follow the same reasoning?
[ July 08, 2008: Message edited by: Marvin Lew ]
 
Garrett Rowe
Ranch Hand
Posts: 1296
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The difference is that an Integer[] is a subclass of a Number[], however a List<Integer> is not a subclass of List<Number>.

Java arrays are covariant in their types; if S is a subtype of T, then S[] is a subtype of T[]. Generic types are not covariant in their type parameter. This, in my opinion, is a failing in Java arrays.
 
Garrett Rowe
Ranch Hand
Posts: 1296
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
To harp on this point with an example:

Object[] arr = new String[5]; //legal
List<Object> list = new ArrayList<String>(); //illegal
List<? extends Object> list2 = new ArrayList<String>();//legal, but nothing can be added to list2
List<?> list3 = new ArrayList<String>();//exactly the same as above

See also Angelika Langer's very informative article on Java generics and arrays.
 
Marvin Lew
Greenhorn
Posts: 11
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi, Garrett,

Thank you so much for the explanation and the article. It looks much clearer to me now.

I changed the declaration of the method wrtieAll() to:

public static <T> T writeAll(Collection<? extends T> coll, Sink<T> snk)

and the actual call to:

Object str = writeAll(cs, s);

Then the program compiled. I guess now the actual call to writeAll():

Object str = writeAll(cs, s);

makes the compiler infer that the arguments in the call are: (Collection<Object>, Sink<Object> .

Am I right on this? Thanks.
 
Garrett Rowe
Ranch Hand
Posts: 1296
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Not exactly, the type parameters are allowed to be different in this case. Your method now take a Sink which can flush() any T, and a Collection which holds 'T' or some subclass of T which can legally be treated as a T.
 
Marvin Lew
Greenhorn
Posts: 11
  • 0
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're right. I forgot about the bounded wildcard. Thanks.
 
I agree. Here's the link: http://aspose.com/file-tools
  • Post Reply
  • Bookmark Topic Watch Topic
  • New Topic