aspose file tools*
The moose likes Java in General and the fly likes Type argument inference in Generics Big Moose Saloon
  Search | Java FAQ | Recent Topics | Flagged Topics | Hot Topics | Zero Replies
Register / Login
JavaRanch » Java Forums » Java » Java in General
Bookmark "Type argument inference in Generics" Watch "Type argument inference in Generics" New topic
Author

Type argument inference in Generics

Marvin Lew
Greenhorn

Joined: Mar 01, 2003
Posts: 11
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

Joined: Nov 07, 2000
Posts: 242
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

Joined: Mar 01, 2003
Posts: 11
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
Sheriff

Joined: Sep 28, 2004
Posts: 18990
    
  40

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


Books: Java Threads, 3rd Edition, Jini in a Nutshell, and Java Gems (contributor)
Marvin Lew
Greenhorn

Joined: Mar 01, 2003
Posts: 11
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

Joined: Jan 17, 2006
Posts: 1296
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.


Some problems are so complex that you have to be highly intelligent and well informed just to be undecided about them. - Laurence J. Peter
Garrett Rowe
Ranch Hand

Joined: Jan 17, 2006
Posts: 1296
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

Joined: Mar 01, 2003
Posts: 11
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

Joined: Jan 17, 2006
Posts: 1296
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

Joined: Mar 01, 2003
Posts: 11
You're right. I forgot about the bounded wildcard. Thanks.
 
I agree. Here's the link: http://aspose.com/file-tools
 
subject: Type argument inference in Generics