













Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
The concept of generics in java, focusing on container types and their use of type parameters. It also covers the use of wildcard types and when to use them instead of generic methods. Examples of classes and interfaces, as well as discussions on the benefits and limitations of using generics and wildcards.
Typology: Lecture notes
1 / 21
This page cannot be seen from the preview
Don't miss anything!














JDK 1.5 introduces several extensions to the Java programming language. One of these is the introduction of generics. This tutorial is aimed at introducing you to generics. You may be familiar with similar constructs from other languages, most notably C++ templates. If so, you’ll soon see that there are both similarities and important differences. If you are not familiar with look-a-alike constructs from elsewhere, all the better; you can start afresh, without unlearning any misconceptions. Generics allow you to abstract over types. The most common examples are con- tainer types, such as those in the Collection hierarchy. Here is a typical usage of that sort:
List myIntList = new LinkedList(); // 1 myIntList.add(new Integer(0)); // 2 Integer x = (Integer) myIntList.iterator().next(); // 3
The cast on line 3 is slightly annoying. Typically, the programmer knows what kind of data has been placed into a particular list. However, the cast is essential. The compiler can only guarantee that an Object will be returned by the iterator. To ensure the assignment to a variable of type Integer is type safe, the cast is required. Of course, the cast not only introduces clutter. It also introduces the possibility of a run time error, since the programmer might be mistaken. What if programmers could actually express their intent, and mark a list as being restricted to contain a particular data type? This is the core idea behind generics. Here is a version of the program fragment given above using generics:
List
Notice the type declaration for the variable myIntList. It specifies that this is not just an arbitrary List, but a List of Integer, written List
Here is a small excerpt from the definitions of the interfaces List and Iterator in pack- age java.util:
public interface List
This should all be familiar, except for the stuff in angle brackets. Those are the declarations of the formal type parameters of the interfaces List and Iterator. Type parameters can be used throughout the generic declaration, pretty much where you would use ordinary types (though there are some important restrictions; see section 7). In the introduction, we saw invocations of the generic type declaration List, such as List
public interface IntegerList { void add(Integer x) Iterator
This intuition can be helpful, but it’s also misleading. It is helpful, because the parameterized type List
Consider the problem of writing a routine that prints out all the elements in a collection. Here’s how you might write it in an older version of the language:
void printCollection(Collection c) { Iterator i = c.iterator(); for (k = 0; k < c.size(); k++) { System.out.println(i.next()); }}
And here is a naive attempt at writing it using generics (and the new for loop syn- tax):
void printCollection(Collection
The problem is that this new version is much less useful than the old one. Whereas the old code could be called with any kind of collection as a parameter, the new code only takes Collection
void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e); }}
and now, we can call it with any type of collection. Notice that inside printCollec- tion(), we can still read elements from c and give them type Object. This is always safe, since whatever the actual type of the collection, it does contain objects. It isn’t safe to add arbitrary objects to it however:
Collection<?> c = new ArrayList
Since we don’t know what the element type of c stands for, we cannot add objects to it. The add() method takes arguments of type E, the element type of the collection. When the actual type parameter is ?, it stands for some unknown type. Any parameter we pass to add would have to be a subtype of this unknown type. Since we don’t know what type that is, we cannot pass anything in. The sole exception is null, which is a member of every type. On the other hand, given a List<?>, we can call get() and make use of the result. The result type is an unknown type, but we always know that it is an object. It is
therefore safe to assign the result of get() to a variable of type Object or pass it as a parameter where the type Object is expected.
Consider a simple drawing application that can draw shapes such as rectangles and cir- cles. To represent these shapes within the program, you could define a class hierarchy such as this:
public abstract class Shape { public abstract void draw(Canvas c); } public class Circle extends Shape { private int x, y, radius; public void draw(Canvas c) { ... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Canvas c) { ... } }
These classes can be drawn on a canvas:
public class Canvas { public void draw(Shape s) { s.draw( this ); } }
Any drawing will typically contain a number of shapes. Assuming that they are represented as a list, it would be convenient to have a method in Canvas that draws them all:
public void drawAll(List
Now, the type rules say that drawAll() can only be called on lists of exactly Shape: it cannot, for instance, be called on a List
public void drawAll(List<? extends Shape> shapes) { ... }
There is a small but very important difference here: we have replaced the type List
have recognized that using Collection<?> 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.
static
We can call this method with any kind of collection whose element type is a super- type of the element type of the array.
Object[] oa = new Object[100]; Collection
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. One 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.
interface Collection
We could have used generic methods here instead:
interface Collection
However, in both containsAll and addAll, the type parameter T 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():
class Collections { public static
Note the dependency between the types of the two parameters. Any object copied from the source list, src, must be assignable to the element type T of the destination list, dst. So the element type of src can be any subtype of T - we don’t care which. The signature of copy 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:
class Collections { public static <T, S extends T> void copy(List src){...} }
This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src - nothing else depends on it. This is a sign that we can replace S 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 signa- tures, 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 have drawAll() store its incoming argument into the history field.
static 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 ); }}
the collection you pass in is indeed a Collection of Part. Of course, generics are tailor made for this:
package com.mycompany.inventory; import com.Fooblibar.widgets.*; public class ... Blade implements Part { } public class Guillotine implements Part { } public class Main { public static void main(String[] args) { Collection
When we call addAssembly, it expects the second parameter to be of type Collec- tion. The actual argument is of type Collection
type system usually provides are void. However, you are still better off than you were without using generics at all. At least you know the code on your end is consistent. At the moment there’s a lot more non-generic code out there then there is generic code, and there will inevitably be situations where they have to mix. If you find that you must intermix legacy and generic code, pay close attention to the unchecked warnings. Think carefully how you can justify the safety of the code that gives rise to the warning. What happens if you still made a mistake, and the code that caused a warning is indeed not type safe? Let’s take a look at such a situation. In the process, we’ll get some insight into the workings of the compiler.
public String loophole(Integer x) { List
Here, we’ve aliased a list of strings and a plain old list. We insert an Integer into the list, and attempt to extract a String. This is clearly wrong. If we ignore the warning and try to execute this code, it will fail exactly at the point where we try to use the wrong type. At run time, this code behaves like:
public String loophole(Integer x) { List ys = new LinkedList; List xs = ys; xs.add(x); return (String) ys.iterator().next(); // run time error }
When we extract an element from the list, and attempt to treat it as a string by casting it to String, we will get a ClassCastException. The exact same thing happens with the generic version of loophole(). The reason for this is, that generics are implemented by the Java compiler as a front-end conversion called erasure. You can (almost) think of it as a source-to-source translation, whereby the generic version of loophole() is converted to the non-generic version. As a result, the type safety and integrity of the Java virtual machine are never at risk, even in the presence of unchecked warnings. Basically, erasure gets rid of (or erases ) all generic type information. All the type information betweeen angle brackets is thrown out, so, for example, a parameterized type like List
Line 1 generates an unchecked warning, because a raw Collection is being passed in where a Collection of Parts is expected, and the compiler cannot ensure that the raw Collection really is a Collection of Parts. As an alternative, you can compile the client code using the source 1.4 flag, ensur- ing that no warnings are generated. However, in that case you won’t be able to use any of the new language features introduced in JDK 1.5.
What does the following code fragment print?
List
You might be tempted to say false, but you’d be wrong. It prints true, because all instances of a generic class have the same run-time class, regardless of their actual type parameters. Indeed, what makes a class generic is the fact that it has the same behavior for all of its possible type parameters; the same class can be viewed as having many different types. As consequence, the static variables and methods of a class are also shared among all the instances. That is why it is illegal to refer to the type parameters of a type declaration in a static method or initializer, or in the declaration or initializer of a static variable.
Another implication of the fact that a generic class is shared among all its instances, is that it usually makes no sense to ask an instance if it is an instance of a particular invocation of a generic type:
Collection cs = new ArrayList
similarly, a cast such as
Collection
gives an unchecked warning, since this isn’t something the run time system is going to check for you. The same is true of type variables
Type variables don’t exist at run time. This means that they entail no performance overhead in either time nor space, which is nice. Unfortunately, it also means that you can’t reliably use them in casts.
The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects. This is annoying, to be sure. This restriction is necessary to avoid situations like:
List
If arrays of parameterized type were allowed, the example above would compile without any unchecked warnings, and yet fail at run-time. We’ve had type-safety as a primary design goal of generics. In particular, the language is designed to guaran- tee that if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe. However, you can still use wildcard arrays. Here are two variations on the code above. The first forgoes the use of both array objects and array types whose element type is parameterized. As a result, we have to cast explicitly to get a String out of the array.
List>[] lsa = new List>[10]; // ok, array of unbounded wildcard type Object o = lsa; Object[] oa = (Object[]) o; List
IIn the next variation, we refrain from creating an array object whose element type is parameterized, but still use an array type with a parameterized element type. This is legal, but generates an unchecked warning. Indeed, the code is unsafe, and eventually an error occurs.
List
class ... EmpInfoFactory implements Factory
and call it select(getMyEmpInfoFactory(), ”selection string”);
The downside of this solution is that it requires either:
It is very natural to use the class literal as a factory object, which can then be used by reflection. Today (without generics) the code might be written:
Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);... public static Collection select(Class c, String sqlStatement) { Collection result = new ArrayList(); /* run sql query using jdbc */ for ( /* iterate over jdbc results */ ) { Object item = c.newInstance(); /* use reflection and set all of item’s fields from sql results */ result.add(item); } return result; }
However, this would not give us a collection of the precise type we desire. Now that Class is generic, we can instead write
Collection
giving us the precise type of collection in a type safe way. This technique of using class literals as run time type tokens is a very useful trick to know. It’s an idiom that’s used extensively in the new APIs for manipulating anno- tations, for example.
In this section, we’ll consider some of the more advanced uses of wildcards. We’ve seen several examples where bounded wildcards were useful when reading from a data structure. Now consider the inverse, a write-only data structure. The interface Sink is a simple example of this sort.
interface Sink
We can imagine using it as demonstrated by the code below. The method writeAll() is designed to flush all elements of the collection coll to the sink snk, and return the last element flushed.
public static
As written, the call to writeAll() is illegal, as no valid type argument can be inferred; neither String nor Object are appropriate types for T, because the Collection element and the Sink element must be of the same type. We can fix this by modifying the signature of writeAll() as shown below, using a wildcard.
public static ...
The call is now legal, but the assignment is erroneous, since the return type inferred is Object because T matches the element type of s, which is Object. The solution is to use a form of bounded wildcard we haven’t seen yet: wildcards with a lower bound. The syntax? super T denotes an unknown type that is a supertype of T^3. It is the dual of the bounded wildcards we’ve been using, where we use? extends T to denote an unknown type that is a subtype of T.
public static ...
Using this syntax, the call is legal, and the inferred type is String, as desired. (^3) Or T itself. Remember, the supertype relation is reflexive.
public static <T extends Comparable<? super T>> T max(Collection
This reasoning applies to almost any usage of Comparable that is intended to work for arbitrary types: You always want to use Comparable<? super T>. In general, if you have an API that only uses a type parameter T as an argument, its uses should take advantage of lower bounded wildcards (? super T). Conversely, if the API only returns T, you’ll give your clients more flexibility by using upper bounded wildcards (? extends T).
It should be pretty clear by now that given
Set... <?> unknownSet = new HashSet
The call below is illegal.
addToSet(unknownSet, “abc”); // illegal
It makes no difference that the actual set being passed is a set of strings; what matters is that the expression being passed as an argument is a set of an unknown type, which cannot be guaranteed to be a set of strings, or of any type in particular. Now, consider
class ... Collections {
It seems this should not be allowed; yet, looking at this specific call, it is perfectly safe to permit it. After all, unmodifiableSet() does work for any kind of Set, regard- less of its element type. Because this situation arises relatively frequently, there is a special rule that allows such code under very specific circumstances in which the code can be proven to be safe. This rule, known as wildcard capture, allows the compiler to infer the unknown type of a wildcard as a type argument to a generic method.
Earlier, we showed how new and legacy code can interoperate. Now, it’s time to look at the harder problem of “generifying” old code. If you decide to convert old code to use generics, you need to think carefully about how you modify the API.
You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. Consider again some examples from java.util.Collection. The pre-generic API looks like:
interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c); }
A naive attempt to generify it is:
interface Collection
While this is certainly type safe, it doesn’t live up to the API’s original contract. The containsAll() method works with any kind of incoming collection. It will only succeed if the incoming collection really contains only instances of E, but:
In the case of addAll(), we should be able to add any collection that consists of instances of a subtype of E. We saw how to handle this situation correctly in section 5. You also need to ensure that the revised API retains binary compatibility with old clients. This implies that the erasure of the API must be the same as the original, ungenerified API. In most cases, this falls out naturally, but there are some subtle cases. We’ll examine one of the subtlest cases we’ve encountered, the method Col- lections.max(). As we saw in section 9, a plausible signature for max() is:
public static <T extends Comparable<? super T>> T max(Collection
This is fine, except that the erasure of this signature is
public static Comparable max(Collection coll)
which is different than the original signature of max():
public static Object max(Collection coll)
One could certainly have specified this signature for max(), but it was not done, and all the old binary class files that call Collections.max() depend on a signature that returns Object.