By Maurice Naftalin, Philip Wadler
Book Price: $34.99 USD
£24.99 GBP
PDF Price: $27.99
Cover | Table of Contents | Colophon
List<Integer> ints = Arrays.asList(1,2,3);
int s = 0;
for (int n : ints) { s += n; }
assert s == 6;
List
and the class Arrays are part of the Collections Framework (both are found in the package java.util). The type List is now generic; you write List<E> to indicate a list with elements of type E. Here we write List<Integer> to indicate that the elements of the list belong to the class Integer,
the wrapper class that corresponds to the primitive type int. Boxing and unboxing operations, used to convert from the primitive type to the wrapper class, are automatically inserted. The static method asList takes any number of arguments, places them into an array, and returns a new list backed by the array. The new loop form, foreach,
is used to bind a variable successively to each element of the list, and the loop body adds these into the sum. The assertion statement (introduced in Java 1.4), is used to check that the sum is correct; when assertions are enabled, it throws an error if the condition does not evaluate to true.
List ints = Arrays.asList( new Integer[] {
new Integer(1), new Integer(2), new Integer(3)
} );
int s = 0;
for (Iterator it = ints.iterator(); it.hasNext(); ) {
int n = ((Integer)it.next()).intValue();
s += n;
}
assert s == 6;
List<String> words = new ArrayList<String>(); words.add("Hello "); words.add("world!"); String s = words.get(0)+words.get(1); assert s.equals("Hello world!");
ArrayList<E> implements interface List<E>. This trivial code
fragment declares the variable words to contain a list of strings, creates an instance of an ArrayList,
adds two strings to the list, and gets them out again.
List words = new ArrayList();
words.add("Hello ");
words.add("world!");
String s = ((String)words.get(0))+((String)words.get(1))
assert s.equals("Hello world!");
List<Integer>, List<String>, and List<List<String>> are all represented at run-time by the same type, List.
We also use erasure to describe the process that converts the first program to the second. The term erasure is a slight misnomer, since the process erases type parameters but adds casts.
Cast-iron guarantee: the implicit casts added by the compilation of generics never fail.
Object,
and any variable of reference type may be set to the value null. As shown in the following table, there are eight primitive types, and each of these has a corresponding library class of reference type. The library classes are located in the package java.lang.|
Primitive
|
Reference
|
|---|---|
byte
|
Byte
|
short
|
Short
|
int
|
Integer
|
long
|
Long
|
float
|
Float
|
double
|
Double
|
bool
|
Boolean
|
char
|
Character
|
List<Integer> ints = Arrays.asList(1,2,3);
int s = 0;
for (int n : ints) { s += n; }
assert s == 6;
for.
It is equivalent to the following:for (Iterator<Integer> it = ints.iterator(); it.hasNext(); ) { int n = it.next(); s += n; }
it of type Iterator<Integer> to iterate over the list ints of type List<Integer>. In general, the compiler invents a new name that is guaranteed not to clash with any name already in the code. Note that unboxing occurs when the expression it.next() of type Integer is assigned to the variable n of type int.Iterable<E> (in package java.lang),
which in turn refers to the interface Iterator<E> (in package java.util).
These define the methods iterator,
hasNext,
and next,
which are used by the translation of the foreach loop (iterators also have a method remove,
which is not used by the translation):
interface Iterable<E> {
public Iterator<E> iterator();
}
interface Iterator<E> {
public boolean hasNext();
public E next();
public void remove();
}
Iterable<E> interface; and classes defined by other vendors or users may implement it as well.
public static int sumArray(int[] a) {
int s = 0;
for (int n : a) { s += n; }
return s;
}
remove
method
or to iterate over more than one list in parallel. Here is a method that removes negative elements from a list of doubles:
class Lists {
public static <T> List<T> toList(T[] arr) {
List<T> list = new ArrayList<T>();
for (T elt : arr) list.add(elt);
return list;
}
}
toList accepts an array of type T[] and returns a list of type List<T>, and does so for any type T. This is indicated by writing <T> at the beginning of the method signature,
which declares T as a new type variable. A method which declares a type variable in this way is called a generic method. The scope of the type variable T is local to the method itself; it may appear in the method signature and the method body,
but not outside the method.
List<Integer> ints = Lists.toList(new Integer[] { 1, 2, 3 });
List<String> words = Lists.toList(new String[] { "hello", "world" });
1, 2, 3 from int
to Integer.
T[] with T... in the method declaration:
class Lists {
public static <T> List<T> toList(T... arr) {
List<T> list = new ArrayList<T>();
for (T elt : arr) list.add(elt);
return list;
}
}
List<Integer> ints = Lists.toList(1, 2, 3);
List<String> words = Lists.toList("hello", "world");
public static <T> void addAll(List<T> list, T... arr) {
for (T elt : arr) list.add(elt);
}
assert
statement. Each occurrence of assert is followed by a boolean expression that is expected to evaluate to true. If assertions are enabled and the expression evaluates to false, an AssertionError
is thrown, including an indication of where the error occurred. Assertions are enabled by invoking the JVM with the -ea
or -enableassertions
flag.true. Since assertions may not be enabled, an assertion should never have side effects upon which any nonassertion code depends. When checking for a condition that might not hold (such as confirming that the arguments to a method call are valid), we use a conditional and throw an exception explicitly.extends
or implements
clause. Here are some examples:
Integer
|
is a subtype of
|
Number
|
Double
|
is a subtype of
|
Number
|
ArrayList<E>
|
is a subtype of
|
List<E>
|
List<E>
|
is a subtype of
|
Collection<E>
|
Collection<E>
|
is a subtype of
|
Iterable<E>
|
List<E> is a subtype of Iterable<E>. If one type is a subtype of another, we also say that the second is a extends
or implements
clause. Here are some examples:
Integer
|
is a subtype of
|
Number
|
Double
|
is a subtype of
|
Number
|
ArrayList<E>
|
is a subtype of
|
List<E>
|
List<E>
|
is a subtype of
|
Collection<E>
|
Collection<E>
|
is a subtype of
|
Iterable<E>
|
List<E> is a subtype of Iterable<E>. If one type is a subtype of another, we also say that the second is a supertype
of the first. Every reference type
is a subtype of Object,
and Object is a supertype of every reference type. We also say, trivially, that every type is a subtype of itself.Collection
interface is addAll,
which adds all of the members of one collection to another collection:
interface Collection<E> {
...
public boolean addAll(Collection<? extends E> c);
...
}
E, it is OK to add all members of another collection with elements of type E. The quizzical phrase "? extends E" means that it is also OK to add all members of a collection with elements of any type that is a subtype of E. The question mark is called a wildcard,
since it stands for some type that is a subtype of E.
List<Number> nums = new ArrayList<Number>();
List<Integer> ints = Arrays.asList(1, 2);
List<Double> dbls = Arrays.asList(2.78, 3.14);
nums.addAll(ints);
nums.addAll(dbls);
assert nums.toString().equals("[1, 2, 2.78, 3.14]");
nums has type List<Number>, which is a subtype of Collection<Number>, and ints has type List<Integer>, which is a subtype of Collection<? extends Number>. The second call is similarly permitted. In both calls, E is taken to be Number.
If the method signature for addAll had been written without the wildcard, then the calls to add lists of integers and doubles to a list of numbers would not have been permitted; you would only have been able to add a list that was explicitly declared to be a list of numbers.List<Integer> ints = Arrays.asList(1,2); List<? extends Number> nums = ints; nums.add(3.14); // compile-time error assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!
List<Integer> is not a subtype of List<Number>), but the third line was fine (because a double is a number, so you can add a double to a Collections:
public static <T> void copy(List<? super T> dst, List<? extends T> src) { for (int i = 0; i < src.size(); i++) { dst.set(i, src.get(i)); } }
? super T means that the destination list may have elements of any type that is a supertype
of T, just as the source list may have elements of any type that is a subtype of T.
List<Object> objs = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> ints = Arrays.asList(5, 6);
Collections.copy(objs, ints);
assert objs.toString().equals("[5, 6, four]");
Collections.copy(objs, ints); Collections.<Object>copy(objs, ints); Collections.<Number>copy(objs, ints); Collections.<Integer>copy(objs, ints);
Integer, since that is the most specific choice that works. In the third line, the type parameter T is taken to be Number.
The call is permitted because objs has type List<Object>, which is a subtype of List<? super Number> (since Object
is a supertype of Number, as required by the super)
and ints has type List<Integer>, which is a subtype of List<? extends Number> (since Integer is a subtype of Number, as required by the extends
wildcard).
public static <T> void copy(List<T> dst, List<T> src) public static <T> void copy(List<T> dst, List<? extends T> src) public static <T> void copy(List<? super T> dst, List<T> src) public static <T> void copy(List<? super T> dst, List<? extends T> src)
extends, where should you use super, and where is it inappropriate to use a wildcard at all?The Get and Put Principle: use anextendswildcard when you only get values out of a structure, use asuperwildcard when you only put values into a structure, and don't use a wildcard when you both get and put.
copy
method:public static <T> void copy(List<? super T> dest, List<? extends T> src)
src, so it is declared with an extends wildcard, and it puts values into the destination dst, so it is declared with a super wildcard.extends wildcard. Here is a method that takes a collection of numbers, converts each to a double, and sums them up:
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums) s += num.doubleValue();
return s;
}
extends, all of the following calls are legal:List<Integer> ints = Arrays.asList(1,2,3); assert sum(ints) == 6.0; List<Double> doubles = Arrays.asList(2.78,3.14); assert sum(doubles) == 5.92; List<Number> nums = Arrays.<Number>asList(1,2,2.78,3.14); assert sum(nums) == 8.92;
extends was not used.add
method, you put values into a structure, so use a super wildcard. Here is a method that takes a collection of numbers and an integer n, and puts the first n integers, starting from zero, into the collection:
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
super, all of the following calls are legal:
List<Integer>S[] is considered to be a subtype of T[] whenever S is a subtype of T. Consider the following code fragment, which allocates an array of integers, assigns it to an array of numbers, and then attempts to assign a double into the array:
Integer[] ints = new Integer[] {1,2,3};
Number[] nums = ints;
nums[2] = 3.14; // array store
exception
assert Arrays.toString(ints).equals("[1, 2, 3.14]"); // uh oh!
Integer[] is considered a subtype of Number[], according to the Substitution Principle
the assignment on the second line must be legal. Instead, the problem is caught on the third line, and it is caught at run time. When an array is allocated (as on the first line), it is tagged with its reified type
(a run-time representation of its component type, in this case, Integer), and every time an array is assigned into (as on the third line), an array store exception is raised if the reified type is not compatible with the assigned value (in this case, a double cannot be stored into an array of Integer).List<S> is not considered to be a subtype of List<T>, except in the trivial case where S and T are identical. Here is a code fragment analogous to the preceding one, with lists replacing arrays:List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints; // compile-time error nums.put(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!
List<Integer> is not considered to be a subtype of List<Number>, the problem is detected on the second line, not the third, and it is detected at compile time, not run time.contains
method checks whether a collection
contains a given object, and its generalization, containsAll,
checks whether a collection contains every element of another collection. This section presents two alternate approaches to giving generic signatures
for these methods. The first approach uses wildcards and is the one used in the Java Collections Framework.
The second approach uses type parameters and is often a more appropriate alternative.
interface Collection<E> {
...
public boolean contains(Object o);
public boolean containsAll(Collection<?> c);
...
}
Collection<?> stands for:Collection<? extends Object>
Object is one of the most common uses of wildcards, so it makes sense to provide a short form for writing it.
Object obj = "one";
List<Object> objs = Arrays.<Object>asList("one", 2, 3.14, 4);
List<Integer> ints = Arrays.asList(2, 4);
assert objs.contains(obj);
assert objs.containsAll(ints);
assert !ints.contains(obj);
assert !ints.containsAll(objs);
"one" and the given list of integers, but the given list of integers does not contain the string "one", nor does it contain the given list of objects.ints.contains(obj) and ints.containsAll(objs) might seem silly. Of course, a list of integers won't contain an arbitrary object, such as the string "one". But it is permitted because sometimes such tests might succeed:Object obj = 1; List<Object> objs = Arrays.<Object>asList(1, 3); List<Integer> ints = Arrays.asList(1, 2, 3, 4); assert ints.contains(obj); assert ints.containsAll(objs);
reverse
in the convenience class java.util.Collections, which accepts a list of any type and reverses it. It can be given either of the following two signatures, which are equivalent:public static void reverse(List<?> list); public static void <T> reverse(List<T> list);
public static void <T> reverse(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); } }
public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); // compile-time error } }
List<Object> with List<?> won't fix the problem, because now we have two lists with (possibly different) unknown element types.public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); } }
T has captured the wildcard.
This is a generally useful technique when dealing with wildcards, and it is worth knowing.new),
in explicit type parameters
in generic method calls, or in supertypes (extends
and implements).
List<?> list = new ArrayList<?>(); // compile-time error Map<String, ? extends Number> map = new HashMap<String, ? extends Number>(); // compile-time error
extends wildcard) or only put values into it (if it is a super wildcard).
For a structure to be useful, we must do both. Therefore, we usually create a structure at a precise type, even if we use wildcard types to put values into or get values from the structure, as in the following example:List<Number> nums = new ArrayList<Number>(); List<? super Number> sink = nums; List<? extends Number> source = nums; for (int i=0; i<10; i++) sink.add(i); double sum=0; for (Number num : source) sum+=num.doubleValue();
List<List<?>> lists = new ArrayList<List<?>>();
lists.add(Arrays.asList(1,2,3));
lists.add(Arrays.asList("four","five"));
assert lists.toString().equals("[[1, 2, 3], [four, five]]");
Object,
but since that is the type used by toString,
this code is well typed.Comparable<T> and Comparator<T>, which are used to support comparison
on elements. These interfaces are useful, for instance, if you want to find the maximum element of a collection or sort a list. Along the way, we will introduce bounds
on type variables, an important feature of generics that is particularly useful in combination with the Comparable<T> interface.Comparable<T> contains a single method that can be used to compare one object to another:
interface Comparable<T> {
public int compareTo(T o);
}
compareTo
method returns a value that is negative, zero, or positive depending upon whether the argument is less than, equal to, or greater than the given object. When a class implements Comparable,
the ordering specified by this interface is called the natural ordering
for that class.Integer implements Comparable<Integer>:Integer int0 = 0; Integer int1 = 1; assert int0.compareTo(int1) < 0;
0 precedes 1 under numerical ordering.
Similarly, String implements Comparable<String>:String str0 = "zero"; String str1 = "one"; assert str0.compareTo(str1) > 0;
"zero" follows "one" under alphabetic ordering.
Integer i = 0;
String s = "one";
assert i.compareTo(s) < 0; // compile-time error
Number m = new Integer(2); Number n = new Double(3.14); assert m.compareTo(n) < 0;
Comparable<T> contains a single method that can be used to compare one object to another:
interface Comparable<T> {
public int compareTo(T o);
}
compareTo
method returns a value that is negative, zero, or positive depending upon whether the argument is less than, equal to, or greater than the given object. When a class implements Comparable,
the ordering specified by this interface is called the natural ordering
for that class.Integer implements Comparable<Integer>:Integer int0 = 0; Integer int1 = 1; assert int0.compareTo(int1) < 0;
0 precedes 1 under numerical ordering.
Similarly, String implements Comparable<String>:String str0 = "zero"; String str1 = "one"; assert str0.compareTo(str1) > 0;
"zero" follows "one" under alphabetic ordering.
Integer i = 0;
String s = "one";
assert i.compareTo(s) < 0; // compile-time error
Number m = new Integer(2);
Number n = new Double(3.14);
assert m.compareTo(n) < 0; // compile-time error
Number class does not implement the Compar-able interface.
x.equals(y) if and only if x.compareTo(y) == 0
Comparable<T> interface to find the maximum element in a collection. We begin with a simplified version. The actual version found in the Collections Framework
has a type signature that is a bit more complicated, and later we will see why.Collections:
public static <T extends Comparable<T>> T max(Collection<T> coll) {
T candidate = coll.iterator().next();
for (T elt : coll) {
if (candidate.compareTo(elt) < 0) candidate = elt;
}
return candidate;
}
asList takes an array of type E[] and returns a result of type List<E>, and does so for any type E. Here we have a generic method that declares a bound
on the type variable.
The method max takes a collection of type Collection<T> and returns a T, and it does this for any type T such that T is a subtype
of Comparable<T>.T, and we say that T is bounded by Comparable<T>. As with wildcards, bounds
for type variables are always indicated by the keyword extends,
even when the bound is an interface rather than a class, as is the case here. Unlike wildcards, type variables must always be bounded using extends, never super.
T itself depends upon T. It is even possible to have mutually recursive bounds,
such as:<T extends C<T,U>, U extends D<T,U>>
iterator().next()
rather than get(0) to get the first element, because Comparable<T> interface gives fine control over what can and cannot be compared. Say that we have a Fruit class with subclasses Apple and Orange. Depending on how we set things up, we may prohibit
comparison of apples with oranges or we may permit
such comparison.
class Fruit {...}
class Apple extends Fruit implements Comparable<Apple> {...}
class Orange extends Fruit implements Comparable<Orange> {...}
Apple implements Comparable<Apple>, it is clear that you can compare apples with apples, but not with oranges. The test code builds three lists, one of apples, one of oranges, and one containing mixed fruits. We may find the maximum of the first two lists, but attempting to find the maximum of the mixed list signals an error at compile time.abstract class Fruit implements Comparable<Fruit> { protected String name; protected int size; protected Fruit(String name, int size) { this.name = name; this.size = size; } public boolean equals(Object o) { if (o instanceof Fruit) { Fruit that = (Fruit)o; return this.name.equals(that.name) && this.size == that.size; } else return false; } public int hash() { return name.hash()*29 + size; } public int compareTo(Fruit that) { return this.size < that.size ? - 1 : this.size == that.size ? 0 : 1 ; } } class Apple extends Fruit { public Apple(int size) { super("Apple", size); } } class Orange extends Fruit { public Orange(int size) { super("Orange", size); } } class Test { public static void main(String[] args) { Apple a1 = new Apple(1); Apple a2 = new Apple(2); Orange o3 = new Orange(3); Orange o4 = new Orange(4); List<Apple> apples = Arrays.asList(a1,a2); assert Collections.max(apples).equals(a2); List<Orange> oranges = Arrays.asList(o3,o4); assert Collections.max(oranges).equals(o4); List<Fruit> mixed = Arrays.<Fruit>asList(a1,o3); assert Collections.max(mixed).equals(o3);
Comparable interface, or to compare objects using a different ordering from the one specified by that interface. The ordering provided by the Comparable interface is called the natural ordering, so the Comparator
interface provides, so to speak, an unnatural ordering.
Comparator interface, which contains a single method:
interface Comparator<T> {
public int compare(T o1, T o2);
}
compare
method returns a value that is negative, zero, or positive depending upon whether the first object is less than, equal to, or greater than the second object—just as with compareTo.
Comparator<String> sizeOrder =
new Comparator<String>() {
public int compare(String s1, String s2) {
return
s1.length() < s2.length() ? −1 :
s1.length() > s2.length() ? 1 :
s1.compareTo(s2) ;
}
};
assert "two".compareTo("three") > 0;
assert sizeOrder.compare("two","three") < 0;
"two" is greater than "three", whereas in the size ordering
it is smaller.Comparable
and Comparator.
For every generic method
with a type variable
bounded by Comparable, there is another generic method with an additional argument of type Comparator. For instance, corresponding to:
public static <T extends Comparable<? super T>>
T max(Collection<? extends T> coll)
public static <T>
T max(Collection<? extends T> coll, Comparator<? super T> cmp)
enum Season { WINTER, SPRING, SUMMER, FALL }
enum
declaration above expands into a class called Season. Exactly four instances of this class exist, bound to four static final variables with the names WINTER, SPRING, SUMMER, and FALL.java.lang.Enum. Its definition in the Java documentation begins like this:class Enum<E extends Enum<E>>
E extends Enum<E> is a lot like the phrase T extends Comparable<T> that we encountered in the definition of max (see Section 3.2), and we'll see that they appear for related reasons.Enum
and Example 3.5 shows the class Season that corresponds to the enumerated type declaration above. (The code for Enum follows the source in the Java library, but we have simplified a few points.)
public abstract class Enum<E extends Enum<E>> implements Comparable<E> {
private final String name;
private final int ordinal;
protected Enum(String name, int ordinal) {
this.name = name; this.ordinal = ordinal;
}
public final String name() { return name; }
public final int ordinal() { return ordinal; }
public String toString() { return name; }
public final int compareTo(E o) {
return ordinal - o.ordinal;
}
}
// corresponds to
// enum Season { WINTER, SPRING, SUMMER, FALL }
final class Season extends Enum<Season> {
private Season(String name, int ordinal) { super(name,ordinal); }
public static final Season WINTER = new Season("WINTER",0);
public static final Season SPRING = new Season("SPRING",1);
public static final Season SUMMER = new Season("SUMMER",2);
public static final Season FALL = new Season("FALL",3);
private static final Season[] VALUES = { WINTER, SPRING, SUMMER, FALL };
public static Season[] values() { return VALUES.clone(); }
public static Season valueOf(String name) {
for (Season e : VALUES) if (e.name().equals(name)) return e;
throw new IllegalArgumentException();
}
}