Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
Consider writing a method that takes an array of objects and a collection and puts all objects in the array into the collection. Here's a first attempt:By now, you will have learned to avoid the beginner's mistake of trying to usestatic void fromArrayToCollection(Object[] a, Collection<?> c) { for (Object o : a) { c.add(o); // Compile time error } }Collection<Object>
as the type of the collection parameter. You may or may not have recognized that usingCollection<?>
isn't going to work either. Recall that you cannot just shove objects into a collection of unknown type.The way to do deal with these problems is to use generic methods. Just like type declarations, method declarations can be generic--that is, parameterized by one or more type parameters.
We can call this method with any kind of collection whose element type is a supertype of the element type of the array.static <T> void fromArrayToCollection(T[] a, Collection<T> c) { for (T o : a) { c.add(o); // Correct } }Notice that we don't have to pass an actual type argument to a generic method. The compiler infers the type argument for us, based on the types of the actual arguments. It will generally infer the most specific type argument that will make the call type-correct.Object[] oa = new Object[100]; Collection<Object> co = new ArrayList<Object>(); fromArrayToCollection(oa, co); // T inferred to be Object String[] sa = new String[100]; Collection<String> cs = new ArrayList<String>(); fromArrayToCollection(sa, cs); // T inferred to be String fromArrayToCollection(sa, co); // T inferred to be Object Integer[] ia = new Integer[100]; Float[] fa = new Float[100]; Number[] na = new Number[100]; Collection<Number> cn = new ArrayList<Number>(); fromArrayToCollection(ia, cn); // T inferred to be Number fromArrayToCollection(fa, cn); // T inferred to be Number fromArrayToCollection(na, cn); // T inferred to be Number fromArrayToCollection(na, co); // T inferred to be Object fromArrayToCollection(na, cs); // compile-time errorOne question that arises is: when should I use generic methods, and when should I use wildcard types? To understand the answer, let's examine a few methods from the
Collection
libraries.We could have used generic methods here instead:interface Collection<E> { public boolean containsAll(Collection<?> c); public boolean addAll(Collection<? extends E> c); }However, in bothinterface Collection<E> { public <T> boolean containsAll(Collection<T> c); public <T extends E> boolean addAll(Collection<T> c); // Hey, type variables can have bounds too! }containsAll
andaddAll
, the type parameterT
is used only once. The return type doesn't depend on the type parameter, nor does any other argument to the method (in this case, there simply is only one argument). This tells us that the type argument is being used for polymorphism; its only effect is to allow a variety of actual argument types to be used at different invocation sites. If that is the case, one should use wildcards. Wildcards are designed to support flexible subtyping, which is what we're trying to express here.Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn't such a dependency, a generic method should not be used.
It is possible to use both generic methods and wildcards in tandem. Here is the method
Collections.copy()
:Note the dependency between the types of the two parameters. Any object copied from the source list,class Collections { public static <T> void copy(List<T> dest, List<? extends T> src) { ... }src
, must be assignable to the element typeT
of the destination list,dst
. So the element type ofsrc
can be any subtype ofT
--we don't care which. The signature ofcopy
expresses the dependency using a type parameter, but uses a wildcard for the element type of the second parameter.We could have written the signature for this method another way, without using wildcards at all:
This is fine, but while the first type parameter is used both in the type ofclass Collections { public static <T, S extends T> void copy(List<T> dest, List<S> src) { ... }dst
and in the bound of the second type parameter,S
,S
itself is only used once, in the type ofsrc
--nothing else depends on it. This is a sign that we can replaceS
with a wildcard. Using wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.Wildcards also have the advantage that they can be used outside of method signatures, as the types of fields, local variables and arrays. Here is an example.
Returning to our shape drawing problem, suppose we want to keep a history of drawing requests. We can maintain the history in a static variable inside class
Shape
, and havedrawAll()
store its incoming argument into the history field.Finally, again let's take note of the naming convention used for the type parameters. We usestatic List<List<? extends Shape>> history = new ArrayList<List<? extends Shape>>(); public void drawAll(List<? extends Shape> shapes) { history.addLast(shapes); for (Shape s: shapes) { s.draw(this); } }T
for type, whenever there isn't anything more specific about the type to distinguish it. This is often the case in generic methods. If there are multiple type parameters, we might use letters that neighborT
in the alphabet, such asS
. If a generic method appears inside a generic class, it's a good idea to avoid using the same names for the type parameters of the method and class, to avoid confusion. The same applies to nested generic classes.
Start of Tutorial > Start of Trail > Start of Lesson |
Search
Feedback Form |
Copyright 1995-2005 Sun Microsystems, Inc. All rights reserved.