JETZT ONLINE BESTELLEN
First Edition November 2006
ISBN 978-0-596-52775-4
284 Seiten
EUR29.00
Weitere Informationen zu diesem Buch
Inhaltsverzeichnis |
Kolophon |
Rezensionen |
Inhaltsverzeichnis
- Chapter 1: Introduction
- InhaltsvorschauGenerics and collections work well with a number of other new features introduced in the latest versions of Java, including boxing and unboxing, a new form of loop, and functions that accept a variable number of arguments. We begin with an example that illustrates all of these. As we shall see, combining them is synergistic: the whole is greater than the sum of its parts.Taking that as our motto, let's do something simple with sums: put three numbers into a list and add them together. Here is how to do it in Java with generics:
List<Integer> ints = Arrays.asList(1,2,3); int s = 0; for (int n : ints) { s += n; } assert s == 6;You can probably read this code without much explanation, but let's touch on the key features. The interfaceListand the classArraysare part of the Collections Framework (both are found in the packagejava.util). The typeListis now generic; you writeList<E>to indicate a list with elements of typeE. Here we writeList<Integer>to indicate that the elements of the list belong to the classInteger, the wrapper class that corresponds to the primitive typeint. Boxing and unboxing operations, used to convert from the primitive type to the wrapper class, are automatically inserted. The static methodasListtakes 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 totrue.Here is how the same code looks in Java before generics: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;Reading this code is not quite so easy. Without generics, there is no way to indicate in the type declaration what kind of elements you intend to store in the list, so instead of writingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generics
- InhaltsvorschauAn interface or class may be declared to take one or more type parameters, which are written in angle brackets and should be supplied when you declare a variable belonging to the interface or class or when you create a new instance of a class.We saw one example in the previous section. Here is another:
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!");
In the Collections Framework, classArrayList<E>implements interfaceList<E>. This trivial code fragment declares the variablewordsto contain a list of strings, creates an instance of anArrayList, adds two strings to the list, and gets them out again.In Java before generics, the same code would be written as follows: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!");Without generics, the type parameters are omitted, but you must explicitly cast whenever an element is extracted from the list.In fact, the bytecode compiled from the two sources above will be identical. We say that generics are implemented by erasure because the typesList<Integer>,List<String>, andList<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.Generics implicitly perform the same cast that is explicitly performed without generics. If such casts could fail, it might be hard to debug code written with generics. This is why it is reassuring that generics come with the following guarantee:Cast-iron guarantee: the implicit casts added by the compilation of generics never fail.There is also some fine print on this guaranteee: it applies only when no unchecked warnings have been issued by the compiler. Later, we will discuss at some length what causes unchecked warnings to be issued and how to minimize their effect.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Boxing and Unboxing
- InhaltsvorschauRecall that every type in Java is either a reference type or a primitive type. A reference type is any class, instance, or array type. All reference types are subtypes of class
Object, and any variable of reference type may be set to the valuenull. 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 packagejava.lang.PrimitiveReferencebyteByteshortShortintIntegerlongLongfloatFloatdoubleDoubleboolBooleancharCharacterConversion of a primitive type to the corresponding reference type is called boxing and conversion of the reference type to the corresponding primitive type is calledEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Foreach
- InhaltsvorschauHere, again, is our code that computes the sum of a list of integers.
List<Integer> ints = Arrays.asList(1,2,3); int s = 0; for (int n : ints) { s += n; } assert s == 6;The loop in the third line is called a foreach loop even though it is written with the keywordfor. It is equivalent to the following:for (Iterator<Integer> it = ints.iterator(); it.hasNext(); ) { int n = it.next(); s += n; }
The emphasized code corresponds to what was written by the user, and the unemphasized code is added in a systematic way by the compiler. It introduces the variableitof typeIterator<Integer>to iterate over the listintsof typeList<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 expressionit.next()of typeIntegeris assigned to the variablenof typeint.The foreach loop can be applied to any object that implements the interfaceIterable<E>(in packagejava.lang), which in turn refers to the interfaceIterator<E>(in packagejava.util). These define the methodsiterator,hasNext, andnext, which are used by the translation of the foreach loop (iterators also have a methodremove, 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(); }All collections, sets, and lists in the Collections Framework implement theIterable<E>interface; and classes defined by other vendors or users may implement it as well.The foreach loop may also be applied to an array:public static int sumArray(int[] a) { int s = 0; for (int n : a) { s += n; } return s; }The foreach loop was deliberately kept simple and catches only the most common case. You need to explicitly introduce an iterator if you wish to use theremovemethod or to iterate over more than one list in parallel. Here is a method that removes negative elements from a list of doubles:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generic Methods and Varargs
- InhaltsvorschauHere is a method that accepts an array of any type and converts it to a list:
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; } }The static methodtoListaccepts an array of typeT[]and returns a list of typeList<T>, and does so for any typeT. This is indicated by writing<T>at the beginning of the method signature, which declaresTas a new type variable. A method which declares a type variable in this way is called a generic method. The scope of the type variableTis local to the method itself; it may appear in the method signature and the method body, but not outside the method.The method may be invoked as follows:List<Integer> ints = Lists.toList(new Integer[] { 1, 2, 3 }); List<String> words = Lists.toList(new String[] { "hello", "world" });In the first line, boxing converts1, 2, 3frominttoInteger.Packing the arguments into an array is cumbersome. The vararg feature permits a special, more convenient syntax for the case in which the last argument of a method is an array. To use this feature, we replaceT[]withT...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; } }Now the method may be invoked as follows:List<Integer> ints = Lists.toList(1, 2, 3); List<String> words = Lists.toList("hello", "world");This is just shorthand for what we wrote above. At run time, the arguments are packed into an array which is passed to the method, just as previously.Any number of arguments may precede a last vararg argument. Here is a method that accepts a list and adds all the additional arguments to the end of the list:public static <T> void addAll(List<T> list, T... arr) { for (T elt : arr) list.add(elt); }Whenever a vararg is declared, one may either pass a list of arguments to be implicitly packed into an array, or explicitly pass the array directly. Thus, the preceding method may be invoked as follows:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Assertions
- InhaltsvorschauWe clarify our code by liberal use of the
assertstatement. Each occurrence ofassertis followed by a boolean expression that is expected to evaluate totrue. If assertions are enabled and the expression evaluates tofalse, anAssertionErroris thrown, including an indication of where the error occurred. Assertions are enabled by invoking the JVM with the-eaor-enableassertionsflag.We only write assertions that we expect to evaluate totrue. 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.To sum up, we have seen how generics, boxing and unboxing, foreach loops, and varargs work together to make Java code easier to write, having illustrated this through the use of the Collections Framework.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 2: Subtyping and Wildcards
- InhaltsvorschauNow that we've covered the basics, we can start to cover more-advanced features of generics, such as subtyping and wildcards. In this section, we'll review how subtyping works and we'll see how wildcards let you use subtyping in connection with generics. We'll illustrate our points with examples from the Collections Framework.Subtyping is a key feature of object-oriented languages such as Java. In Java, one type is a subtype of another if they are related by an
extendsorimplementsclause. Here are some examples:Integeris a subtype ofNumberDoubleis a subtype ofNumberArrayList<E>is a subtype ofList<E>List<E>is a subtype ofCollection<E>Collection<E>is a subtype ofIterable<E>Subtyping is transitive, meaning that if one type is a subtype of a second, and the second is a subtype of a third, then the first is a subtype of the third. So, from the last two lines in the preceding list, it follows thatList<E>is a subtype ofIterable<E>. If one type is a subtype of another, we also say that the second is aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Subtyping and the Substitution Principle
- InhaltsvorschauSubtyping is a key feature of object-oriented languages such as Java. In Java, one type is a subtype of another if they are related by an
extendsorimplementsclause. Here are some examples:Integeris a subtype ofNumberDoubleis a subtype ofNumberArrayList<E>is a subtype ofList<E>List<E>is a subtype ofCollection<E>Collection<E>is a subtype ofIterable<E>Subtyping is transitive, meaning that if one type is a subtype of a second, and the second is a subtype of a third, then the first is a subtype of the third. So, from the last two lines in the preceding list, it follows thatList<E>is a subtype ofIterable<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 ofObject, andObjectis a supertype of every reference type. We also say, trivially, that every type is a subtype of itself.The Substitution Principle tells us that wherever a value of one type is expected, one may provide a value of any subtype of that type:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Wildcards with extends
- InhaltsvorschauAnother method in the
Collectioninterface isaddAll, which adds all of the members of one collection to another collection:interface Collection<E> { ... public boolean addAll(Collection<? extends E> c); ... }Clearly, given a collection of elements of typeE, it is OK to add all members of another collection with elements of typeE. 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 ofE. The question mark is called a wildcard, since it stands for some type that is a subtype ofE.Here is an example. We create an empty list of numbers, and add to it first a list of integers and then a list of doubles: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]");The first call is permitted becausenumshas typeList<Number>, which is a subtype ofCollection<Number>, andintshas typeList<Integer>, which is a subtype ofCollection<? extends Number>. The second call is similarly permitted. In both calls,Eis taken to beNumber. If the method signature foraddAllhad 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.We can also use wildcards when declaring variables. Here is a variant of the example at the end of the preceding section, changed by adding a wildcard to the second line: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!
Before, the second line caused a compile-time error (becauseList<Integer>is not a subtype ofList<Number>), but the third line was fine (because a double is a number, so you can add a double to aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Wildcards with super
- InhaltsvorschauHere is a method that copies into a destination list all of the elements from a source list, from the convenience class
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)); } }
The quizzical phrase? super Tmeans that the destination list may have elements of any type that is a supertype ofT, just as the source list may have elements of any type that is a subtype ofT.Here is a sample call.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]");As with any generic method, the type parameter may be inferred or may be given explicitly. In this case, there are four possible choices, all of which type-check and all of which have the same effect:Collections.copy(objs, ints); Collections.<Object>copy(objs, ints); Collections.<Number>copy(objs, ints); Collections.<Integer>copy(objs, ints);
The first call leaves the type parameter implicit; it is taken to beInteger, since that is the most specific choice that works. In the third line, the type parameterTis taken to beNumber. The call is permitted becauseobjshas typeList<Object>, which is a subtype ofList<? super Number>(sinceObjectis a supertype ofNumber, as required by thesuper) andintshas typeList<Integer>, which is a subtype ofList<? extends Number>(sinceIntegeris a subtype ofNumber, as required by theextendswildcard).We could also declare the method with several possible signatures.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)
The first of these is too restrictive, as it only permits calls when the destination and source have exactly the same type. The remaining three are equivalent for calls that use implicit type parameters, but differ for explicit type parameters. For the example calls above, the second signature works only when the type parameter isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - The Get and Put Principle
- InhaltsvorschauIt may be good practice to insert wildcards whenever possible, but how do you decide which wildcard to use? Where should you use
extends, where should you usesuper, and where is it inappropriate to use a wildcard at all?Fortunately, a simple principle determines which is appropriate.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.We already saw this principle at work in the signature of thecopymethod:public static <T> void copy(List<? super T> dest, List<? extends T> src)
The method gets values out of the sourcesrc, so it is declared with anextendswildcard, and it puts values into the destinationdst, so it is declared with asuperwildcard.Whenever you use an iterator, you get values out of a structure, so use anextendswildcard. 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; }Since this usesextends, 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;
The first two calls would not be legal ifextendswas not used.Whenever you use theaddmethod, you put values into a structure, so use asuperwildcard. Here is a method that takes a collection of numbers and an integern, and puts the firstnintegers, 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); }Since this usessuper, all of the following calls are legal:List<Integer>Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Arrays
- InhaltsvorschauIt is instructive to compare the treatment of lists and arrays in Java, keeping in mind the Substitution Principle and the Get and Put Principle.In Java, array subtyping is covariant, meaning that type
S[]is considered to be a subtype ofT[]wheneverSis a subtype ofT. 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!Something is wrong with this program, since it puts a double into an array of integers! Where is the problem? SinceInteger[]is considered a subtype ofNumber[], 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 ofInteger).In contrast, the subtyping relation for generics is invariant, meaning that typeList<S>is not considered to be a subtype ofList<T>, except in the trivial case whereSandTare 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!
SinceList<Integer>is not considered to be a subtype ofList<Number>, the problem is detected on the second line, not the third, and it is detected at compile time, not run time.Wildcards reintroduce covariant subtyping for generics, in that typeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Wildcards Versus Type Parameters
- InhaltsvorschauThe
containsmethod 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.Wildcards Here are the types that the methods have in Java with generics:interface Collection<E> { ... public boolean contains(Object o); public boolean containsAll(Collection<?> c); ... }The first method does not use generics at all! The second method is our first sight of an important abbreviation. The typeCollection<?>stands for:Collection<? extends Object>
ExtendingObjectis one of the most common uses of wildcards, so it makes sense to provide a short form for writing it.These methods let us test for membership and containment: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);The given list of objects contains both the string"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.The testsints.contains(obj)andints.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);
In this case, the object may be contained in the list of integers because it happens to be an integer, and the list of objects may be contained within the list of integers because every object in the list happens to be an integer.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Wildcard Capture
- InhaltsvorschauWhen a generic method is invoked, the type parameter may be chosen to match the unknown type represented by a wildcard. This is called wildcard capture.Consider the method
reversein the convenience classjava.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);
The wildcard signature is slightly shorter and clearer, and is the one used in the library.If you use the second signature, it is easy to implement the method: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)); } }
This copies the argument into a temporary list, and then writes from the copy back into the original in reverse order.If you try to use the first signature with a similar method body, it won't work: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 } }
Now it is not legal to write from the copy back into the original, because we are trying to write from a list of objects into a list of unknown type. ReplacingList<Object>withList<?>won't fix the problem, because now we have two lists with (possibly different) unknown element types.Instead, you can implement the method with the first signature by implementing a private method with the second signature, and calling the second from the first: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)); } }
Here we say that the type variableThas captured the wildcard. This is a generally useful technique when dealing with wildcards, and it is worth knowing.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Restrictions on Wildcards
- InhaltsvorschauWildcards may not appear at the top level in class instance creation expressions (
new), in explicit type parameters in generic method calls, or in supertypes (extendsandimplements).Instance Creation In a class instance creation expression, if the type is a parameterized type, then none of the type parameters may be wildcards. For example, the following are illegal:List<?> list = new ArrayList<?>(); // compile-time error Map<String, ? extends Number> map = new HashMap<String, ? extends Number>(); // compile-time error
This is usually not a hardship. The Get and Put Principle tells us that if a structure contains a wildcard, we should only get values out of it (if it is anextendswildcard) or only put values into it (if it is asuperwildcard). 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();
Here wildcards appear in the second and third lines, but not in the first line that creates the list.Only top-level parameters in instance creation are prohibited from containing wildcards. Nested wildcards are permitted. Hence, the following is legal: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]]");Even though the list of lists is created at a wildcard type, each individual list within it has a specific type: the first is a list of integers and the second is a list of strings. The wildcard type prohibits us from extracting elements from the inner lists at any type other thanObject, but since that is the type used bytoString, this code is well typed.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 3: Comparison and Bounds
- InhaltsvorschauNow that we have the basics, let's look at some more-advanced uses of generics. This chapter describes the interfaces
Comparable<T>andComparator<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 theComparable<T>interface.The interfaceComparable<T>contains a single method that can be used to compare one object to another:interface Comparable<T> { public int compareTo(T o); }ThecompareTomethod 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 implementsComparable, the ordering specified by this interface is called the natural ordering for that class.Typically, an object belonging to a class can only be compared with an object belonging to the same class. For instance,IntegerimplementsComparable<Integer>:Integer int0 = 0; Integer int1 = 1; assert int0.compareTo(int1) < 0;
The comparison returns a negative number, since0precedes1under numerical ordering. Similarly,StringimplementsComparable<String>:String str0 = "zero"; String str1 = "one"; assert str0.compareTo(str1) > 0;
This comparison returns a positive number, since"zero"follows"one"under alphabetic ordering.The type parameter to the interface allows nonsensical comparisons to be caught at compile time:Integer i = 0; String s = "one"; assert i.compareTo(s) < 0; // compile-time errorYou can compare an integer with an integer or a string with a string, but attempting to compare an integer with a string signals a compile-time error.Comparison is not supported between arbitrary numerical types:Number m = new Integer(2); Number n = new Double(3.14); assert m.compareTo(n) < 0;
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparable
- InhaltsvorschauThe 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); }ThecompareTomethod 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 implementsComparable, the ordering specified by this interface is called the natural ordering for that class.Typically, an object belonging to a class can only be compared with an object belonging to the same class. For instance,IntegerimplementsComparable<Integer>:Integer int0 = 0; Integer int1 = 1; assert int0.compareTo(int1) < 0;
The comparison returns a negative number, since0precedes1under numerical ordering. Similarly,StringimplementsComparable<String>:String str0 = "zero"; String str1 = "one"; assert str0.compareTo(str1) > 0;
This comparison returns a positive number, since"zero"follows"one"under alphabetic ordering.The type parameter to the interface allows nonsensical comparisons to be caught at compile time:Integer i = 0; String s = "one"; assert i.compareTo(s) < 0; // compile-time errorYou can compare an integer with an integer or a string with a string, but attempting to compare an integer with a string signals a compile-time error.Comparison is not supported between arbitrary numerical types:Number m = new Integer(2); Number n = new Double(3.14); assert m.compareTo(n) < 0; // compile-time errorHere the comparison is illegal, because theNumberclass does not implement theCompar-ableinterface.Consistent with Equals Usually, we require that two objects are equal if and only if they compare as the same:x.equals(y) if and only if x.compareTo(y) == 0In this case, we say that the natural ordering is consistent with equals.It is recommended that when designing a class you choose a natural ordering that is consistent with equals. This is particularly important if you use the interfacesEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Maximum of a Collection
- InhaltsvorschauIn this section, we show how to use the
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.Here is code to find the maximum element in a nonempty collection, from the classCollections: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; }We first saw generic methods that declare new type variables in the signature in Section 1.4. For instance, the methodasListtakes an array of typeE[]and returns a result of typeList<E>, and does so for any typeE. Here we have a generic method that declares a bound on the type variable. The methodmaxtakes a collection of typeCollection<T>and returns aT, and it does this for any typeTsuch thatTis a subtype ofComparable<T>.The highlighted phrase in angle brackets at the beginning of the type signature declares the type variableT, and we say thatTis bounded byComparable<T>. As with wildcards, bounds for type variables are always indicated by the keywordextends, even when the bound is an interface rather than a class, as is the case here. Unlike wildcards, type variables must always be bounded usingextends, neversuper.In this case, the bound is recursive, in that the bound onTitself depends uponT. It is even possible to have mutually recursive bounds, such as:<T extends C<T,U>, U extends D<T,U>>
An example of mutually recursive bounds appears in Section 9.5.The method body chooses the first element in the collection as a candidate for the maximum, and then compares the candidate with each element in the collection, setting the candidate to the element when the element is larger. We useiterator().next()rather thanget(0)to get the first element, becauseEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Fruity Example
- InhaltsvorschauThe
Comparable<T>interface gives fine control over what can and cannot be compared. Say that we have aFruitclass with subclassesAppleandOrange. Depending on how we set things up, we may prohibit comparison of apples with oranges or we may permit such comparison.Example 3.2 prohibits comparison of apples with oranges. Here are the three classes it declares:class Fruit {...} class Apple extends Fruit implements Comparable<Apple> {...} class Orange extends Fruit implements Comparable<Orange> {...}Each fruit has a name and a size, and two fruits are equal if they have the same name and the same size. Following good practice, we have also defined a hash method, to ensure that equal objects have the same hash code. Apples are compared by comparing their sizes, and so are oranges. SinceAppleimplementsComparable<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.Example 3.1 permits comparison of apples with oranges. Compare these three class declarations with those given previously (all differences between Examples 3.2 and 3.1 are highlighted):Example 3-1. Permitting comparison of apples with orangesabstract 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);
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparator
- InhaltsvorschauSometimes we want to compare objects that do not implement the
Comparableinterface, or to compare objects using a different ordering from the one specified by that interface. The ordering provided by theComparableinterface is called the natural ordering, so theComparatorinterface provides, so to speak, an unnatural ordering.We specify additional orderings using theComparatorinterface, which contains a single method:interface Comparator<T> { public int compare(T o1, T o2); }Thecomparemethod 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 withcompareTo.Here is a comparator that considers the shorter of two strings to be smaller. Only if two strings have the same length are they compared using the natural (alphabetic) ordering.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) ; } };Here is an example:assert "two".compareTo("three") > 0; assert sizeOrder.compare("two","three") < 0;In the natural alphabetic ordering,"two"is greater than"three", whereas in the size ordering it is smaller.The Java libraries always provide a choice betweenComparableandComparator. For every generic method with a type variable bounded byComparable, there is another generic method with an additional argument of typeComparator. For instance, corresponding to:public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)we also have:public static <T> T max(Collection<? extends T> coll, Comparator<? super T> cmp)There are similar methods to find the minimum. For example, here is how to find the maximum and minimum of a list using the natural ordering and using the size ordering:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Enumerated Types
- InhaltsvorschauJava 5 includes support for enumerated types. Here is a simple example:
enum Season { WINTER, SPRING, SUMMER, FALL }Each enumerated type declaration can be expanded into a corresponding class in a stylized way. The corresponding class is designed so that it has exactly one instance for each of the enumerated constants, bound to a suitable static final variable. For example, theenumdeclaration above expands into a class calledSeason. Exactly four instances of this class exist, bound to four static final variables with the namesWINTER,SPRING,SUMMER, andFALL.Each class that corresponds to an enumerated type is a subclass ofjava.lang.Enum. Its definition in the Java documentation begins like this:class Enum<E extends Enum<E>>
You may find this frightening at first sight—both of us certainly did! But don't panic. Actually, we've already seen something similar. The worrying phraseE extends Enum<E>is a lot like the phraseT extends Comparable<T>that we encountered in the definition ofmax(see Section 3.2), and we'll see that they appear for related reasons.To understand what's going on, we need to take a look at the code. Example 3.4 shows the base classEnumand Example 3.5 shows the classSeasonthat corresponds to the enumerated type declaration above. (The code forEnumfollows the source in the Java library, but we have simplified a few points.)Example 3-4. Base class for enumerated typespublic 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; } }Example 3-5. Class corresponding to an enumerated type// 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(); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Multiple Bounds
- InhaltsvorschauWe have seen many examples where a type variable or wildcard is bounded by a single class or interface. In rare situations, it may be desirable to have multiple bounds, and we show how to do so here.To demonstrate, we use three interfaces from the Java library. The
Readableinterface has areadmethod to read into a buffer from a source, theAppendableinterface has anappendmethod to copy from a buffer into a target, and theCloseableinterface has aclosemethod to close a source or target. Possible sources and targets include files, buffers, streams, and so on.For maximum flexibility, we might want to write acopymethod that takes any source that implements bothReadableandCloseableand any target that implements bothAppendableandCloseable:public static <S extends Readable & Closeable, T extends Appendable & Closeable> void copy(S src, T trg, int size) throws IOException { CharBuffer buf = CharBuffer.allocate(size); int i = src.read(buf); while (i >= 0) { buf.flip(); // prepare buffer for writing trg.append(buf); buf.clear(); // prepare buffer for reading i = src.read(buf); } src.close(); trg.close(); }
This method repeatedly reads from the source into a buffer and appends from the buffer into a target. When the source is empty, it closes both the source and the target. The first line specifies thatSranges over any type that implements bothReadableandCloseable, and thatTranges over any type that implementsAppendableandCloseable. When multiple bounds on a type variable appear, they are separated by ampersands. (You cannot use a comma, since that is already used to separate declarations of type variables.)For example, this method may be called with two files as source and target, or with the same two files wrapped in buffers as source and target:int size = 32; FileReader r = new FileReader("file.in"); FileWriter w = new FileWriter("file.out"); copy(r,w,size); BufferedReader br = new BufferedReader(new FileReader("file.in")); BufferedWriter bw = new BufferedWriter(new FileWriter("file.out")); copy(br,bw,size);Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Bridges
- InhaltsvorschauAs we mentioned earlier, generics are implemented by erasure: when you write code with generics, it compiles in almost exactly the same way as the code you would have written without generics. In the case of a parameterized interface such as
Comparable<T>, this may cause additional methods to be inserted by the compiler; these additional methods are called bridges.Example 3.6 shows theComparableinterface and a simplified version of theIntegerclass in Java before generics. In the nongeneric interface, thecompareTomethod takes an argument of typeObject. In the nongeneric class, there are twocompareTomethods. The first is the naïve method you might expect, to compare an integer with another integer. The second compares an integer with an arbitrary object: it casts the object to an integer and calls the first method. The second method is necessary in order to override thecompareTomethod in theComparableinterface, because overriding occurs only when the method signatures are identical. This second method is called a bridge.Example 3-6. Legacy code for comparable integersinterface Comparable { public int compareTo(Object o); } class Integer implements Comparable { private final int value; public Integer(int value) { this.value = value; } public int compareTo(Object o) { return compareTo((Integer)o); } public int compareTo(Integer i) { return (value < i.value) ? −1 : (value == i.value) ? 0 : 1; } }Example 3.7 shows what happens when theComparableinterface and theIntegerclass are generified. In the generic interface, thecompareTomethod takes an argument of typeT. In the generic class, a singlecompareTomethod takes an argument of typeInteger. The bridge method is generated automatically by the compiler. Indeed, the compiled version of the code for both examples is essentially identical.Example 3-7. Generic code for comparable integersinterface Comparable<T> { public int compareTo(T o); } class Integer implements Comparable<Integer> { private final int value; public Integer(int value) { this.value = value; } public intEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Covariant Overriding
- InhaltsvorschauJava 5 supports covariant method overriding. This feature is not directly related to generics, but we mention it here because it is worth knowing, and because it is implemented using a bridging technique like that described in the previous section.In Java 1.4 and earlier, one method can override another only if the signatures match exactly. In Java 5, a method can override another if the arguments match exactly but the return type of the overriding method is a subtype of the return type of the other method.The
clonemethod of classObjectillustrates the advantages of covariant overriding:class Object { ... public Object clone() { ... } }In Java 1.4, any class that overridesclonemust give it exactly the same return type, namelyObject:class Point { public int x; public int y; public Point(int x, int y) { this.x=x; this.y=y; } public Object clone() { return new Point(x,y); } }Here, even thoughclonealways returns aPoint, the rules require it to have the return typeObject. This is annoying, since every invocation ofclonemust cast its result.Point p = new Point(1,2); Point q = (Point)p.clone();In Java 5, it is possible to give theclonemethod a return type that is more to the point:class Point { public int x; public int y; public Point(int x, int y) { this.x=x; this.y=y; } public Point clone() { return new Point(x,y); } }Now we may clone without a cast:Point p = new Point(1,2); Point q = p.clone();
Covariant overriding is implemented using the bridging technique described in the previous section. As before, you can see the bridge if you apply reflection. Here is code that finds all methods with the nameclonein the classPoint:for (Method m : Point.class.getMethods()) if (m.getName().equals("clone")) System.out.println(m.toGenericString());Running this code on the covariant version of thePointclass produces the following output:public Point Point.clone() public bridge java.lang.Object Point.clone()
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 4: Declarations
- InhaltsvorschauThis chapter discusses how to declare a generic class. It describes constructors, static members, and nested classes, and it fills in some details of how erasure works.In a generic class, type parameters appear in the header that declares the class, but not in the constructor:
class Pair<T, U> { private final T first; private final U second; public Pair(T first, U second) { this.first=first; this.second=second; } public T getFirst() { return first; } public U getSecond() { return second; } }
The type parametersTandUare declared at the beginning of the class, not in the constructor. However, actual type parameters are passed to the constructor whenever it is invoked:Pair<String, Integer> pair = new Pair<String, Integer>("one",2); assert pair.getFirst().equals("one") && pair.getSecond() == 2;Look Out for This! A common mistake is to forget the type parameters when invoking the constructor:Pair<String, Integer> pair = new Pair("one",2);This mistake produces a warning, but not an error. It is taken to be legal, becausePairis treated as a raw type, but conversion from a raw type to the corresponding parameterized type generates an unchecked warning; see Section 5.3, which explains how the-Xlint:uncheckedflag can help you spot errors of this kind.Because generics are compiled by erasure, at run time the classesList<Integer>,List<String>, andList<List<String>>are all implemented by a single class, namelyList. You can see this using reflection:List<Integer> ints = Arrays.asList(1,2,3); List<String> strings = Arrays.asList("one","two"); assert ints.getClass() == strings.getClass();Here the class associated with a list of integers at run time is the same as the class associated with a list of strings.One consequence is that static members of a generic class are shared across all instantiations of that class, including instantiations at different types. Static members of a class cannot refer to the type parameter of a generic class, and when accessing a static member the class name should not be parameterized.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Constructors
- InhaltsvorschauIn a generic class, type parameters appear in the header that declares the class, but not in the constructor:
class Pair<T, U> { private final T first; private final U second; public Pair(T first, U second) { this.first=first; this.second=second; } public T getFirst() { return first; } public U getSecond() { return second; } }
The type parametersTandUare declared at the beginning of the class, not in the constructor. However, actual type parameters are passed to the constructor whenever it is invoked:Pair<String, Integer> pair = new Pair<String, Integer>("one",2); assert pair.getFirst().equals("one") && pair.getSecond() == 2;Look Out for This! A common mistake is to forget the type parameters when invoking the constructor:Pair<String, Integer> pair = new Pair("one",2);This mistake produces a warning, but not an error. It is taken to be legal, becausePairis treated as a raw type, but conversion from a raw type to the corresponding parameterized type generates an unchecked warning; see Section 5.3, which explains how the-Xlint:uncheckedflag can help you spot errors of this kind.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Static Members
- InhaltsvorschauBecause generics are compiled by erasure, at run time the classes
List<Integer>,List<String>, andList<List<String>>are all implemented by a single class, namelyList. You can see this using reflection:List<Integer> ints = Arrays.asList(1,2,3); List<String> strings = Arrays.asList("one","two"); assert ints.getClass() == strings.getClass();Here the class associated with a list of integers at run time is the same as the class associated with a list of strings.One consequence is that static members of a generic class are shared across all instantiations of that class, including instantiations at different types. Static members of a class cannot refer to the type parameter of a generic class, and when accessing a static member the class name should not be parameterized.For example, here is a class,Cell<T>, in which each cell has an integer identifier and a value of typeT:class Cell<T> { private final int id; private final T value; private static int count = 0; private static synchronized int nextId() { return count++; } public Cell(T value) { this.value=value; id=nextId(); } public T getValue() { return value; } public int getId() { return id; } public static int getCount() { return count; } }A static field,count, is used to allocate a distinct identifier to each cell. The staticnextIdmethod is synchronized to ensure that unique identifiers are generated even in the presence of multiple threads. The staticgetCountmethod returns the current count.Here is code that allocates a cell containing a string and a cell containing an integer, which are allocated the identifiers0and1, respectively:Cell<String> a = new Cell<String>("one"); Cell<Integer> b = new Cell<Integer>(2); assert a.getId() == 0 && b.getId() == 1 && Cell.getCount() == 2;Static members are shared across all instantiations of a class, so the same count is incremented when allocating either a string or an integer cell.Because static members are independent of any type parameters, we are not permitted to follow the class name with type parameters when accessing a static member:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Nested Classes
- InhaltsvorschauJava permits nesting one class inside another. If the outer class has type parameters and the inner class is not static, then type parameters of the outer class are visible within the inner class.Example 4.1 shows a class implementing collections as a singly-linked list. The class extends
java.util.AbstractCollection, so it only needs to define the methodssize,add, anditerator. The class contains an inner class,Node, for the list nodes, and an anonymous inner class implementingIterator<E>. The type parameterEis in scope within both of these classes.Example 4-1. Type parameters are in scope for nested, nonstatic classespublic class LinkedCollection<E> extends AbstractCollection<E> { private class Node { private E element; private Node next = null; private Node(E elt) { element = elt; } } private Node first = new Node(null); private Node last = first; private int size = 0; public LinkedCollection() {} public LinkedCollection(Collection<? extends E> c) { addAll(c); } public int size() { return size; } public boolean add(E elt) { last.next = new Node(elt); last = last.next; size++; return true; } public Iterator<E> iterator() { return new Iterator<E>() { private Node current = first; public boolean hasNext() { return current.next != null; } public E next() { if (current.next != null) { current = current.next; return current.element; } else throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } }; } }For contrast, Example 4.2 shows a similar implementation, but this time the nestedNodeclass is static, and so the type parameterEis not in scope for this class. Instead, the nested class is declared with its own type parameter,T. Where the previous version referred toNode, the new version refers toNode<E>. The anonymous iterator class in the preceding example has also been replaced by a nested static class, again with its own type parameter.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - How Erasure Works
- InhaltsvorschauThe erasure of a type is defined as follows: drop all type parameters from parameterized types, and replace any type variable with the erasure of its bound, or with
Objectif it has no bound, or with the erasure of the leftmost bound if it has multiple bounds. Here are some examples:-
The erasure of
List<Integer>,List<String>, andList<List<String>>isList. -
The erasure of
List<Integer>[]isList[]. -
The erasure of
Listis itself, similarly for any raw type (see Section 5.3 for an explanation of raw types). -
The erasure of
intis itself, similarly for any primitive type. -
The erasure of
Integeris itself, similarly for any type without type parameters. -
The erasure of
Tin the definition ofasList(see Section 1.4) isObject, becauseThas no bound. -
The erasure of
Tin the definition ofmax(see Section 3.2) isComparable, becauseThas boundComparable<? super T>. -
The erasure of
Tin the final definition ofmax(see Section 3.6) isObject, becauseThas boundObject & Comparable<T>and we take the erasure of the leftmost bound. -
The erasures of
SandTin the definition ofcopy(see Section 3.6) areAppendableandReadable, becauseShas boundAppendable & CloseableandThas boundReadable & Closeable. -
The erasure of
LinkedCollection<E>.NodeorLinkedCollection.Node<E>(see Section 4.3) isLinkedCollection.Node.
In Java, two distinct methods cannot have the same signature. Since generics are implemented by erasure, it also follows that two distinct methods cannot have signatures with the same erasure. A class cannot overload two methods whose signatures have the same erasure, and a class cannot implement two interfaces that have the same erasure.For example, here is a class with two convenience methods. One adds together every integer in a list of integers, and the other concatenates together every string in a list of strings:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Chapter 5: Evolution, Not Revolution
- InhaltsvorschauOne motto underpinning the design of generics for Java is evolution, not revolution. It must be possible to migrate a large, existing body of code to use generics gradually (evolution) without requiring a radical, all-at-once change (revolution). The generics design ensures that old code compiles against the new Java libraries, avoiding the unfortunate situation in which half of your code needs old libraries and half of your code needs new libraries.The requirements for evolution are much stronger than the usual backward compatibility. With simple backward compatibility, one would supply both legacy and generic versions for each application; this is exactly what happens in C#, for example. If you are building on top of code supplied by multiple suppliers, some of whom use legacy collections and some of whom use generic collections, this might rapidly lead to a versioning nightmare.What we require is that the same client code works with both the legacy and generic versions of a library. This means that the supplier and clients of a library can make completely independent choices about when to move from legacy to generic code. This is a much stronger requirement than backward compatibility; it is called migration compatibility or platform compatibility.Java implements generics via erasure, which ensures that legacy and generic versions usually generate identical class files, save for some auxiliary information about types. It is possible to replace a legacy class file by a generic class file without changing, or even recompiling, any client code; this is called binary compatibility.We summarize this with the motto binary compatibility ensures migration compatibility—or, more concisely, erasure eases evolution.This section shows how to add generics to existing code; it considers a small example, a library for stacks that extends the Collections Framework, together with an associated client. We begin with the legacy stack library and client (written for Java before generics), and then present the corresponding generic library and client (written for Java with generics). Our example code is small, so it is easy to update to generics all in one go, but in practice the library and client will be much larger, and we may want to evolve them separately. This is aided byEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Legacy Library with Legacy Client
- InhaltsvorschauWe begin with a simple library of stacks and an associated client, as presented in Example 5.1. This is legacy code, written for Java 1.4 and its version of the Collections Framework. Like the Collections Framework, we structure the library as an interface
Stack(analogous toList), an implementation classArrayStack(analogous toArrayList), and a utility classStacks(analogous toCollections). The interfaceStackprovides just three methods:empty, push, andpop. The implementation classArrayStackprovides a single constructor with no arguments, and implements the methodsempty, push, andpopusing methodssize, add, andremoveon lists. The body ofpopcould be shorter—instead of assigning the value to the variable, it could be returned directly—but it will be interesting to see how the type of the variable changes as the code evolves. The utility class provides just one method,reverse, which repeatedly pops from one stack and pushes onto another.Example 5-1. Legacy library with legacy clientl/Stack.java: interface Stack { public boolean empty(); public void push(Object elt); public Object pop(); } l/ArrayStack.java: import java.util.*; class ArrayStack implements Stack { private List list; public ArrayStack() { list = new ArrayList(); } public boolean empty() { return list.size() == 0; } public void push(Object elt) { list.add(elt); } public Object pop() { Object elt = list.remove(list.size()-1); return elt; } public String toString() { return "stack"+list.toString(); } } l/Stacks.java: class Stacks { public static Stack reverse(Stack in) { Stack out = new ArrayStack(); while (!in.empty()) { Object elt = in.pop(); out.push(elt); } return out; } } l/Client.java: class Client { public static void main(String[] args) { Stack stack = new ArrayStack(); for (int i = 0; i<4; i++) stack.push(new Integer(i)); assert stack.toString().equals("stack[0, 1, 2, 3]"); int top =
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generic Library with Generic Client
- InhaltsvorschauNext, we update the library and client to use generics, as presented in Example 5.2. This is generic code, written for Java 5 and its version of the Collections Framework. The interface now takes a type parameter, becoming
Stack<E>(analogous toList<E>), and so does the implementing class, becomingArrayStack<E>(analogous toArrayList<E>), but no type parameter is added to the utility classStacks(analogous toCollections). The typeObjectin the signatures and bodies ofpushandpopis replaced by the type parameterE. Note that the constructor inArrayStackdoes not require a type parameter. In the utility class, thereversemethod becomes a generic method with argument and result of typeStack<T>. Appropriate type parameters are added to the client, and boxing and unboxing are now implicit.Example 5-2. Generic library with generic clientg/Stack.java: interface Stack<E> { public boolean empty(); public void push(E elt); public E pop(); } g/ArrayStack.java: import java.util.*; class ArrayStack<E> implements Stack<E> { private List<E> list; public ArrayStack() { list = new ArrayList<E>(); } public boolean empty() { return list.size() == 0; } public void push(E elt) { list.add(elt); } public E pop() { E elt = list.remove(list.size()-1); return elt; } public String toString() { return "stack"+list.toString(); } } g/Stacks.java: class Stacks { public static <T> Stack<T> reverse(Stack<T> in) { Stack<T> out = new ArrayStack<T>(); while (!in.empty()) { T elt = in.pop(); out.push(elt); } return out; } } g/Client.java: class Client { public static void main(String[] args) { Stack<Integer> stack = new ArrayStack<Integer>(); for (int i = 0; i<4; i++) stack.push(i); assert stack.toString().equals("stack[0, 1, 2, 3]"); int top = stack.pop(); assert top == 3 && stack.toString().equals("stack[0, 1, 2]"); Stack<Integer> reverse = Stacks.reverse(stack); assert stack.empty(); assert reverse.toString().equals("stack[2, 1, 0]"); } }
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generic Library with Legacy Client
- InhaltsvorschauNow let's consider the case where the library is updated to generics while the client remains in its legacy version. This may occur because there is not enough time to convert everything all at once, or because the library and client are controlled by different organizations. This corresponds to the most important case of backward compatibility, where the generic Collections Framework of Java 5 must still work with legacy clients written against the Collections Framework in Java 1.4.In order to support evolution, whenever a parameterized type is defined, Java also recognizes the corresponding unparameterized version of the type, called a raw type. For instance, the parameterized type
Stack<E>corresponds to the raw typeStack, and the parameterized typeArrayStack<E>corresponds to the raw typeArrayStack.Every parameterized type is a subtype of the corresponding raw type, so a value of the parameterized type can be passed where a raw type is expected. Usually, it is an error to pass a value of a supertype where a value of its subtype is expected, but Java does permit a value of a raw type to be passed where a parameterized type is expected—however, it flags this circumstance by generating an unchecked conversion warning. For instance, you can assign a value of typeStack<E>to a variable of typeStack, since the former is a subtype of the latter. You can also assign a value of typeStackto a variable of typeStack<E>, but this will generate an unchecked conversion warning.To be specific, consider compiling the generic source forStack<E>, ArrayStack<E>, andStacksfrom Example 5.2 (say, in directoryg) with the legacy source forClientfrom Example 5.1 (say, in directoryl). Sun's Java 5 compiler yields the following message:% javac g/Stack.java g/ArrayStack.java g/Stacks.java l/Client.java Note: Client.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
The unchecked warning indicates that the compiler cannot offer the same safety guarantees that are possible when generics are used uniformly throughout. However, when the generic code is generated by updating legacy code, we know that equivalent class files are produced from both, and hence (despite the unchecked warning) running a legacy client with the generic library will yield the same result as running the legacy client with the legacy library. Here we assume that the only change in updating the library was to introduce generics, and that no change to the behavior was introduced, either on purpose or by mistake.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Legacy Library with Generic Client
- InhaltsvorschauIt usually makes sense to update the library before the client, but there may be cases when you wish to do it the other way around. For instance, you may be responsible for maintaining the client but not the library; or the library may be large, so you may want to update it gradually rather than all at once; or you may have class files for the library, but no source.In such cases, it makes sense to update the library to use parameterized types in its method signatures, but not to change the method bodies. There are three ways to do this: by making minimal changes to the source, by creating stub files, or by use of wrappers. We recommend use of minimal changes when you have access to source and use of stubs when you have access only to class files, and we recommend against use of wrappers.The minimal changes technique is shown in Example 5.3. Here the source of the library has been edited, but only to change method signatures, not method bodies. The exact changes required are highlighed in boldface. This is the recommended technique for evolving a library to be generic when you have access to the source.Example 5-3. Evolving a library using minimal changes
m/Stack.java: interface Stack<E> { public boolean empty(); public void push(E elt); public E pop(); } m/ArrayStack.java: @SuppressWarnings("unchecked") class ArrayStack<E> implements Stack<E> { private List list; public ArrayStack() { list = new ArrayList(); } public boolean empty() { return list.size() == 0; } public void push(E elt) { list.add(elt); } // unchecked call public E pop() { Object elt = list.remove(list.size()-1); return (E)elt; // unchecked cast } public String toString() { return "stack"+list.toString(); } } m/Stacks.java: @SuppressWarnings("unchecked") class Stacks { public static <T> Stack<T> reverse(Stack<T> in) { Stack out = new ArrayStack(); while (!in.empty()) { Object elt = in.pop(); out.push(elt);
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Conclusions
- InhaltsvorschauTo review, we have seen both generic and legacy versions of a library and client. These generate equivalent class files, which greatly eases evolution. You can use a generic library with a legacy client, or a legacy library with a generic client. In the latter case, you can update the legacy library with generic method signatures, either by minimal changes to the source or by use of stub files.The foundation stone that supports all this is the decision to implement generics by erasure, so that generic code generates essentially the same class files as legacy code—a property referred to as binary compatibility. Usually, adding generics in a natural way causes the legacy and generic versions to be binary compatible. However, there are some corner cases where caution is required; these are discussed in Section 8.4.It is interesting to compare the design of generics in Java and in C#. In Java, generic types do not carry information about type parameters at run time, whereas arrays do contain information about the array element type at run time. In C#, both generic types and arrays contain information about parameter and element types at run time. Each approach has advantages and disadvantages. In the next chapter, we will discuss problems with casting and arrays that arise because Java does not reify information about type parameters, and these problems do not arise in C#. On the other hand, evolution in C# is much more difficult. Legacy and generic collection classes are completely distinct, and any attempt to combine legacy collections and generic collections will encounter the difficulties with wrappers discussed earlier. In contrast, as we've seen, evolution in Java is straightforward.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Chapter 6: Reification
- InhaltsvorschauThe Oxford English Dictionary defines reify thus: "To convert mentally into a thing; to materialize." A plainer word with the same meaning is thingify. In computing, reification has come to mean an explicit representation of a type—that is, run-time type information. In Java, arrays reify information about their component types, while generic types do not reify information about their type parameters.The previous chapter was, in a sense, about the advantages of not reifying parameter types. Legacy code makes no distinction between
List<Integer>andList<String>andList<List<String>>, so not reifying parameter types is essential to easing evolution and promoting compatibility between legacy code and new code.But now the time has come to pay the piper. Reification plays a critical role in certain aspects of Java, and the absence of reification that is beneficial for evolution also necessarily leads to some rough edges. This chapter warns you of the limitations and describes some workarounds. The chapter deals almost entirely with things you might wish you didn't need to know—and, indeed, if you never use generic types in casts, instance tests, exceptions, or arrays then you are unlikely to need the material covered here.We begin with a precise definition of what it means for a type in Java to be reifiable. We then consider corner cases related to reification, including instance tests and casts, exceptions, and arrays. The fit between arrays and generics is the worst rough corner in the language, and we encapsulate how to avoid the worst pitfalls with the Principle of Truth in Advertising and the Principle of Indecent Exposure.In Java, the type of an array is reified with its component type, while the type of a parameterized type is reified without its type parameters. For instance, an array of numbers will carry the reified typeNumber[], while a list of numbers will carry the reified typeArrayList, notArrayList<Number>; the raw type, not the parameterized type, is reified. Of course, each element of the list will have a reified type attached to it—sayEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reifiable Types
- InhaltsvorschauIn Java, the type of an array is reified with its component type, while the type of a parameterized type is reified without its type parameters. For instance, an array of numbers will carry the reified type
Number[], while a list of numbers will carry the reified typeArrayList, notArrayList<Number>; the raw type, not the parameterized type, is reified. Of course, each element of the list will have a reified type attached to it—sayIntegerorDouble—but this is not the same as reifying the parameter type. If every element in the list was an integer, we would not be able to tell whether we had anArrayList<Integer>,ArrayList<Number>, orArrayList<Object>; if the list was empty, we would not be able to tell what kind of empty list it was.In Java, we say that a type is reifiable if the type is completely represented at run time — that is, if erasure does not remove any useful information. To be precise, a type is reifiable if it is one of the following:-
A primitive type(such as
int) -
A nonparameterized class or interface type(such as
Number,String, orRunnable) -
A parameterized type in which all type arguments are unbounded wildcards(such as
List<?>,ArrayList<?>, orMap<?, ?>) -
A raw type(such as
List,ArrayList, orMap) -
An array whose component type is reifiable(such as
int[],Number[],List<?>[],List[], orint[][])
A type is not reifiable if it is one of the following:-
A type variable(such as
T) -
A parameterized type with actual parameters(such as
List<Number>,ArrayList<String>, orMap<String, Integer>) -
A parameterized type with a bound(such as
List<? extends Number>orComparable<? super String>)
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Instance Tests and Casts
- InhaltsvorschauInstance tests and casts depend on examining types at run time, and hence depend on reification. For this reason, an instance test against a type that is not reifiable reports an error, and a cast to a type that is not reifiable usually issues a warning.As an example, consider the use of instance tests and casts in writing equality. Here is a fragment of the definition of the class
Integerinjava.lang(slightly simplified from the actual source):public class Integer extends Number { private final int value; public Integer(int value) { this.value=value; } public int intValue() { return value; } public boolean equals(Object o) { if (o instanceof Integer) { return value == ((Integer)o).intValue(); } else return false; } ... }The equality method takes an argument of typeObject, checks whether the object is an instance of classInteger, and, if so, casts it toIntegerand compares the values of the two integers. This code works becauseIntegeris a reifiable type: all of the information needed to check whether an object is an instance ofIntegeris available at run time.Now consider how one might define equality on lists, as in the classAbstractListinjava.util. A natural—but incorrect—way to define this is as follows:import java.util.*; public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> { public boolean equals(Object o) { if (o instanceof List<E>) { // compile-time error Iterator<E> it1 = iterator(); Iterator<E> it2 = ((List<E>)o).iterator(); // unchecked cast while (it1.hasNext() && it2.hasNext()) { E e1 = it1.next(); E e2 = it2.next(); if (!(e1 == null ? e2 == null : e1.equals(e2))) return false; } return !it1.hasNext() && !it2.hasNext(); } else return false; } ... }Again, the equality method takes an argument of typeObject, checks whether the object is an instance of typeList<E>, and, if so, casts it toList<E>and compares corresponding elements of the two lists. This code doesEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Exception Handling
- InhaltsvorschauIn a
trystatement, eachcatchclause checks whether the thrown exception matches a given type. This is the same as the check performed by an instance test, so the same restriction applies: the type must be reifiable. Further, the type in acatchclause is required to be a subclass ofThrowable. Since there is little point in creating a subclass ofThrowablethat cannot appear in acatchclause, the Java compiler complains if you attempt to create a parameterized subclass ofThrowable.For example, here is a permissible definition of a new exception, which contains an integer value:class IntegerException extends Exception { private final int value; public IntegerException(int value) { this.value = value; } public int getValue() { return value; } }And here is a simple example of how to use the exception:class IntegerExceptionTest { public static void main(String[] args) { try { throw new IntegerException(42); } catch (IntegerException e) { assert e.getValue() == 42; } } }The body of thetrystatement throws the exception with a given value, which is caught by thecatchclause.In contrast, the following definition of a new exception is prohibited, because it creates a parameterized type:class ParametricException<T> extends Exception { // compile-time error private final T value; public ParametricException(T value) { this.value = value; } public T getValue() { return value; } }
An attempt to compile the above reports an error:% javac ParametricException.java ParametricException.java:1: a generic class may not extend java.lang.Throwable class ParametricException<T> extends Exception { // compile-time error ^ 1 errorThis restriction is sensible because almost any attempt to catch such an exception must fail, because the type is not reifiable. One might expect a typical use of the exception to be something like the following:class ParametricExceptionTest { public static void main(String[] args) { try { throw new ParametricException<Integer>(42); } catch (Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Array Creation
- InhaltsvorschauArrays reify their component types, meaning that they carry run-time information about the type of their components. This reified type information is used in instance tests and casts, and also used to check whether assignments into array components are permitted. Recall this example from Section 2.5.
Integer[] ints = new Integer[] {1,2,3}; Number[] nums = ints; nums[2] = 3.14; // array store exceptionThe first line allocates a new array, with reified type information indicating that it is an array of integers. The second line assigns this array to a variable containing an array of numbers; this is permitted because arrays, unlike generic types, are covariant. The assignment on the third line raises an array store exception at run time because the assigned value is of typedouble, and this is not compatible with the reified type attached to the array.Because arrays must reify their component types, it is an error to create a new array unless its component type is reifiable. The two main problems you are likely to encounter are when the type of the array is a type variable, and when the type of the array is a parameterized type.Consider the following (incorrect) code to convert a collection to an array:import java.util.*; class Annoying { public static <T> T[] toArray(Collection<T> c) { T[] a = new T[c.size()]; // compile-time error int i=0; for (T x : c) a[i++] = x; return a; } }This is an error, because a type variable is not a reifiable type. An attempt to compile this code reports a generic array creation error:% javac Annoying.java Annoying.java:4: generic array creation T[] a = new T[c.size()]; // compile-time error ^ 1 errorWe discuss workarounds for this problem shortly.As a second example, consider the following (incorrect) code that returns an array containing two lists:import java.util.*; class AlsoAnnoying { public static List<Integer>[] twoLists() { List<Integer> a = Arrays.asList(1,2,3); List<Integer> b = Arrays.asList(4,5,6); returnEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - The Principle of Truth in Advertising
- InhaltsvorschauWe saw in the previous section that a naïve method to convert a collection to an array will not work. The first fix we might try is to add an unchecked cast, but we will see shortly that this leads to even more perplexing problems. The correct fix will require us to resort to reflection. Since the same issues arise when converting any generic structure to an array, it is worth understanding the problems and their solution. We will study variations of the static
toArraymethod from the previous section; the same ideas apply to thetoArraymethod in theCollectioninterface of the Collections Framework.Here is a second attempt to convert a collection to an array, this time using an unchecked cast, and with test code added:import java.util.*; class Wrong { public static <T> T[] toArray(Collection<T> c) { T[] a = (T[])new Object[c.size()]; // unchecked cast int i=0; for (T x : c) a[i++] = x; return a; } public static void main(String[] args) { List<String> strings = Arrays.asList("one","two"); String[] a = toArray(strings); // class cast error } }The code in the previous section used the phrasenew T[c.size()]to create the array, causing the compiler to report a generic array creation error. The new code instead allocates an array of objects and casts it to typeT[], which causes the compiler to issue an unchecked cast warning:% javac -Xlint Wrong.java Wrong.java:4: warning: [unchecked] unchecked cast found : java.lang.Object[] required: T[] T[] a = (T[])new Object[c.size()]; // unchecked cast ^ 1 warningAs you might guess from the name chosen for this program, this warning should not be ignored. Indeed, running this program gives the following result:% java Wrong Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; at Wrong.main(Wrong.java:11)The obscure phrase[Ljava.lang.Objectis the reified type of the array, where[Lindicates that it is an array of reference type, andEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - The Principle of Indecent Exposure
- InhaltsvorschauAlthough it is an error to create an array with a component type that is not reifiable, it is possible to declare an array with such a type and to perform an unchecked cast to such a type. These features must be used with extreme caution, and it is worthwhile to understand what can go wrong if they are not used properly. In particular, a library should never publicly expose an array with a nonreifiable type.Recall that Section 2.5 presents an example of why reification is necessary:
Integer[] ints = new Integer[] {1}; Number[] nums = ints; nums[0] = 1.01; // array store exception int n = ints[0];This assigns an array of integers to an array of numbers, and then attempts to store a double into the array of numbers. The attempt raises an array store exception because of the check against the reified type. This is just as well, since otherwise the last line would attempt to store a double into an integer variable.Here is a similar example, where arrays of numbers are replaced by arrays of lists of numbers:List<Integer>[] intLists = (List<Integer>[])new List[] {Arrays.asList(1)}; // unchecked cast List<? extends Number>[] numLists = intLists; numLists[0] = Arrays.asList(1.01); int n = intLists[0].get(0); // class cast exception!
This assigns an array of lists of integers to an array of lists of numbers, and then attempts to store a list of doubles into the array of lists of numbers. This time the attempted store does not fail, even though it should, because the check against the reified type is inadequate: the reified information contains only the erasure of the type, indicating that it is an array ofList, not an array ofList<Integer>. Hence the store succeeds, and the program fails unexpectedly elsewhere.Example 6.1 presents a similar example, divided into two classes in order to demonstrate how a poorly designed library can create problems for an innocent client. The first class, calledDeceptiveLibrary, defines a static method that returns an array of lists of integers of a given size. Since generic array creation is not permitted, the array is created with components of the raw typeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - How to Define ArrayList
- InhaltsvorschauWe have argued elsewhere that it is usually preferable to use a list than to use an array. There are a few places where this is not appropriate. In rare circumstances, you will need to use an array for reasons of efficiency or compatibility. Also, of course, you need to use arrays to implement
ArrayListitself. Here we present the implementation ofArrayListas a model of what to do in the rare circumstance where you do need to use an array. Such implementations need to be written with care, as they necessarily involve use of unchecked casts. We will see how the Principles of Indecent Exposure and of Truth in Advertising figure in the implementation.Example 6.2 shows the implementation. We have derivedArrayListby subclassing fromAbstractList. Classes derived from this class need to define only four methods, namelyget,set,add, andremove; the other methods are defined in terms of these. We also indicate that the class implementsRandomAccess, indicating that clients of the class will have more efficient access usinggetthan using the list iterator.Example 6-2. How to define ArrayListimport java.util.*; class ArrayList<E> extends AbstractList<E> implements RandomAccess { private E[] arr; private int size = 0; public ArrayList(int cap) { if (cap < 0) throw new IllegalArgumentException("Illegal Capacity: "+cap); arr = (E[])new Object[cap]; // unchecked cast } public ArrayList() { this(10); } public ArrayList(Collection<? extends E> c) { this(c.size()); addAll(c); } public void ensureCapacity(int mincap) { int oldcap = arr.length; if (mincap > oldcap) { int newcap = Math.max(mincap, (oldcap*3)/2+1); E[] oldarr = arr; arr = (E[])new Object[newcap]; // unchecked cast System.arraycopy(oldarr,0,arr,0,size); } } public int size() { return size; } private void checkBounds(int i, int size) { if (i < 0 || i >= size) throw new IndexOutOfBoundsException("Index: "+i+", Size: "+size); } public E get(int i) { checkBounds(i,size); return arr[i]; } public E set(int i, E elt) { checkBounds(i,size); E old = arr[i]; arr[i] = elt; return old; } public void add(int i, E elt) { checkBounds(i,size+1); ensureCapacity(size+1); System.arraycopy(arr,i,arr,i+1,size-i); arr[i] = elt; size++; } public E remove(int i) { checkBounds(i,size); E old = arr[i]; arr[i] = null; size--; System.arraycopy(arr,i+1,arr,i,size-i); return old; } public <T> T[] toArray(T[] a) { if (a.length < size) a =Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Array Creation and Varargs
- InhaltsvorschauThe convenient vararg notation allows methods to accept a variable number of arguments and packs them into an array, as discussed in Section 1.4. This notation is not as convenient as you might like, because the arrays it creates suffer from the same issues involving reification as other arrays.In Section 1.4 we discussed the method
java.util.Arrays.asList, which is declared as follows:public static <E> List<E> asList(E... arr)
For instance, here are three calls to this method:List<Integer> a = Arrays.asList(1, 2, 3); List<Integer> b = Arrays.asList(4, 5, 6); List<List<Integer>> x = Arrays.asList(a, b); // generic array creationRecall that an argument list of variable length is implemented by packing the arguments into an array and passing that. Hence these three calls are equivalent to the following:List<Integer> a = Arrays.asList(new Integer[] { 1, 2, 3 }); List<Integer> b = Arrays.asList(new Integer[] { 4, 5, 6 }); List<List<Integer>> x = Arrays.asList(new List<Integer>[] { a, b }); // generic array creationThe first two calls are fine, but sinceList<Integer>is not a reifiable type, the third warns of an unchecked generic array creation at compile time.VarargError.java:6: warning: [unchecked] unchecked generic array creation of type java.util.List<java.lang.Integer>[] for varargs parameter List<List<Integer>> x = Arrays.asList(a, b);This warning can be confusing, particularly since that line of source code does not contain an explicit instance of array creation!A similar problem occurs if one attempts to create a list of a generic type. Here is a method that usesArrays.asListto create a list of length one containing a given element:public static List<E> singleton(E elt) { return Arrays.asList(elt); // generic array creation }This also generates a warning, which can be confusing for the same reasons.Normally, generic array creation reports an error. As a workaround, one can create the array at a reifiable type and perform an unchecked cast. That workaround is not available for the array creation that is implicit in the use ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Arrays as a Deprecated Type?
- InhaltsvorschauWe have seen that collections are superior to arrays in a number of ways:
-
Collections provide more precise typing than arrays. With lists, one can write
List<T>,List<? extends T>, orList<? super T>; whereas with arrays, one can only writeT[], which corresponds to the second of the three options for lists. More-precise typing enables more errors to be detected at compile time rather than run time. This makes coding, debugging, testing, and maintenance easier, and also improves efficiency. (See Section 2.5.) -
Collections are more flexible than arrays. Collections offer a variety of representations, including arrays, linked lists, trees, and hash tables, whereas arrays have a fixed representation, and the libraries offer a larger variety of methods and convenience algorithms on collections than on arrays. (See Section 2.5.)
-
Collections may have elements of any type, whereas arrays should only have components of reifiable type. When creating an array, one must adhere to the Principle of Truth in Advertising—the reified type must conform to the static type—and the Principle of Indecent Exposure—never publicly expose an array where the components do not have reifiable type. (See Sections 6.5 and 6.6).
In retrospect, there are several places in Java 5 where avoiding the use of arrays might have improved the design:-
Variable-length arguments (varargs) are represented by an array, and so are subject to the same restrictions. If a vararg is bound to actual arguments of nonreifiable type then a generic array creation warning is issued (which raises the same concerns as an unchecked warning). For instance, the function
Arrays.asListtakes a vararg. There is no difficulty in applying this function to return a result of typeList<Integer>, but it is problematic to create a result of typeList<List<Integer>>or of typeList<E>. This problem would not arise if lists had been used in preference to arrays. (See Section 6.8.)
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Summing Up
- InhaltsvorschauWe conclude by giving a checklist of places where reifiable types are required or recommended.
-
An instance test must be against a reifiable type.
-
A cast should usually be to a reifiable type. (A cast to a nonreifiable type usually issues an unchecked warning.)
-
A class that extends
Throwablemust not be parameterized. -
An array instance creation must be at a reifiable type.
-
The reified type of an array must be a subtype of the erasure of its static type (see the Principle of Truth in Advertising), and a publicly exposed array should be of a reifiable type (see the Principle of Indecent Exposure).
-
Varargs should be of a reifiable type. (A vararg of a nonreifiable type will issue an unchecked warning.)
These restrictions arise from the fact that generics are implemented via erasure, and they should be regarded as the price one pays for the ease of evolution that we explored in the previous chapter.For completeness, we also list restrictions connected with reflection:-
Class tokens correspond to reifiable types, and the type parameter in
Class<T>should be a reifiable type. (See Section 7.2.)
These are discussed in the next chapter.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Chapter 7: Reflection
- InhaltsvorschauReflection is the term for a set of features that allows a program to examine its own definition. Reflection in Java plays a role in class browsers, object inspectors, debuggers, interpreters, services such as JavaBeans™ and object serialization, and any tool that creates, inspects, or manipulates arbitrary Java objects on the fly.Reflection has been present in Java since the beginning, but the advent of generics changes reflection in two important ways, introducing both generics!for reflection and reflection for generics.By generics for reflection we mean that some of the types used for reflection are now generic types. In particular, the class
Classbecomes the generic classClass<T>. This may seem confusing at first, but once understood it can make programs using reflection much clearer. Class literals and the methodObject.getClassuse special tricks to return more-precise type information. Generics are used to especially good effect in the reflection of annotations. We observe that the type parameterTinClass<T>should always be bound to a reifiable type, and we present a short library that can help you avoid many common cases of unchecked casts.By reflection for generics we mean that reflection now returns information about generic types. There are new interfaces to represent generic types, including type variables, parameterized types, and wildcard types, and there are new methods that get the generic types of fields, constructors, and methods.We explain each of these points in turn. We don't assume any previous knowledge of reflection, but we focus on the aspects tied to generics.Java has supported facilities for reflection since version 1.0 and class literals since version 1.1. Central to these is the classClass, which represents information about the type of an object at run time. You may write a type followed by.classas a literal that denotes the class token corresponding to that type, and the methodgetClassis defined on every object and returns a class token that represents the reified type information carried by that object at run-time. Here is an example:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generics for Reflection
- InhaltsvorschauJava has supported facilities for reflection since version 1.0 and class literals since version 1.1. Central to these is the class
Class, which represents information about the type of an object at run time. You may write a type followed by.classas a literal that denotes the class token corresponding to that type, and the methodgetClassis defined on every object and returns a class token that represents the reified type information carried by that object at run-time. Here is an example:Class ki = Integer.class; Number n = new Integer(42); Class kn = n.getClass(); assert ki == kn;
For a given class loader, the same type is always represented by the same class token. To emphasize this point, here we compare class tokens using identity (the==operator). However, in practice, it often is more robust to use equality (theequalsmethod).One of the changes in Java 5 is that the classClassnow takes a type parameter, soClass<T>is the type of the class token for the typeT. The preceding code is now written as follows:Class<Integer> ki = Integer.class; Number n = new Integer(42); Class<? extends Number> kn = n.getClass(); assert ki == kn;
Class tokens and thegetClassmethod are treated specially by the compiler. In general, ifTis a type without type parameters, thenT.classhas typeClass<T>, and ifeis an expression of typeTthene.getClass()has typeClass<? extends T>. (We'll see what happens whenTdoes have type parameters in the next section.) The wildcard is needed because the type of the object referred to by the variable may be a subtype of the type of the variable, as in this case, where a variable of typeNumbercontains an object of typeInteger.For many uses of reflection, you won't know the exact type of the class token (if you did, you probably wouldn't need to use reflection), and in those cases you can writeClass<?>for the type, using an unbounded wildcard. However, in some cases the type information provided by the type parameter is invaluable, as in the variant ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reflected Types are Reifiable Types
- InhaltsvorschauReflection makes reified type information available to the program. Of necessity, therefore, each class token corresponds to a reifiable type. If you try to reflect a parameterized type, you get the reified information for the corresponding raw type:
List<Integer> ints = new ArrayList<Integer>(); List<String> strs = new ArrayList<String>(); assert ints.getClass() == strs.getClass(); assert ints.getClass() == ArrayList.class;
Here the type list of integers and the type list of strings are both represented by the same class token, the class literal for which is writtenArrayList.class.Because the class always represents a reifiable type, there is no point in parameterizing the classClasswith a type that is not reifiable. Hence, the two main methods for producing a class with a type parameter, namely thegetClassmethod and class literals, are both designed to yield a reifiable type for the type parameter in all cases.Recall that thegetClassmethod is treated specially by the compiler. In general, if expressionehas typeT, then the expressione.getClass()has typeClass<? extends |T|>, where|T|is the erasure of the typeT. Here's an example:List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class;Here the expressionintshas typeList<Integer>, so the expressionint.getClass()has typeClass<? extends List>; this is the case because erasingList<Integer>yields the raw typeList. The actual value ofkisArrayList.class, which has typeClass<ArrayList>, which is indeed a subtype ofClass<? extends List>.Class literals are also restricted; it is not even syntactically valid to supply a type parameter to the type in a class literal. Thus, the following fragment is illegal:class ClassLiteral { public Class<?> k = List<Integer>.class; // syntax error }Indeed, Java's grammar makes a phrase such as the preceding one difficult to parse, and it may trigger a cascade of syntax errors:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reflection for Primitive Types
- InhaltsvorschauEvery type in Java, including primitive types and array types, has a class literal and a corresponding class token.For instance,
int.classdenotes the class token for the primitive type for integers (this token is also the value of the static fieldInteger.TYPE). The type of this class token cannot beClass<int>, sinceintis not a reference type, so it is taken to beClass<Integer>. Arguably, this is an odd choice, since according to this type you might expect the callsint.class.cast(o)andint.class.newInstance()to return values of typeInteger, but in fact these calls raise an exception. Similarly, you might expect the calljava.lang.reflect.array.newInstance(int.class,size)
to return a value of typeInteger[], but in fact the call returns a value of typeint[]. These examples suggest that it might have made more sense to give the class tokenint.classthe typeClass<?>.On the other hand,int[].classdenotes the class token for arrays with components of the primitive type integer, and the type of this class token isClass<int[]>, which is permitted sinceint[]is a reference type.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - A Generic Reflection Library
- InhaltsvorschauAs we've seen, careless use of unchecked casts can lead to problems, such as violating the Principle of Truth in Advertising or the Principle of Indecent Exposure (see Sections 6.5 and 6.6). One technique to minimize the use of unchecked casts is to encapsulate these within a library. The library can be carefully scrutinized to ensure that its use of unchecked casts is safe, while code that calls the library can be free of unchecked casts. Sun is considering adding library methods similar to the ones described here.Example 7.1 provides a library of generic functions that use reflection in a type-safe way. It defines a convenience class
GenericReflectioncontaining the following methods:public static <T> T newInstance(T object) public static <T> Class<? extends T> getComponentType(T[] a) public static <T> T[] newArray(Class<? extends T> k, int size) public static <T> T[] newArray(T[] a, int size)
Example 7-1. A type-safe library for generic reflectionclass GenericReflection { public static <T> T newInstance(T obj) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException { Object newobj = obj.getClass().getConstructor().newInstance(); return (T)newobj; // unchecked cast } public static <T> Class<? extends T> getComponentType(T[] a) { Class<?> k = a.getClass().getComponentType(); return (Class<? extends T>)k; // unchecked cast } public static <T> T[] newArray(Class<? extends T> k, int size) { if (k.isPrimitive()) throw new IllegalArgumentException ("Argument cannot be primitive: "+k); Object a = java.lang.reflect.Array.newInstance(k, size); return (T[])a; // unchecked cast } public static <T> T[] newArray(T[] a, int size) { return newArray(getComponentType(a), size); } }The first takes an object, finds the class of that object, and returns a new instance of the class; this must have the same type as the original object. The second takes an array and returns a class token for its component type, as carried in its run-time type information. Conversely, the third allocates a new array with its component type specified by a given class token and a specified size. The fourth takes an array and a size, and allocates a new array with the same component type as the given array and the given size; it simply composes calls to the previous two methods. The code for each of the first three methods consists of a call to one or two corresponding methods in the Java reflection library and an unchecked cast to the appropriate return type.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reflection for Generics
- InhaltsvorschauGenerics change the reflection library in two ways. We have discussed generics for reflection, where Java added a type parameter to the class
Class<T>. We now discuss reflection for generics, where Java adds methods and classes that support access to generic types.Example 7.2 shows a simple demonstration of the use of reflection for generics. It uses reflection to find the class associated with a given name, and it prints out the fields, constructors, and methods associated with the class, using the reflection library classesField,Constructor, andMethod. Two different methods are available for converting a field, constructor, or method to a string for printing: the oldtoStringmethod and the newtoGenericStringmethod. The old method is maintained mainly for backward compatibility. A small sample class is shown in Example 7.3, and a sample run with this class is shown in Example 7.4.Example 7-2. Reflection for genericsimport java.lang.reflect.*; import java.util.*; class ReflectionForGenerics { public static void toString(Class<?> k) { System.out.println(k + " (toString)"); for (Field f : k.getDeclaredFields()) System.out.println(f.toString()); for (Constructor c : k.getDeclaredConstructors()) System.out.println(c.toString()); for (Method m : k.getDeclaredMethods()) System.out.println(m.toString()); System.out.println(); } public static void toGenericString(Class<?> k) { System.out.println(k + " (toGenericString)"); for (Field f : k.getDeclaredFields()) System.out.println(f.toGenericString()); for (Constructor c : k.getDeclaredConstructors()) System.out.println(c.toGenericString()); for (Method m : k.getDeclaredMethods()) System.out.println(m.toGenericString()); System.out.println(); } public static void main (String[] args) throws ClassNotFoundException { for (String name : args) { Class<?> k = Class.forName(name); toString(k); toGenericString(k); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reflecting Generic Types
- InhaltsvorschauThe reflection library provides a
Typeinterface to describe a generic type. There is one class that implements this interface and four other interfaces that extend it, corresponding to the five different kinds of types:-
The class
Class, representing a primitive type or raw type -
The interface
ParameterizedType, representing an application of a generic class or interface to parameter types, from which you can extract an array of the parameter types -
The interface
TypeVariable, representing a type variable, from which you can extract the bounds on the type variable -
The interface
GenericArrayType, representing an array, from which you can extract the array component type -
The interface
WildcardType, representing a wildcard, from which you can extract a lower or upper bound on the wildcard
By performing a series of instance tests on each of these interfaces, you may determine which kind of type you have, and print or process the type; we will see an example of this shortly.Methods are available to return the superclass and superinterfaces of a class as types, and to access the generic type of a field, the argument types of a constructor, and the argument and result types of a method.You can also extract the type variables that stand for the formal parameters of a class or interface declaration, or of a generic method or constructor. The type for type variables takes a parameter, and is writtenTypeVariable<D>, whereDrepresents the type of object that declared the type variable. Thus, the type variables of a class have typeTypeVariable<Class<?>>, while the type variables of a generic method have typeTypeVariable<Method>. Arguably, the type parameter is confusing and is not very helpful. Since it is responsible for the problem described in Section 6.6, Sun may remove it in the future.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Chapter 8: Effective Generics
- InhaltsvorschauThis chapter contains advice on how to use generics effectively in practical coding. We consider checked collections, security issues, specialized classes, and binary compatibility. The title of this section is an homage to Joshua Bloch's book, Effective Java (Addison-Wesley).As we have seen, generic types are checked at compile time, not run time. Usually, this is just what we want, since checking at compile time reports errors earlier and incurs no run-time overhead. However, sometimes this may not be appropriate, either because we can't be sure that compile-time checks are adequate (say, because we are passing an instance of a parameterized type to a legacy client or to a client we don't trust), or because we need information about types at run time (say, because we want a reifiable type for use as an array component). A checked collection will often do the trick, and when that will not do, we can create a specialized class. We consider checked collections in this section, security issues in the next section, and specialized classes in the section after that.Consider a legacy library that contains methods to add items to a given list and to return a new list containing given items:
class LegacyLibrary { public static void addItems(List list) { list.add(new Integer(1)); list.add("two"); } public static List getItems() { List list = new ArrayList(); list.add(new Integer(3)); list.add("four"); return list; } }Now consider a client that uses this legacy library, having been told (incorrectly) that the items are always integers:class NaiveClient { public static void processItems() { List<Integer> list = new ArrayList<Integer>(); LegacyLibrary.addItems(list); List<Integer> list2 = LegacyLibrary.getItems(); // unchecked // sometime later ... int s = 0; for (int i : list) s += i; // class cast exception for (int i : list2) s += i; // class cast exception } }There is no warning when passing the integer list to the methodEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Take Care when Calling Legacy Code
- InhaltsvorschauAs we have seen, generic types are checked at compile time, not run time. Usually, this is just what we want, since checking at compile time reports errors earlier and incurs no run-time overhead. However, sometimes this may not be appropriate, either because we can't be sure that compile-time checks are adequate (say, because we are passing an instance of a parameterized type to a legacy client or to a client we don't trust), or because we need information about types at run time (say, because we want a reifiable type for use as an array component). A checked collection will often do the trick, and when that will not do, we can create a specialized class. We consider checked collections in this section, security issues in the next section, and specialized classes in the section after that.Consider a legacy library that contains methods to add items to a given list and to return a new list containing given items:
class LegacyLibrary { public static void addItems(List list) { list.add(new Integer(1)); list.add("two"); } public static List getItems() { List list = new ArrayList(); list.add(new Integer(3)); list.add("four"); return list; } }Now consider a client that uses this legacy library, having been told (incorrectly) that the items are always integers:class NaiveClient { public static void processItems() { List<Integer> list = new ArrayList<Integer>(); LegacyLibrary.addItems(list); List<Integer> list2 = LegacyLibrary.getItems(); // unchecked // sometime later ... int s = 0; for (int i : list) s += i; // class cast exception for (int i : list2) s += i; // class cast exception } }There is no warning when passing the integer list to the methodaddItems, because the parameterized typeList<Integer>is considered a subtype ofList. The conversion fromListtoList<Integer>of the list returned bygetItemsdoes issue an unchecked warning. At run-time, a class cast exception will be raised when attempting to extract data from these lists, since the cast to typeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Use Checked Collections to Enforce Security
- InhaltsvorschauIt is important to be aware that the guarantees offered by generic types apply only if there are no unchecked warnings. This means that generic types are useless for ensuring security in code written by others, since you have no way of knowing whether that code raised unchecked warnings when it was compiled.Say we have a class that defines an order, with a subclass that defines an authenticated order:
class Order { ... } class AuthenticatedOrder extends Order { ... }Interfaces specify suppliers and processors of orders. Here the supplier is required to provide only authenticated orders, while the processor handles all kinds of orders:interface OrderSupplier { public void addOrders(List<AuthenticatedOrder> orders); } interface OrderProcessor { public void processOrders(List<? extends Order> orders); }From the types involved, you might think that the following broker guarantees that only authenticated orders can pass from the supplier to the processor:class NaiveBroker { public void connect(OrderSupplier supplier, OrderProcessor processor) { List<AuthenticatedOrder> orders = new ArrayList<AuthenticatedOrder>(); supplier.addOrders(orders); processor.processOrders(orders); } }But a devious supplier may, in fact, supply unauthenticated orders:class DeviousSupplier implements OrderSupplier { public void addOrders(List<AuthenticatedOrder> orders) { List raw = orders; Order order = new Order(); // not authenticated raw.add(order); // unchecked call } }Compiling the devious supplier will issue an unchecked warning, but the broker has no way of knowing this.Incompetence can cause just as many problems as deviousness. Any code that issues unchecked warnings when compiled could cause similar problems, perhaps simply because the author made a mistake. In particular, legacy code may raise such problems, as described in the previous section.The correct solution is for the broker to pass a checked list to the supplier:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Specialize to Create Reifiable Types
- InhaltsvorschauParameterized types are not reifiable, but some operations, such as instance tests, casting, and array creation apply only to reifiable types. In such cases, one workaround is to create a specialized version of the parameterized type. Specialized versions can be created either by delegation (that is, wrappers) or by inheritance (that is, subclassing), and we discuss each in turn.Example 8.1 shows how to specialize lists to strings; specializing to other types is similar. We begin by specializing the
Listinterface to the desired type:Example 8-1. Specialize to create reifiable typesinterface ListString extends List<String> {} class ListStrings { public static ListString wrap(final List<String> list) { class Random extends AbstractList<String> implements ListString, RandomAccess { public int size() { return list.size(); } public String get(int i) { return list.get(i); } public String set(int i, String s) { return list.set(i,s); } public String remove(int i) { return list.remove(i); } public void add(int i, String s) { list.add(i,s); } } class Sequential extends AbstractSequentialList<String> implements ListString { public int size() { return list.size(); } public ListIterator<String> listIterator(int index) { final ListIterator<String> it = list.listIterator(index); return new ListIterator<String>() { public void add(String s) { it.add(s); } public boolean hasNext() { return it.hasNext(); } public boolean hasPrevious() { return it.hasPrevious(); } public String next() { return it.next(); } public int nextIndex() { return it.nextIndex(); } public String previous() { return it.previous(); } public int previousIndex() { return it.previousIndex(); } public void remove() { it.remove(); } public void set(String s) { it.set(s); } }; } } return list instanceof RandomAccess ? new Random() : new Sequential(); } } class ArrayListString extends ArrayList<String> implements ListString { public ArrayListString() { super(); } public ArrayListString(Collection<? extends String> c) { super(c); } public ArrayListString(int capacity) { super(capacity); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Maintain Binary Compatibility
- InhaltsvorschauAs we have stressed, generics are implemented via erasure in order to ease evolution. When evolving legacy code to generic code, we want to ensure that the newly-generified code will work with any existing code, including class files for which we do not have the source. When this is the case, we say that the legacy and generic versions are binary compatible.Binary compatibility is guaranteed if the erasure of the signature of the generic code is identical to the signature of the legacy code and if both versions compile to the same bytecode. Usually, this is a natural consequence of generification, but in this section we look at some of the corner cases that can cause problems.Some examples for this section were taken from internal Sun notes written by Mark Reinhold.Adjusting the Erasure One corner case arises in connection with the generification of the
maxmethod in theCollectionsclass. We discussed this case in Sections 3.2 and 3.6, but it is worth a quick review.Here is the legacy signature of this method:// legacy version public static Object max(Collection coll)And here is the natural generic signature, using wildcards to obtain maximum flexibility (see Section 3.2):// generic version -- breaks binary compatibility public static <T extends Comparable<? super T>> T max(Collection<? extends T> coll)But this signature has the wrong erasure—its return type isComparablerather thanObject. In order to get the right signature, we need to fiddle with the bounds on the type parameter, using multiple bounds (see Section 3.6). Here is the corrected version:// generic version -- maintains binary compatibility public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
When there are multiple bounds, the leftmost bound is taken for the erasure. So the erasure ofTis nowObject, giving the result type we require.Some problems with generification arise because the original legacy code contains less-specific types than it might have. For example, the legacy version ofEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 9: Design Patterns
- InhaltsvorschauThis chapter reviews five well-known design patterns—Visitor, Interpreter, Function, Strategy, and Subject-Observer—and shows how they can take advantage of generics. The Function pattern generalizes the idea behind the
Comparatorinterface. The other four patterns are described in the seminal book Design Patterns, by Gamma, Helm, Johnson, and Vlissides (Addison-Wesley).Often, a data structure is defined by case analysis and recursion. For example, a binary tree of typeTree<E>is one of the following:-
A leaf, containing a single value of type
E -
A branch, containing a left subtree and a right subtree, both of type
Tree<E>
It is easy to think of many other examples: a shape may be either a triangle, a rectangle, a combination of two shapes, or the transposition of a shape; an XML node is either a text node, an attribute node, or an element node (which may contain other nodes); and so on.To represent such a structure in an object-oriented language, the data structure is represented by an abstract class, and each case is represented by a subclass. The abstract class declares an abstract method for each possible operation on the data structure, and each subclass implements the method as appropriate for the corresponding case.Example 9.1 illustrates this technique applied to trees. There is an abstract class,Tree<E>, with two abstract methods,toStringandsum. (The former applies to any tree, while the latter applies only to a tree of numbers—for simplicity, this restriction is enforced by a cast at run time rather than a type at compile time, as discussed later.) There are two static factory methods, one to construct a leaf and one to construct a branch. Each of these contains a nested class that extendsTree<E>and implements each of the methodstoStringandsum.Example . A simple tree and clientabstract class Tree<E> { abstract public String toString(); abstract public Double sum(); public static <E> Tree<E> leaf(final E e) { return new Tree<E>() { public String toString() { return e.toString(); } public Double sum() { return ((Number)e).doubleValue(); } }; } public static <E> Tree<E> branch(final Tree<E> l, final Tree<E> r) { return new Tree<E>() { public String toString() { return "("+l.toString()+"^"+r.toString()+")"; } public Double sum() { return l.sum() + r.sum(); } }; } } class TreeClient { public static void main(String[] args) { Tree<Integer> t = Tree.branch(Tree.branch(Tree.leaf(1), Tree.leaf(2)), Tree.leaf(3)); assert t.toString().equals("((1^2)^3)"); assert t.sum() == 6; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Visitor
- InhaltsvorschauOften, a data structure is defined by case analysis and recursion. For example, a binary tree of type
Tree<E>is one of the following:-
A leaf, containing a single value of type
E -
A branch, containing a left subtree and a right subtree, both of type
Tree<E>
It is easy to think of many other examples: a shape may be either a triangle, a rectangle, a combination of two shapes, or the transposition of a shape; an XML node is either a text node, an attribute node, or an element node (which may contain other nodes); and so on.To represent such a structure in an object-oriented language, the data structure is represented by an abstract class, and each case is represented by a subclass. The abstract class declares an abstract method for each possible operation on the data structure, and each subclass implements the method as appropriate for the corresponding case.Example 9.1 illustrates this technique applied to trees. There is an abstract class,Tree<E>, with two abstract methods,toStringandsum. (The former applies to any tree, while the latter applies only to a tree of numbers—for simplicity, this restriction is enforced by a cast at run time rather than a type at compile time, as discussed later.) There are two static factory methods, one to construct a leaf and one to construct a branch. Each of these contains a nested class that extendsTree<E>and implements each of the methodstoStringandsum.Example . A simple tree and clientabstract class Tree<E> { abstract public String toString(); abstract public Double sum(); public static <E> Tree<E> leaf(final E e) { return new Tree<E>() { public String toString() { return e.toString(); } public Double sum() { return ((Number)e).doubleValue(); } }; } public static <E> Tree<E> branch(final Tree<E> l, final Tree<E> r) { return new Tree<E>() { public String toString() { return "("+l.toString()+"^"+r.toString()+")"; } public Double sum() { return l.sum() + r.sum(); } }; } } class TreeClient { public static void main(String[] args) { Tree<Integer> t = Tree.branch(Tree.branch(Tree.leaf(1), Tree.leaf(2)), Tree.leaf(3)); assert t.toString().equals("((1^2)^3)"); assert t.sum() == 6; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Interpreter
- InhaltsvorschauOne use of trees is to represent expressions in a programming language. As in the previous section, the expression type is represented by an abstract class, with each kind of expression represented by a subclass. There is an abstract method to evaluate an expression, and each subclass implements the method as appropriate for the corresponding kind of expression.With generics, it is possible to parameterize the expression type by the type of the expression. For example,
Exp<Integer>is an expression that returns an integer, whileExp<Pair<Integer, Integer>>is an expression that returns a pair of integers.Example 9.4 demonstrates the Interpreter pattern with generics. It begins by defining a classPair<A, B>, with a constructor and two methods to select the left and right components of a pair. It then declares an abstract class,Exp<A>, for an expression that returns a value of typeA, with an abstract methodevalthat returns a value of typeA. In our example, there are five kinds of expression:-
An integer literal, of type
Exp<Integer> -
A sum expression, of type
Exp<Integer>, which has two subexpressions, each of typeExp<Integer> -
An expression to construct a pair, of type
Exp<Pair<A, B>>, which has two subexpressions of typeExp<A>andExp<B> -
An expression to select the left component of a pair, of type
Exp<A>, which has a subexpression of typeExp<Pair<A, B>> -
An expression to select the right component of a pair, of type
Exp<B>, which has a subexpression of typeExp<Pair<A, B>>
Example . An interpreter with genericsclass Pair<A, B> { private final A left; private final B right; public Pair(A l, B r) { left=l; right=r; } public A left() { return left; } public B right() { return right; } } abstract class Exp<T> { abstract public T eval(); static Exp<Integer> lit(final int i) { return new Exp<Integer>() { public Integer eval() { return i; } }; } static Exp<Integer> plus(final Exp<Integer> e1, final Exp<Integer> e2) { return new Exp<Integer>() { public Integer eval() { return e1.eval()+e2.eval(); } }; } static <A, B> Exp<Pair<A, B>> pair(final Exp<A> e1, final Exp<B> e2) { return new Exp<Pair<A, B>>() { public Pair<A, B> eval() { return new Pair<A, B>(e1.eval(), e2.eval()); } }; } static <A, B> Exp<A> left(final Exp<Pair<A, B>> e) { return new Exp<A>() { public A eval() { return e.eval().left(); } }; } static <A, B> Exp<B> right(final Exp<Pair<A, B>> e) { return new Exp<B>() { public B eval() { return e.eval().right(); } }; } public static void main(String[] args) { Exp<Integer> e = left(pair(plus(lit(3),lit(4)),lit(5))); assert e.eval() == 7; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Function
- InhaltsvorschauThe Function pattern converts an arbitrary method into an object. The relation between a function and the corresponding method is similar to the relation between
Comparatorand thecompareTomethod.The generic version of the Function pattern demonstrates how to use a type variable in thethrowsclause of a method declaration. This may be useful when different instances of a class contain methods that may raise different checked exceptions.Recall that the classThrowablehas two major subclasses,ExceptionandError, and that the first of these has another major subclass,RuntimeException. An exception is checked if it is a subclass ofExceptionbut not a subclass ofRuntimeException. Thethrowsclause of a method may list any subclass ofThrowable, but must list any checked exception that might be thrown by the method body, including any checked exceptions declared for the methods invoked within the body.An example of the use of a type variable in athrowsclause is shown in Example 9.5. The example defines a class,Function<A, B, X>, which represents a function. The class contains an abstract method,apply, that accepts an argument of typeA, returns a result of typeB, and may throw an exception of typeX. The class also contains anapplyAllmethod that accepts an argument of typeList<A>, returns a result of typeList<B>, and again may throw an exception of typeX; the method invokes theapplymethod on each element of the argument list to produce the result list.Example . Type parameter in a throws clauseimport java.util.*; import java.lang.reflect.*; interface Function<A, B, X extends Throwable> { public B apply(A x) throws X; } class Functions { public <A, B, X extends Throwable> List<B> applyAll(List<A> list) throws X { List<B> result = new ArrayList<B>(list.size()); for (A x : list) result.add(apply(x)); return result; } public static void main(String[] args) { Function<String, Integer, Error> length = new Function<String, Integer, Error>() { public Integer apply(String s) { return s.length(); } }; Function<String, Class<?>, ClassNotFoundException> forName = new Function<String, Class<?>, ClassNotFoundException>() { public Class<?> apply(String s) throws ClassNotFoundException { return Class.forName(s); } }; Function<String, Method, Exception> getRunMethod = new Function<String, Method, Exception>() { public Method apply(String s) throws ClassNotFoundException,NoSuchMethodException { return Class.forName(s).getMethod("run"); } }; List<String> strings = Arrays.asList(args); System.out.println(applyAll(length, strings)); try { System.out.println(applyAll(forName, strings)); } catch (ClassNotFoundException e) { System.out.println(e); } try { System.out.println(applyAll(getRunMethod, strings)); } catch (ClassNotFoundException e) { System.out.println(e); } catch (NoSuchMethodException e) { System.out.println(e); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new AssertionError(); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Strategy
- InhaltsvorschauThe Strategy pattern is used to decouple a method from an object, allowing you to supply many possible instances of the method. Our discussion of the Strategy pattern illustrates a structuring technique found in many object-oriented programs, that of parallel class hierarchies. We will illustrate the Strategy pattern by considering how tax payers may apply different tax strategies. There will be a hierarchy for tax payers, and a related hierarchy for tax strategies. For example, there is a default strategy that applies to any tax payer. One subclass of tax payer is a trust, and one subclass of the default strategy is one that applies only to trusts.Our discussion will also illustrate a technique often used with generic types—the use of type variables with recursive bounds. We have already seen this trick at work in the definition of the
Comparableinterface and theEnumclass; here we will apply it to clarify the connection between tax payers and their associated tax strategies. We also explain thegetThistrick, which allows us to assign a more precise type tothiswhen type variables with recursive bounds appear.First, we'll look at a basic version of the Strategy pattern, which shows how to use generics to design parallel class hierarchies. Next, we'll look at an advanced version where objects contain their own strategies, which uses type variables with recursive bounds and explains thegetThistrick.The example in this section was developed in discussion with Heinz M. Kabutz, and also appears in his online publication, The Java Specialists' Newsletter.Parallel Class Hierarchies A typical use of the Strategy pattern is for tax computation, as shown in Example 9.6. We have a classTaxPayerwith subclassesPersonandTrust. Every tax payer has an income, and, in addition, a trust may be nonprofit. For example, we may define a person with an income of $100,000.00 and two trusts with the same income, one nonprofit and one otherwise:Example . A basic Strategy pattern with parallel class hierarchiesEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Subject-Observer
- InhaltsvorschauWe finish with a more extended example, illustrating the generic Subject-Observer pattern. Like the Strategy pattern, the Subject-Observer pattern uses parallel class hierarchies, but this time we require two type variables with mutually recursive bounds, one to stand for the specific kind of subject and one to stand for the specific kind of observer. This is our first example of type variables with mutually recursive bounds.The Java library implements a nongeneric version of the Subject-Observer pattern in the package
java.utilwith the classObservableand the interfaceObserver(the former corresponding to the subject), signatures for which are shown in Example 9.8.Example . Observable and Observer before genericspackage java.util; public class Observable { public void addObserver(Observer o) {...} protected void clearChanged() {...} public int countObservers() {...} public void deleteObserver(Observer o) {...} public boolean hasChanged() {...} public void notifyObservers() {...} public void notifyObservers(Object arg) {...} protected void setChanged() {...} } package java.util; public interface Observer { public void update(Observable o, Object arg); }TheObservableclass contains methods to register observers (addObserver), to indicate that the observable has changed (setChanged), and to notify all observers of any changes (notifyObservers), among others. ThenotifyObserversmethod may accept an arbitrary argument of typeObjectthat is to be broadcast to all the observers. TheObserverinterface specifies theupdatemethod that is called bynotifyObservers. This method takes two parameters: the first, of typeObservable, is the subject that has changed; the second, of typeObject, is the broadcast argument.The appearance ofObjectin a method signature often indicates an opportunity to generify. So we should expect to generify the classes by adding a type parameter,A, corresponding to the argument type. Further, we can replaceEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 10: The Main Interfaces of the Java Collections Framework
- InhaltsvorschauFigure 10.1 shows the main interfaces of the Java Collections Framework, together with one other—
Iterable—which is outside the Framework but is an essential adjunct to it. Its purpose is as follows:-
Iterabledefines the contract that a class has to fulfill for its instances to be usable with the foreach statement.
And the Framework interfaces have the following purposes:-
Collectioncontains the core functionality required of any collection other than a map. It has no direct concrete implementations; the concrete collection classes all implement one of its subinterfaces as well. -
Setis a collection, without duplicates, in which order is not significant.SortedSetautomatically sorts its elements and returns them in order.NavigableSetextends this, adding methods to find the closest matches to a target element. -
Queueis a collection designed to accept elements at its tail for processing, yielding them up at its head in the order in which they are to be processed. Its subinterfaceDequeextends this by allowing elements to be added or removed at both head and tail.QueueandDequehave subinterfaces,BlockingQueueandBlockingDequerespectively, that support concurrent access and allow threads to be blocked, indefinitely or for a maximum time, until the requested operation can be carried out. -
Listis a collection in which order is significant, accommodating duplicate elements. -
Mapis a collection which uses key-value associations to store and retrieve elements. It is extended byConcurrentMap, which provides support for concurrent access, bySortedMap, which guarantees to return its values in ascending key order, byNavigableMapwhich extendsSortedMapto find the closest matches to a target element, and byConcurrentNavigableMapwhich extendsConcurrentMapandNavigableMap.
Chapters 12 through 16 will concentrate on each of the Collections Framework interfaces in turn. First, though, in Chapter 11, we need to cover some preliminary ideas which run through the entire Framework design.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. -
- Chapter 11: Preliminaries
- InhaltsvorschauIn this chapter, we will take time to discuss the concepts underlying the framework, before we get into the detail of the collections themselves.An iterator is an object that implements the interface
Iterator:public Iterator<E> { boolean hasNext(); // return true if the iteration has more elements E next(); // return the next element in the iteration void remove(); // remove the last element returned by the iterator }The purpose of iterators is to provide a uniform way of accessing collection elements sequentially, so whatever kind of collection you are dealing with, and however it is implemented, you always know how to process its elements in turn. This used to require some rather clumsy code; for example, in earlier versions of Java, you would write the following to print the string representation of a collection's contents:// coll refers to an object which implements Collection // ----- not the preferred idiom from Java 5 on ------- for (Iterator itr = coll.iterator() ; itr.hasNext() ; ) { System.out.println(itr.next()); }The strange-lookingforstatement was the preferred idiom before Java 5 because, by restricting the scope ofitrto the body of the loop, it eliminated accidental uses of it elsewhere. This code worked because any class implementingCollectionhas aniteratormethod which returns an iterator appropriate to objects of that class. It is no longer the approved idiom because Java 5 introduced something better: the foreach statement, which you met in Part I. Using foreach, we can write the preceding code more concisely:for (Object o : coll) { System.out.println(o); }This code will work with anything that implements the interfaceIterable—that is, anything that can produce anIterator. This is the declaration ofIterable:public Iterable<T> { Iterator<T> iterator(); // return an iterator over elements of type T }In Java 5 theCollectioninterface was made to extendEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Iterable and Iterators
- InhaltsvorschauAn iterator is an object that implements the interface
Iterator:public Iterator<E> { boolean hasNext(); // return true if the iteration has more elements E next(); // return the next element in the iteration void remove(); // remove the last element returned by the iterator }The purpose of iterators is to provide a uniform way of accessing collection elements sequentially, so whatever kind of collection you are dealing with, and however it is implemented, you always know how to process its elements in turn. This used to require some rather clumsy code; for example, in earlier versions of Java, you would write the following to print the string representation of a collection's contents:// coll refers to an object which implements Collection // ----- not the preferred idiom from Java 5 on ------- for (Iterator itr = coll.iterator() ; itr.hasNext() ; ) { System.out.println(itr.next()); }The strange-lookingforstatement was the preferred idiom before Java 5 because, by restricting the scope ofitrto the body of the loop, it eliminated accidental uses of it elsewhere. This code worked because any class implementingCollectionhas aniteratormethod which returns an iterator appropriate to objects of that class. It is no longer the approved idiom because Java 5 introduced something better: the foreach statement, which you met in Part I. Using foreach, we can write the preceding code more concisely:for (Object o : coll) { System.out.println(o); }This code will work with anything that implements the interfaceIterable—that is, anything that can produce anIterator. This is the declaration ofIterable:public Iterable<T> { Iterator<T> iterator(); // return an iterator over elements of type T }In Java 5 theCollectioninterface was made to extendIterable, so any set, list, or queue can be the target of foreach, as can arrays. If you write your own implementation ofIterable, that too can be used with foreachEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementations
- InhaltsvorschauWe have looked briefly at the interfaces of the Collections Framework, which define the behavior that we can expect of each collection. But as we mentioned in the introduction to this chapter, there are several ways of implementing each of these interfaces. Why doesn't the Framework just use the best implementation for each interface? That would certainly make life simpler—too simple, in fact, to be anything like life really is. If an implementation is a greyhound for some operations, Murphy's Law tells us that it will be a tortoise for others. Because there is no "best" implementation of any of the interfaces, you have to make a tradeoff, judging which operations are used most frequently in your application and choosing the implementation that optimizes those operations.The three main kinds of operations that most collection interfaces require are insertion and removal of elements by position, retrieval of elements by content, and iteration over the collection elements. The implementations provide many variations on these operations, but the main differences among them can be discussed in terms of how they carry out these three. In this section, we'll briefly survey the four main structures used as the basis of the implementations and later, as we need them, we will look at each in more detail. The four structures are:
- Arrays
-
These are the structures familiar from the Java language—and just about every other programming language since Fortran. Because arrays are implemented directly in hardware, they have the properties of random-access memory: very fast for accessing elements by position and for iterating over them, but slower for inserting and removing elements at arbitrary positions (because that may require adjusting the position of other elements). Arrays are used in the Collections Framework as the backing structure for
ArrayList,CopyOnWriteArrayList,EnumSetandEnumMap, and for many of theQueueandDequeimplementations. They also form an important part of the mechanism for implementing hash tables (discussed shortly).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Efficiency and the -Notation
- InhaltsvorschauIn the last section, we talked about different implementations being "good" for different operations. A good algorithm is economical in its use of two resources: time and space. Implementations of collections usually use space proportional to the size of the collection, but they can vary greatly in the time required for access and update, so that will be our primary concern. It's very hard to say precisely how quickly a program will execute, as that depends on many factors, including some that are outside the province of the programmer, such as the quality of the compiled code and the speed of the hardware. Even if we ignore these and limit ourselves to thinking only about how the execution time for an algorithm depends on its data, detailed analysis can be complex. A relatively simple example is provided in Donald Knuth's classic book Sorting and Searching (Addison-Wesley), where the worst-case execution time for a multiple list insertion sort program on Knuth's notional MIX machine is derived as
[3.5N2 + 24.5N + 4M + 2]
where N is the number of elements being sorted and M is the number of lists.As a shorthand way of describing algorithm efficiency, this isn't very convenient. Clearly we need a broader brush for general use. The one most commonly used is the O-notation (pronounced "big-oh notation"). The O-notation is a way of describing the performance of an algorithm in an abstract way, without the detail required to predict the precise performance of a particular program running on a particular machine. Our main reason for using it is that it gives us a way of describing how the execution time for an algorithm depends on the size of its data set, provided the data set is large enough. For example, in the previous expression the first two terms are comparable for low values of N; in fact, for N < 8, the second term is larger. But as N grows, the first term increasingly dominates the expression and, by the time it reaches 100, the first term is 15 times as large as the second one. Using a very broad brush, we say that the worst case for this algorithm takes timeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Contracts
- InhaltsvorschauIn reading about software design, you are likely to come across the term contract, often without any accompanying explanation. In fact, software engineering gives this term a meaning that is very close to what people usually understand a contract to be. In everyday usage, a contract defines what two parties can expect of each other—their obligations to each other in some transaction. If a contract specifies the service that a supplier is offering to a client, the supplier's obligations are obvious. But the client, too, may have obligations—besides the obligation to pay—and failing to meet them will automatically release the supplier from her obligations as well. For example, airlines' conditions of carriage—for the class of tickets that we can afford, anyway—release them from the obligation to carry passengers who have failed to turn up on time. This allows the airlines to plan their service on the assumption that all the passengers they are carrying are punctual; they do not have to incur extra work to accommodate clients who have not fulfilled their side of the contract.Contracts work just the same way in software. If the contract for a method states preconditions on its arguments (i.e., the obligations that a client must fulfill), the method is required to return its contracted results only when those preconditions are fulfilled. For example, binary search (see Section 17.1.4) is a fast algorithm to find a key within an ordered list, and it fails if you apply it to an unordered list. So the contract for
Collections.binarySearchcan say, "if the list is unsorted, the results are undefined", and the implementer of binary search is free to write code which, given an unordered list, returns random results, throws an exception, or even enters an infinite loop. In practice, this situation is relatively rare in the contracts of the core API because, instead of restricting input validity, they mostly allow for error states in the preconditions and specify the exceptions that the method must throw if it gets bad input. This design may be appropriate for general libraries such as the Collections Framework, which will be very heavily used in widely varying situations and by programmers of widely varying ability. You should probably avoid it for less-general libraries, because it restricts the flexibility of the supplier unnecessarily. In principle, all that a client should need to know is how to keep to its side of the contract; if it fails to do that, all bets are off and there should be no need to say exactly what the supplier will do.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Collections and Thread Safety
- InhaltsvorschauWhen a Java program is running, it is executing one or more execution streams, or threads. A thread is like a lightweight process, so a program simultaneously executing several threads can be thought of as a computer running several programs simultaneously, but with one important difference: different threads can simultaneously access the same memory locations and other system resources. On machines with multiple processors, truly concurrent thread execution can be achieved by assigning a processor to each thread. If, however, there are more threads than processors—the usual case—multithreading is implemented by time slicing, in which a processor executes some instructions from each thread in turn before switching to the next one.There are two good reasons for using multithread programming. An obvious one, in the case of multicore and multiprocessor machines, is to share the work and get it done quicker. (This reason is becoming ever more compelling as hardware designers turn increasingly to parallelism as the way of improving overall performance.) A second one is that two operations may take varying, perhaps unknown, amounts of time, and you do not want the response to one operation to await the completion of the other. This is particularly true for the a graphical user interface (GUI), where the response to the user clicking a button should be immediate, and should not be delayed if, say, the program happens to be running a compute-intensive part of the application at the time.Although concurrency may be essential to achieving good performance, it comes at a price. Different threads simultaneously accessing the same memory location can produce unexpected results, unless you take care to constrain their access. Consider Example 11.2, in which the class
ArrayStackuses an array and an index to implement the interfaceStack, which models a stack ofint(despite the similarity of names, this example is different from Example 5.1). ForArrayStackto work correctly, the variableindexshould always point at the top element of the stack, no matter how many elements are added to or removed from the stack. This is anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 12: The Collection Interface
- InhaltsvorschauThe interface
Collection(see Figure 12.1) defines the core functionality that we expect of any collection other than a map. It provides methods in four groups.
Figure 12-1: CollectionAdding Elementsboolean add(E e) // add the element e boolean addAll(Collection<? extends E> c) // add the contents of c
The boolean result returned by these methods indicates whether the collection was changed by the call. It can be false for collections, such as sets, which will be unchanged if they are asked to add an element that is already present. But the method contracts specify that the elements being added must be present after execution so, if the collection refuses an element for any other reason (for example, some collections don't permitnullelements), these methods must throw an exception.The signatures of these methods show that, as you might expect, you can add elements or element collections only of the parametric type.Removing Elementsboolean remove(Object o) // remove the element o void clear() // remove all elements boolean removeAll(Collection<?> c) // remove the elements in c boolean retainAll(Collection<?> c) // remove the elements *not* in c
If the elementoisnull,removeremoves anullfrom the collection if one is present. Otherwise, if an elementeis present for whicho.equals(e), it removes it. If not, it leaves the collection unchanged. Where a method in this group returns aboolean, the value istrueif the collection changed as a result of applying the operation.In contrast to the methods for adding elements, these methods—and those of the next group—will accept elements or element collections of any type. We will explain this in a moment, when we look at examples of the use of these methods.Querying the Contents of a Collectionboolean contains(Object o) // true if o is present boolean containsAll(Collection<?> c) // true if all elements of c // are present in the collection boolean isEmpty() // true if no elements are present int size() // return the element count (or // Integer.MAX_VALUE if that is less)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using the Methods of Collection
- InhaltsvorschauTo illustrate the use of the collection classes, let's construct a tiny example. Your authors are forever trying to get organized; let's imagine that our latest effort involves writing our own to-do manager. We begin by defining a class to represent tasks, and subclasses to represent different kinds of tasks, such as writing some code or making a phone call.Here is the definition of tasks that we shall use:
public abstract class Task implements Comparable<Task> { protected Task() {} public boolean equals(Object o) { if (o instanceof Task) { return toString().equals(o.toString()); } else return false; } public int compareTo(Task t) { return toString().compareTo(t.toString()); } public int hashCode() { return toString().hashCode(); } public abstract String toString(); }We only require four operations on tasks:equals,compareTo,hashCodeandtoString. Equality will be used to test whether a collection contains a given task, comparison will be used to by ordered collections (such asOrderedSetandOrderedMap) and the hash code will be used by collections based on hash tables (such asHashSetandHashMap), and the string representation of a task will be used whenever we show the contents of a collection. The first three methods are defined in terms of thetoStringmethod, which is declaredabstract, so it must be defined in each subclass ofTask. We consider two tasks equal if they are represented by the same string, and the natural ordering on tasks is the same as the ordering on their strings. This guarantees that the natural ordering on tasks is consistent with equality, as discussed in Section 3.1—that is,compareToreturns0exactly whenequalsreturnstrue.We define subclasses for two kinds of tasks, writing some code and making a phone call:public final class CodingTask extends Task { private final String spec; public CodingTask(String spec) { this.spec = spec; } public String getSpec() { return spec; } public String toString() { return "code " + spec; } } public final class PhoneTask extends Task { private final String name; private final String number; public PhoneTask(String name, String number) { this.name = name; this.number = number; } public String getName() { return name; } public String getNumber() { return number; } public String toString() { return "phone " + name; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Collection
- InhaltsvorschauThere are no concrete implementations of
Collection. The classAbstractCollection, which partially implements it, is one of a series of skeletal implementations—includingAbstractSet,AbstractList, and so on—which provide functionality common to the different concrete implementations of each interface. These skeletal implementations are available to help the designer of new implementations of the Framework interfaces. For example,Collectioncould serve as the interface for bags (unordered lists), and a programmer implementing bags could extendAbstractCollectionand find most of the implementation work already done.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Collection Constructors
- InhaltsvorschauWe will go on to look at these three main kinds of collection in the next three chapters, but we should first explain two common forms of constructor which are shared by most collection implementations. Taking
HashSetas an example, these are:public HashSet() public HashSet(Collection<? extends E> c)
The first of these creates an empty set, and the second a set that will contain the elements of any collection of the parametric type—or one of its subtypes, of course. Using this constructor has the same effect as creating an empty set with the default constructor, and then adding the contents of a collection usingaddAll. This is sometimes called a "copy constructor", but that term should really be reserved for constructors which make a copy of an object of the same class, whereas constructors of the second form can take any object which implements the interfaceCollection<? extends E>. Joshua Bloch has suggested the term "conversion constructor".Not all collection classes have constructors of both forms—ArrayBlockingQueue, for example, cannot be created without fixing its capacity, andSynchronousQueuecannot hold any elements at all, so no constructor of the second form is appropriate. In addition, many collection classes have other constructors besides these two, but which ones they have depends not on the interface they implement but on the underlying implementation; these additional constructors are used to configure the implementation.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 13: Sets
- InhaltsvorschauA set is a collection of items that cannot contain duplicates; adding an item if it is already present in the set has no effect. The
Setinterface has the same methods as those ofCollection, but it is defined separately in order to allow the contract ofadd(andaddAll, which is defined in terms ofadd) to be changed in this way. Returning to the task manager example in the previous chapter, suppose that on Monday you have free time to carry out your telephone tasks. You can make the appropriate collection by adding all your telephone tasks to your Monday tasks. LetmondayTasksandphoneTasksbe as declared in Example 12.1. Using a set (again choosing a conveniently common implementation ofSet), you can write:Set<Task> phoneAndMondayTasks = new TreeSet<Task>(mondayTasks); phoneAndMondayTasks.addAll(phoneTasks); assert phoneAndMondayTasks.toString().equals( "[code logic, phone Mike, phone Paul]");
This works because of the way that duplicate elements are handled. The taskmikePhone, which is in bothmondayTasksandphoneTasks, appears as intended, only once, inphoneAndMondayTasks—you definitely don't want to have to do all such tasks twice over!When we used the methods ofCollectionin the examples of Chapter 12, we emphasized that they would work with any implementation ofCollection. What if we had decided that we would use one of theSetimplementations from the Collections Framework? We would have had to choose between the various concrete implementations that the Framework provides, which differ both in how fast they perform the basic operations ofadd,contains, and iteration, and in the order in which their iterators return their elements. In this section and the next we will look at these differences, then at the end of the Chapter we will summarize the comparative performance of the different implementations.There are six concrete implementations ofSetin the Collections Framework. Figure 13.1 shows their relationship toSetand its subinterfacesSortedSetEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Set
- InhaltsvorschauWhen we used the methods of
Collectionin the examples of Chapter 12, we emphasized that they would work with any implementation ofCollection. What if we had decided that we would use one of theSetimplementations from the Collections Framework? We would have had to choose between the various concrete implementations that the Framework provides, which differ both in how fast they perform the basic operations ofadd,contains, and iteration, and in the order in which their iterators return their elements. In this section and the next we will look at these differences, then at the end of the Chapter we will summarize the comparative performance of the different implementations.There are six concrete implementations ofSetin the Collections Framework. Figure 13.1 shows their relationship toSetand its subinterfacesSortedSetandNavigableSet. In this section, we will look atHashSet,LinkedHashSet,CopyOnWriteArraySetandEnumSet.
Figure 13-1: Implementations of the Set interfaceWe will discussSortedSetandNavigableSet, together with their implementations,TreeSetandConcurrentSkipListSet, in Section 13.2.This class is the most commonly used implementation ofSet. As the name implies, it is implemented by a hash table, an array in which elements are stored at a position derived from their contents. Since hash tables store and retrieve elements by their content, they are well suited to implementing the operations ofSet(the Collections Framework also uses them for various implementations ofMap). For example, to implementcontains(Object o)you would look for the elementoand returntrueif it were found.An element's position in a hash table is calculated by a hash function of its contents. Hash functions are designed to give, as far as possible, an even spread of results (hash codes) over the element values that might be stored. For example, here is code like that used in theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - SortedSet and NavigableSet
- Inhaltsvorschau
Sethas one subinterface,SortedSet(Figure 13.4), which adds to theSetcontract a guarantee that its iterator will traverse the set in ascending element order.SortedSetwas itself extended in Java 6 by the interfaceNavigableSet(see Figure 13.5), which adds methods to find the closest matches to a target element. The only implementation ofSortedSetbefore Java 6 wasTreeSet, which has been retrofitted with the methods required to implement the new interface. Since there is no platform implementation ofSortedSetin Java 6 that does not also implementNavigableSet, it makes sense to discuss them in the same section. For new client code developed for the Java 6 platform, there is no need to use theSortedSetinterface at all, but for the benefit of readers still constrained to use Java 5 we shall present the methods of the two interfaces separately in this section.
Figure 13-4: SortedSet
Figure 13-5: NavigableSetIn Chapter 3 we saw that element ordering can either be defined by the element class itself, if that implementsComparable, or it can be imposed by an externalComparator, supplied by a constructor such as this one, forTreeSet:TreeSet(Comparator<? super E> comparator)
Taskdoes implementComparable(its natural ordering is the natural ordering of its string representation), so we don't need to supply a separate comparator. Now merging two ordered lists, which was quite tricky using parallel iterators, is trivial if we get aSortedSetto do the work. Using the task collections of Example 12.1, it requires two lines of code:Set<Task> naturallyOrderedTasks = new TreeSet<Task>(mondayTasks); naturallyOrderedTasks.addAll(tuesdayTasks); assert naturallyOrderedTasks.toString().equals ( "[code db, code gui, code logic, phone Mike, phone Paul]");
This simplicity comes at a price, though; merging two sorted lists of sizeEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing Set Implementations
- InhaltsvorschauTable 13.1 shows the comparative performance of the different
Setimplementations. When you are choosing an implementation, of course, efficiency is only one of the factors you should take into account. Some of these implementations are specialized for specific situations; for example,EnumSetshould always (and only) be used to represent sets ofenum. Similarly,CopyOnWriteArraySetshould only be used where set size will remain relatively small, read operations greatly outnumber writes, thread safety is required, and read-only iterators are acceptable.Table 13.1: Comparative performance of different Set implementations addcontainsnextnotesHashSetO(1)O(1)O(h/n)h is the table capacityLinkedHashSetO(1)O(1)O(1)CopyOnWriteArraySetO(n)O(n)O(1)EnumSetO(1)O(1)O(1)TreeSetEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 14: Queues
- InhaltsvorschauA queue is a collection designed to hold elements for processing, yielding them up in the order in which they are to be processed. The corresponding Collections Framework interface
Queue(see Figure 14.1) has a number of different implementations embodying different rules about what this order should be. Many of the implementations use the rule that tasks are to be processed in the order in which they were submitted (First In First Out, or FIFO), but other rules are possible—for example, the Collections Framework includes queue classes whose processing order is based on task priority. TheQueueinterface was introduced in Java 5, motivated in part by the need for queues in the concurrency utilities included in that release. A glance at the hierarchy of implementations shown in Figure 14.2 shows that, in fact, nearly all of theQueueimplementations in the Collections Framework are in the packagejava.util.concurrent.
Figure 14-1: Queue
Figure 14-2: Implementations of Queue in the Collections FrameworkOne classic requirement for queues in concurrent systems comes when a number of tasks have to be executed by a number of threads working in parallel. An everyday example of this situation is that of a single queue of airline passengers being handled by a line of check-in operators. Each operator works on processing a single passenger (or a group of passengers) while the remaining passengers wait in the queue. As they arrive, passengers join the tail of the queue, wait until they reach its head, and are then assigned to the next operator who becomes free. A good deal of fine detail is involved in implementing a queue such as this; operators have to be prevented from simultaneously attempting to process the same passenger, empty queues have to be handled correctly, and in computer systems there has to be a way of defining queues with a maximum size, or bound. (This last requirement may not often be imposed in airline terminals, but it can be very useful in systems in which there is a maximum waiting time for a task to be executed.) TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using the Methods of Queue
- InhaltsvorschauLet's look at examples of the use of these methods. Queues should provide a good way of implementing a task manager, since their main purpose is to yield up elements, such as tasks, for processing. For the moment we shall use
ArrayDequeas the fastest and most straightforward implementation ofQueue(and also ofDeque, of course). But, as before, we shall confine ourselves to the methods of the interface—though you should note that, in choosing a queue implementation, you are also choosing an ordering. WithArrayDeque, you get FIFO ordering—well, our attempts to get organized using fancy scheduling methods never seem to work very well; perhaps it's time to try something simpler.ArrayDequeis unbounded, so we could use eitheraddorofferto set up the queue with new tasks.Queue<Task> taskQueue = new ArrayDeque<Task>(); taskQueue.offer(mikePhone); taskQueue.offer(paulPhone);
Any time we feel ready to do a task, we can take the one that has reached the head of the queue:Task nextTask = taskQueue.poll(); if (nextTask != null) { // process nextTask }The choice between usingpollandremovedepends on whether we want to regard queue emptiness as an exceptional condition. Realistically—given the nature of the application—that might be a sensible assumption, so this is an alternative:try { Task nextTask = taskQueue.remove(); // process nextTask } catch (NoSuchElementException e) { // but we *never* run out of tasks! }This scheme needs some refinement to allow for the nature of different kinds of tasks. Phone tasks fit into relatively short time slots, whereas we don't like to start coding unless there is reasonably substantial time to get into the task. So if time is limited—say, until the next meeting—we might like to check that the next task is of the right kind before we take it off the queue:Task nextTask = taskQueue.peek(); if (nextTask instanceof PhoneTask) { taskQueue.remove(); // process nextTask }These inspection and removal methods are a major benefit of theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Queue
- Inhaltsvorschau
PriorityQueueis one of the two nonlegacyQueueimplementations not designed primarily for concurrent use (the other one isArrayDeque). It is not thread-safe, nor does it provide blocking behavior. It gives up its elements for processing according to an ordering like that used byNavigableSet—either the natural order of its elements if they implementComparable, or the ordering imposed by aComparatorsupplied when thePriorityQueueis constructed. SoPriorityQueuewould be an alternative design choice (obviously, given its name) for the priority-based to-do manager that we outlined in Section 13.2 usingNavigableSet. Your application will dictate which alternative to choose: if it needs to examine and manipulate the set of waiting tasks, useNavigableSet. If its main requirement is efficient access to the next task to be performed, usePriorityQueue.ChoosingPriorityQueueallows us to reconsider the ordering: since it accommodates duplicates, it does not share the requirement ofNavigableSetfor an ordering consistent withequals. To emphasize the point, we will define a new ordering for our to-do manager that depends only on priorities. Contrary to what you might expect,PriorityQueuegives no guarantee of how it presents multiple elements with the same value. So if, in our example, several tasks are tied for the highest priority in the queue, it will choose one of them arbitrarily as the head element.The constructors forPriorityQueueare:PriorityQueue() // natural ordering, default initial capacity (11) PriorityQueue(Collection<? extends E> c) // natural ordering of elements taken from c, unless // c is a PriorityQueue or SortedSet, in which case // copy c's ordering PriorityQueue(int initialCapacity) // natural ordering, specified initial capacity PriorityQueue(int initialCapacity, Comparator<? super E> comparator) // Comparator ordering, specified initial capacity PriorityQueue(PriorityQueue<? extends E> c) // ordering and elements copied from c PriorityQueue(SortedSet<? extends E> c) // ordering and elements copied from cEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - BlockingQueue
- InhaltsvorschauJava 5 added a number of classes to the Collections Framework for use in concurrent applications. Most of these are implementations of the
QueuesubinterfaceBlockingQueue(see Figure 14.5), designed primarily to be used in producer-consumer queues.
Figure 14-5: BlockingQueueOne common example of the use of producer-consumer queues is in systems that perform print spooling; client processes add print jobs to the spool queue, to be processed by one or more print service processes, each of which repeatedly "consumes" the task at the head of the queue.The key facilities thatBlockingQueueprovides to such systems are, as its name implies, enqueuing and dequeueing methods that do not return until they have executed successfully. So, for example, a print server does not need to constantly poll the queue to discover whether any print jobs are waiting; it need only call thepollmethod, supplying a timeout, and the system will suspend it until either a queue element becomes available or the timeout expires.BlockingQueuedefines seven new methods, in three groups:Adding an Elementboolean offer(E e, long timeout, TimeUnit unit) // insert e, waiting up to the timeout void put(E e) // add e, waiting as long as necessaryThe nonblocking overload ofofferdefined inQueuewill returnfalseif it cannot immediately insert the element. This new overload waits for a time specified usingjava.util.concurrent.TimeUnit, anEnumwhich allows timeouts to be defined in units such as milliseconds or seconds.Taking these methods together with those inherited fromQueue, there are four ways in which the methods for adding elements to aBlockingQueuecan behave:offerreturnsfalseif it does not succeed immediately, blockingofferreturnsfalseif it does not succeed within its timeout,addthrows an exception if it does not succeed immediately, andputblocks until it succeeds.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Deque
- InhaltsvorschauA deque (pronounced "deck") is a double-ended queue. Unlike a queue, in which elements can be inserted only at the tail and inspected or removed only at the head, a deque can accept elements for insertion and present them for inspection or removal at either end. Also unlike
Queue,Deque's contract specifies the ordering it will use in presenting its elements: it is a linear structure in which elements added at the tail are yielded up in the same order at the head. Used as a queue, then, aDequeis always a FIFO structure; the contract does not allow for, say, priority deques. If elements are removed from the same end (either head or tail) at which they were added, aDequeacts as a stack or LIFO (Last In First Out) structure.Dequeand its subinterfaceBlockingDequewere introduced in Java 6. The fastDequeimplementationArrayDequeuses a circular array (see Section 14.3.2), and is now the implementation of choice for stacks and queues. Concurrent deques have a special role to play in parallelization, discussed in Section 14.4.2.TheDequeinterface (see Figure 14.7) extendsQueuewith methods symmetric with respect to head and tail. For clarity of naming, theQueuemethods that implicitly refer to one end of the queue acquire a synonym that makes their behavior explicit forDeque. For example, the methodspeekandoffer, inherited fromQueue, are equivalent topeekFirstandofferLast. (First and last refer to the head and tail of the deque; the JavaDoc forDequealso uses "front" and "end".)
Figure 14-7: DequeCollection-like Methodsvoid addFirst(E e) // insert e at the head if there is enough space void addLast(E e) // insert e at the tail if there is enough space void push(E e) // insert e at the head if there is enough space boolean removeFirstOccurrence(Object o); // remove the first occurrence of o boolean removeLastOccurrence(Object o); // remove the last occurrence of o Iterator<E> descendingIterator() // get an iterator, returning deque elements in // reverse orderEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing Queue Implementations
- InhaltsvorschauTable 14.1 shows the sequential performance, disregarding locking and CAS overheads, for some sample operations of the
DequeandQueueimplementations we have discussed. These results should be interesting to you in terms of understanding the behavior of your chosen implementation but, as we mentioned at the start of the chapter, they are unlikely to be the deciding factor. Your choice is more likely to be dictated by the functional and concurrency requirements of your application.Table 14-1: Comparative performance of different Queue and Deque implementations offerpeekpollsizePriorityQueueO(log n)O(1)O(log n)O(1)ConcurrentLinkedQueueO(1)O(1)O(1)O(n)ArrayBlockingQueueO(1)O(1)O(1)O(1)LinkedBlockingQueueO(1)O(1)O(1)O(1)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 15: Lists
- InhaltsvorschauLists are probably the most widely used Java collections in practice. A list is a collection which—unlike a set—can contain duplicates, and which—unlike a queue—gives the user full visibility and control over the ordering of its elements. The corresponding Collections Framework interface is
List(see Figure 15.1).
Figure 15-1: ListIn addition to the operations inherited fromCollection, theListinterface includes operations for the following:Positional Access Methods that access elements based on their numerical position in the list:void add(int index, E e) // add element e at given index boolean addAll(int index, Collection<? extends E> c) // add contents of c at given index E get(int index) // return element with given index E remove(int index) // remove element with given index E set(int index, E e) // replace element with given index by eSearch Methods that search for a specified object in the list and return its numerical position. These methods return −1 if the object is not present:int indexOf(Object o) // return index of first occurrence of o int lastIndexOf(Object o) // return index of last occurrence of o
Range-View A method that gets a view of a range of the list:List<E> subList(int fromIndex, int toIndex) // return a view of a portion of the listThe methodsubListworks in a similar way to thesubSetoperations onSortedSet(see Section 13.2), but uses the position of elements in the list rather than their values: the returned list contains the list elements starting atfromIndex, up to but not includingtoIndex. The returned list has no separate existence—it is just a view of part of the list from which it was obtained, so changes in it are reflected in the original list. There is an important difference fromsubSet, though; changes you make to the sublist write through to the backing list, but the reverse is not always true. If elements have been inserted into or removed from the backing list by directly calling one of its "structure changing" methods (Section 12.1), any subsequent attempts to use the sublist will result in aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using the Methods of List
- InhaltsvorschauLet's look at examples of the use of some of these methods in the to-do manager. In the last chapter we considered representing the organization of a single day's tasks in a queue-based class with shutdown capabilities. One useful way of enlarging the scope of the application is to have a number of objects of this type, each one representing the tasks that are scheduled for a day in the future. We will store references to these objects in a
List, which (to keep things simple and to avoid grappling with the distasteful details ofjava.util.Calendar) will be indexed on the number of days in the future that it represents. So the queue of tasks scheduled for today will be stored at element 0 of the list, the queue scheduled for tomorrow at element 1, and so on. Example 15.1 shows the scheduler.Example . A list-based task schedulerpublic class TaskScheduler { private List<StoppableTaskQueue> schedule; private final int FORWARD_PLANNING_DAYS = 365; public TaskScheduler() { List<StoppableTaskQueue> temp = new ArrayList<StoppableTaskQueue>(); for (int i = 0 ; i < FORWARD_PLANNING_DAYS ; i++) { temp.add(new StoppableTaskQueue()); } schedule = new CopyOnWriteArrayList<StoppableTaskQueue>(temp); //1 } public PriorityTask getTask() { for (StoppableTaskQueue daysTaskQueue : schedule) { PriorityTask topTask = daysTaskQueue.getTask(); if (topTask != null) return topTask; } return null; // no outstanding tasks - at all!? } // at midnight, remove and shut down the queue for day 0, assign its tasks // to the new day 0, and create a new day's queue at the planning horizon public void rollOver() throws InterruptedException{ StoppableTaskQueue oldDay = schedule.remove(0); Collection<PriorityTask> remainingTasks = oldDay.shutDown(); StoppableTaskQueue firstDay = schedule.get(0); for (PriorityTask t : remainingTasks) { firstDay.addTask(t); } StoppableTaskQueue lastDay = new StoppableTaskQueue(); schedule.add(lastDay); } public void addTask(PriorityTask task, int day) { if (day < 0 || day >= FORWARD_PLANNING_DAYS) throw new IllegalArgumentException("day out of range"); StoppableTaskQueue daysTaskQueue = schedule.get(day); if (daysTaskQueue.addTask(task)) return; //2 // StoppableTaskQueue.addTask returns false only when called on // a queue that has been shut down. In that case, it will also // have been removed by now, so it's safe to try again. if (! schedule.get(0).addTask(task)) { throw new IllegalStateException("failed to add task " + task); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing List
- InhaltsvorschauThere are three concrete implementations of
Listin the Collections Framework (see Figure 15.3), differing in how fast they perform the various operations defined by the interface and how they behave in the face of concurrent modification; unlikeSetandQueue, however,Listhas no subinterfaces to specify differences in functional behavior. In this and the following section we look at each implementation in turn and provide a performance comparison.
Figure 15-3: Implementations of the List interfaceArrays are provided as part of the Java language and have a very convenient syntax, but their key disadvantage—that, once created, they cannot be resized—makes them increasingly less popular thanListimplementations, which (if resizable at all) are indefinitely extensible. The most commonly used implementation ofListis, in fact,ArrayList—that is, aListbacked by an array.The standard implementation ofArrayListstores theListelements in contiguous array locations, with the first element always stored at index 0 in the array. It requires an array at least large enough (with sufficient capacity) to contain the elements, together with a way of keeping track of the number of "occupied" locations (the size of theList). If anArrayListhas grown to the point where its size is equal to its capacity, attempting to add another element will require it to replace the backing array with a larger one capable of holding the old contents and the new element, and with a margin for further expansion (the standard implementation actually uses a new array that is double the length of the old one). As we explained in Section 11.3, this leads to an amortized cost of O(1).The performance ofArrayListreflects array performance for "random-access" operations:setandgettake constant time. The downside of an array implementation is in inserting or removing elements at arbitrary positions, because that may require adjusting the position of other elements. (We have already met this problem with theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing List Implementations
- InhaltsvorschauTable 15.1 gives the comparative performance for some sample operations on
Listclasses. Even though the choice here is much narrower than with lists or even sets, the same process of elimination can be used. As with queues, the first question to ask is whether your application requires thread safety. If so, you should useCopyOnWriteArrayList, if you can—that is, if writes to the list will be relatively infrequent. If not, you will have to use a synchronized wrapper (see Section 17.3.1) aroundArrayListorLinkedList.Table 15.1: Comparative performance of different List implementations getaddcontainsnextremove(0)iterator.removeArrayListO(1)O(1)O(n)O(1)O(n)O(n)LinkedListO(n)O(1)O(n)O(1)O(1)O(1)CopyOnWrite-ArrayListO(1)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 16: Maps
- InhaltsvorschauThe
Mapinterface is the last of the major Collections Framework interfaces, and the only one that does not inherit fromCollection. It defines the operations that are supported by a set of key-to-value associations in which the keys are unique. These operations are shown in Figure 16.1 and fall into the following four groups, broadly parallel to the four operation groups ofCollection—adding elements, removing elements, querying collection contents, and providing different views of the contents of a collection.
Figure 16-1: MapAdding AssociationsV put(K key, V value) // add or replace a key-value association // return the old value (may be null) if the // key was present; otherwise returns null void putAll(Map<? extends K,? extends V> m) // add each of the key-value associations in // the supplied map into the receiverThe operations in this group are optional; calling them on an unmodifiable map will result in anUnsupportedOperationException.Removing Associationsvoid clear() // remove all associations from this map V remove(Object key) // remove the association, if any, with the // given key; returns the value with which it // was associated, or nullThe signature ofMap.removeis like that of theCollection.remove(see Section 12.1) in that it takes a parameter of typeObjectrather than the generic type. We discussed alternatives to this design in Section 2.6.Like the addition operations of the previous group, these removal operations are optional.Querying the Contents of a MapV get(Object k) // return the value corresponding to k, or // null if k is not present as a key boolean containsKey(Object k) // return true if k is present as a key boolean containsValue(Object v) // return true if v is present as a value int size() // return the number of associations boolean isEmpty() // return true if there are no associationsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Using the Methods of Map
- InhaltsvorschauOne problem with basing the to-do manager on priority queues, as we have done in the last two chapters, is that priority queues are unable to preserve the order in which elements are added to them (unless that can be incorporated in the priority ordering, for example as a timestamp or sequence number). To avoid this, we could use as an alternative model a series of FIFO queues, each one assigned to a single priority. A
Mapwould be suitable for holding the association between priorities and task queues;EnumMapin particular is a highly efficientMapimplementation specialized for use with keys which are members of anenum.This model will rely on aQueueimplementation that maintains FIFO ordering. To focus on the use of theMapmethods, let's assume a single-threaded client and use a series ofArrayDequesas the implementation:Map<Priority,ArrayDeque<Task>> taskMap = new EnumMap<Priority,ArrayDeque<Task>>(Priority.class); for (Priority p : Priority.values()) { taskMap.put(p, new ArrayDeque<Task>()); } // populate the lists, for example: taskMap.get(Priority.MEDIUM).add(mikePhone); taskMap.get(Priority.HIGH).add(databaseCode);Now, to get to one of the task queues—say, the one with the highest-priority tasks—we can write:Queue<Task> highPriorityTaskList = taskMap.get(Priority.HIGH);
Polling this queue will now give us the high priority to-dos, in the order in which they were entered into the system.To see the use of some of the other methods ofMap, let's extend the example a little to allow for the possibility that some of these tasks might actually earn us some money by being billable. One way of representing this would be by defining a classClient:class Client {...} Client acme = new Client("Acme Corp.",...);and creating a mapping from tasks to clients:Map<Task,Client> billingMap = new HashMap<Task,Client>(); billingMap.put(interfaceCode, acme);
We need to ensure that the system can still handle nonbillable tasks. We have a choice here: we can either simply not add the name of a nonbillable task into theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Implementing Map
- InhaltsvorschauThe implementations, eight in all, that the Collections Framework provides for
Mapare shown in Figure 16.2. We shall discussHashMap,LinkedHashMap,WeakHashMap,IdentityHashMap, andEnumMaphere; the interfacesNavigableMap,ConcurrentMap, andConcurrentNavigableMapare discussed, along with their implementations, in the sections following this one.
Figure 16-2: The structure of Map implementations in the Collections FrameworkFor constructors, the general rule forMapimplementations is like that forCollectionimplementations (see Section 12.3). Every implementation excludingEnumMaphas at least two constructors; takingHashMapas an example, they are:public HashMap() public HashMap(Map<? extends K,? extends V> m)
The first of these creates an empty map, and the second a map that will contain the key-value mappings contained in the supplied mapm. The keys and values of mapmmust have types that are the same as (or are subtypes of) the keys and values, respectively, of the map being created. Using this second constructor has the same effect as creating an empty map with the default constructor, and then adding the contents of mapmusingputAll. In addition to these two, the standard implementations have other constructors for configuration purposes.In discussingHashSetin Section 13.1.1, we mentioned that it delegates all its operations to a private instance ofHashMap. Figure 16.3(a) is similar to Figure 13.2, but without the simplification that removed the value elements from the map (all elements in aHashSetare stored as keys with the same constant value). The discussion in Section 13.1 of hash tables and their performance applies equally toHashMap. In particular,HashMapprovides constant-time performance forputandget. Although in principle constant-time performance is only attainable with no collisions, it can be closely approached by the use of rehashing to control the load and thereby to minimize the number of collisions.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - SortedMap and NavigableMap
- InhaltsvorschauLike
SortedSet, the subinterfaceSortedMap(see Figure 16.5) adds to its contract a guarantee that its iterators will traverse the map in ascending key order. Its definition is similar to that ofSortedSet, with methods such asfirstKeyandheadMapcorresponding to theSortedSetmethodsfirstandheadSet. Also likeSortedSet, theSortedMapinterface has been extended in Java 6 by the subinterfaceNavigableMap(see Figure 16.6). This section is structured like Section 13.2 for the same reasons:SortedMaphas been made obsolete byNavigableMap, but it may be helpful for developers prevented for the moment from using Java 6 to have the two interfaces treated separately.
Figure 16-5: SortedMap
Figure 16-6: NavigableMapASortedMapimposes an ordering on its keys, either that of their natural ordering or of aComparator; but in either case thecomparemethod must be consistent withequals, as theSortedMapwill usecompareto determine when a key is already in the map.The extra methods defined by theSortedMapinterface fall into three groups:Getting the First and Last ElementsK firstKey() K lastKey()
If the set is empty, these operations throw aNoSuchElementException.Retrieving the ComparatorComparator<? super K> comparator()
This method returns the map's key comparator if it has been given one, instead of relying on the natural ordering of the keys. Otherwise, it returnsnull.Finding SubsequencesSortedMap<K,V> subMap(K fromKey, K toKey) SortedMap<K,V> headMap(K toKey) SortedMap<K,V> tailMap(K fromKey)
These operations work like the corresponding operations inSortedSet: the key arguments do not themselves have to be present in the map, and the sets returned include thefromKeyEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - ConcurrentMap
- InhaltsvorschauMaps are often used in high-performance server applications—for example, as cache implementations—so a high-throughput thread-safe map implementation is an essential part of the Java platform. This requirement cannot be met by synchronized maps such as those provided by
Collections.synchronizedMap, because with full synchronization each operation needs to obtain an exclusive lock on the entire map, effectively serializing access to it. Locking only a part of the collection at a time—lock striping—can achieve very large gains in throughput, as we shall see shortly withConcurrentHashMap. But because there is no single lock for a client to hold to gain exclusive access, client-side locking no longer works, and clients need assistance from the collection itself to carry out atomic actions.That is the purpose of the interfaceConcurrentMap. It provides declarations for methods that perform compound operations atomically. There are four of these methods:V putIfAbsent(K key, V value) // associate key with value only if key is not currently present. // return the old value (may be null) if the key was present, // otherwise return null boolean remove(Object key, Object value) // remove key only if it is currently mapped to value. Returns // true if the value was removed, false otherwise V replace(K key, V value) // replace entry for key only if it is currently present. Return // the old value (may be null) if the key was present, otherwise // return null boolean replace(K key, V oldValue, V newValue) // replace entry for key only if it is currently mapped to oldValue. // return true if the value was replaced, false otherwiseConcurrentHashMapprovides an implementation ofConcurrentMapand offers a highly effective solution to the problem of reconciling throughput with thread safety. It is optimized for reading, so retrievals do not block even while the table is being updated (to allow for this, the contract states that the results of retrievals will reflect the latest update operations completed before the start of the retrieval). Updates also can often proceed without blocking, because aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - ConcurrentNavigableMap
- Inhaltsvorschau
ConcurrentNavigableMap(see Figure 16.7) inherits from bothConcurrentMapandNavigableMap, and contains just the methods of these two interfaces with a few changes to make the return types more precise. The range-view methods inherited fromSortedMapandNavigableMapnow return views of typeConcurrentNavigableMap. The compatibility concerns that preventedNavigableMapfrom overriding the methods ofSortedMapdon't apply here to overriding the range-view methods ofNavigableMaporSortedMap; because neither of these has any implementations that have been retrofitted to the new interface, the danger of breaking implementation subclasses does not arise. For the same reason, it is now possible to overridekeySetto returnNavigableSet.
Figure 16-7: ConcurrentNavigableMapThe relationship betweenConcurrentSkipListMapandConcurrentSkipListSetis like that betweenTreeMapandTreeSet; aConcurrentSkipListSetis implemented by aConcurrentSkipListMapin which every key is associated with the same standard value, so the mechanism and performance of the skip list implementation given in Section 13.2.2 applies equally here: the basic operations (get,put, andremove) perform in O(log n) time); iterators over the collection views executenextin constant time. These iterators are fail-fast.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Comparing Map Implementations
- InhaltsvorschauTable 16.1 shows the relative performance of the different platform implementations of
Map(the column headed "next" shows the cost of thenextoperation of iterators over the key set). As with the implementations of queue, your choice of map class is likely to be influenced more by the functional requirements of your application and the concurrency properties that you need.Table 16.1: Comparative performance of different Map implementations getcontainsKeynextNotesHashMapO(1)O(1)O(h/n)h is the table capacityLinkedHashMapO(1)O(1)O(1)IdentityHashMapO(1)O(1)O(h/n)h is the table capacityEnumMapO(1)O(1)O(1)TreeMapO(log n)O(log n)Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 17: The Collections Class
- InhaltsvorschauThe class
java.util.Collectionsconsists entirely of static methods that operate on or return collections. There are three main categories: generic algorithms, methods that return empty or prepopulated collections, and methods that create wrappers. We discuss these three categories in turn, followed by a number of other methods which do not fit into a neat classification.All the methods ofCollectionsare public and static, so for readability we will omit these modifiers from the individual declarations.The generic algorithms fall into four major categories: changing element order in a list, changing the contents of a list, finding extreme values in a collection, and finding specific values in a list. They represent reusable functionality, in that they can be applied toLists (or in some cases toCollections) of any type. Generifying the types of these methods has led to some fairly complicated declarations, so each section discusses the declarations briefly after presenting them.There are seven methods for reordering lists in various ways. The simplest of these isswap, which exchanges two elements and, in the case of aListwhich implementsRandomAccess, executes in constant time. The most complex issort, which transfers the elements into an array, applies a merge sort to them in time O(n log n), and then returns them to theList. All of the remaining methods execute in time O(n).void reverse(List<?> list) // reverse the order of the elements void rotate(List<?> list, int distance) // rotate the elements of the list; the element at index // i is moved to index (distance + i) % list.size() void shuffle(List<?> list) // randomly permute the list elements void shuffle(List<?> list, Random rnd) // randomly permute the list using the randomness source rnd <T extends Comparable<? super T>> void sort(List<T> list) // sort the supplied list using natural ordering <T> void sort(List<T> list, Comparator<? super T> c) // sort the supplied list using the supplied ordering void swap(List<?> list, int i, int j) // swap the elements at the specified positionsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Generic Algorithms
- InhaltsvorschauThe generic algorithms fall into four major categories: changing element order in a list, changing the contents of a list, finding extreme values in a collection, and finding specific values in a list. They represent reusable functionality, in that they can be applied to
Lists (or in some cases toCollections) of any type. Generifying the types of these methods has led to some fairly complicated declarations, so each section discusses the declarations briefly after presenting them.There are seven methods for reordering lists in various ways. The simplest of these isswap, which exchanges two elements and, in the case of aListwhich implementsRandomAccess, executes in constant time. The most complex issort, which transfers the elements into an array, applies a merge sort to them in time O(n log n), and then returns them to theList. All of the remaining methods execute in time O(n).void reverse(List<?> list) // reverse the order of the elements void rotate(List<?> list, int distance) // rotate the elements of the list; the element at index // i is moved to index (distance + i) % list.size() void shuffle(List<?> list) // randomly permute the list elements void shuffle(List<?> list, Random rnd) // randomly permute the list using the randomness source rnd <T extends Comparable<? super T>> void sort(List<T> list) // sort the supplied list using natural ordering <T> void sort(List<T> list, Comparator<? super T> c) // sort the supplied list using the supplied ordering void swap(List<?> list, int i, int j) // swap the elements at the specified positionsFor each of these methods, exceptsortandswap, there are two algorithms, one using iteration and another using random access. The methodsorttransfers theListelements to an array, where they are sorted using—in the current implementation—a mergesort algorithm with n log n performance. The methodswapalways uses random access. The standard implementations for the other methods in this section use either iteration or random access, depending on whether the list implements theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Collection Factories
- InhaltsvorschauThe
Collectionsclass provides convenient ways of creating some kinds of collections containing zero or more references to the same object. The simplest possible such collections are empty:<T> List<T> emptyList() // return the empty list (immutable) <K,V> Map<K,V> emptyMap() // return the empty map (immutable) <T> Set<T> emptySet() // return the empty set (immutable)
Empty collections can be useful in implementing methods to return collections of values, where they can be used to signify that there were no values to return. Each method returns a reference to an instance of a singleton inner class ofCollections. Because these instances are immutable, they can safely be shared, so calling one of these factory methods does not result in object creation. TheCollectionsfieldsEMPTY_SET,EMPTY_LIST, andEMPTY_MAPwere commonly used for the same purpose in Java before generics, but are less useful now because their raw types generate unchecked warnings whenever they are used.TheCollectionsclass also provides you with ways of creating collection objects containing only a single member:<T> Set<T> singleton(T o) // return an immutable set containing only the specified object <T> List<T> singletonList(T o) // return an immutable list containing only the specified object <K,V> Map<K,V> singletonMap(K key, V value) // return an immutable map, mapping only the key K to the value VAgain, these can be useful in providing a single input value to a method that is written to accept aCollectionof values.Finally, it is possible to create a list containing a number of copies of a given object.<T> List<T> nCopies(int n, T o) // return an immutable list containing n references to the object oBecause the list produced bynCopiesis immutable, it need contain only a single physical element to provide a list view of the required length. Such lists are often used as the basis for building further collections—for example, as the argument to a constructor or anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Wrappers
- InhaltsvorschauThe
Collectionsclass provides wrapper objects that modify the behavior of standard collections classes in one of three ways—by synchronizing them, by making them unmodifiable, or by checking the type of elements being added to them. These wrapper objects implement the same interfaces as the wrapped objects, and they delegate their work to them. Their purpose is to restrict the circumstances under which that work will be carried out. These are examples of the use of protection proxies (see Design Patterns by Gamma, Helm, Johnson, and Vlissides, Addison-Wesley), a variant of the Proxy pattern in which the proxy controls access to the real subject.Proxies can be created in different ways. Here, they are created by factory methods that wrap the supplied collection object in an inner class ofCollectionsthat implements the collection's interface. Subsequently, method calls to the proxy are (mostly) delegated to the collection object, but the proxy controls the conditions of the call: in the case of the synchronized wrappers, all method calls are delegated but the proxy uses synchronization to ensure that the collection is accessed by only one thread at a time. In the case of unmodifiable and checked collections, method calls that break the contract for the proxy fail, throwing the appropriate exception.As we explained in Section 11.5, most of the Framework classes are not thread-safe—by design—in order to avoid the overhead of unnecessary synchronization (as incurred by the legacy classesVectorandHashtable). But there are occasions when you do need to program multiple threads to have access to the same collection, and these synchronized wrappers are provided by theCollectionsclass for such situations.There are six synchronized wrapper factory methods, corresponding to the six pre-Java 6 interfaces of the Collections Framework. (No synchronized wrappers were provided in Java 6 forNavigableSetorNavigableMap. If they had been provided, there would be very few situations in which you would choose them over the thread-safe collectionsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Other Methods
- InhaltsvorschauThe
Collectionsclass provides a number of utility methods, some of which we have already seen in use. Here we review them in alphabetical order.addAll<T> boolean addAll(Collection<? super T> c, T... elements) // adds all of the specified elements to the specified collection.We have used this method a number of times as a convenient and efficient way of initializing a collection with individual elements, or with the contents of an array.asLifoQueue<T> Queue<T> asLifoQueue(Deque<T> deque) // returns a view of a Deque as a Last-in-first-out (Lifo) Queue.Recall from Chapter 14 that while queues can impose various different orderings on their elements, there is no standardQueueimplementation that provides LIFO ordering.Dequeueimplementations, on the other hand, all support LIFO ordering if elements are removed from the same end of the dequeue as they were added. The methodasLifoQueueallows you to use this functionality through the conveniently conciseQueueinterface.disjointboolean disjoint(Collection<?> c1, Collection<?> c2) // returns true if c1 and c2 have no elements in commonCare is needed in using this method; implementations may iterate over either collection, testing elements of one for containment in the other. So if the two collections determine containment differently, the result of this method is undefined. This could arise if, say, one collection is aSortedSet, for which containment is decided by natural ordering or a comparator, and the other is aSet, for which containment is decided by theequalsmethod of its elements.frequencyint frequency(Collection<?> c, Object o) // returns the number of elements in c that are equal to oIf the supplied valueoisnull, thenfrequencyreturns the number ofnullelements in the collectionc.newSetFromMapEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Zurück zu Java Generics and Collections
