Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
http://www.informit.com/articles/article.asp?p=708377&seqNum=1&rl=1 Generics 101: Mastering the Fundamentals By Jeff Friesen. Date: Mar 23, 2007. Contents 1. Type Safety 2. Generic Types 3. Generic Methods 4. Conclusion Article Description Java 2 Standard Edition 5.0 introduced generics to Java developers. Since their inclusion in the Java language, generics have proven to be controversial: many language enthusiasts believe that the effort to learn generics outweighs their importance to the language. Fortunately, as Jeff Friesen discusses here, you can master the fundamentals without expending much effort. Java 2 Standard Edition 5.0 introduced generics to Java developers. Since their inclusion in the Java language, generics have proven to be controversial: many language enthusiasts believe that the effort to learn generics outweighs their importance to the language. Fortunately, as this article shows, you can master the fundamentals without expending much effort. This article helps you master generics fundamentals by first focusing on type safety, in which you discover the motivation for adding generics to Java. The article next explores generic types and generic methods, which manifest generics at the source code level. For brevity, this article focuses on essentials and does not delve into too many details—complete coverage of generics would probably occupy an entire book. NOTE I developed and tested this article’s code with Sun’s Java 2 Standard Edition 5.0 SDK. Windows 98 SE was the underlying platform. You can download the code here. Type Safety Java developers strive to create Java programs that work correctly for their clients—no developer wants code to fail and then be faced with an angry client. Failure is typically indicated through thrown exceptions; ClassCastExceptions (resulting from improper casting) are among the worst because they usually are not expected (and are not logged so that their causes can be found). Take a look at Listing 1. Listing 1 BeforeGenerics.java // BeforeGenerics.java import java.util.*; public class BeforeGenerics { public static void main (String [] args) { List l = new ArrayList (); l.add (new Double (101.0)); l.add (new Double (89.0)); l.add (new Double (33.0)); double avg = calculateAverage (l); System.out.println ("Average = " + avg); l.add ("Average"); avg = calculateAverage (l); System.out.println ("Average = " + avg); } static double calculateAverage (List l) { double sum = 0.0; Iterator iter = l.iterator (); while (iter.hasNext ()) sum += ((Double) iter.next ()).doubleValue (); return sum / l.size (); } } Listing 1 averages the floating-point values in a java.util.List-referenced java.util.ArrayList of Double objects. Somewhere in this source code lurks a bug that leads to a ClassCastException. If you compile BeforeGenerics.java with a pre-J2SE 5.0 compiler, no error/warning message outputs. Instead, you only discover this bug when you run the program: Average = 74.33333333333333 Exception in thread "main" java.lang.ClassCastException: java.lang.String at BeforeGenerics.calculateAverage(BeforeGenerics.java:28) at BeforeGenerics.main(BeforeGenerics.java:19) From a technical perspective, the ClassCastException results from l.add ("Average"); and sum += ((Double) iter.next ()).doubleValue ();. This exception is thrown when iter.next() returns the previously added String and the cast from String to Double is attempted. This exception indicates that the program is not type safe; it arises from assuming that collections are homogeneous—they store objects of a specific type or of a family of related types. In reality, these collections are heterogeneous—they are capable of storing any type of object because the element type of collections is Object. Although ClassCastExceptions can occur from many sources, they frequently result from violating the integrity of a collection that is considered to be homogeneous. Solving collection-oriented type safety problems motivated the inclusion of generics in the Java language (and an overhaul of the Collections API to support generics). With generics, the compiler can now detect type-safety violations. Examine Listing 2. Listing 2 AfterGenerics.java // AfterGenerics.java import java.util.*; public class AfterGenerics { public static void main (String [] args) { List<Double> l = new ArrayList<Double> (); l.add (101.0); l.add (89.0); l.add (33.0); double avg = calculateAverage (l); System.out.println ("Average = " + avg); l.add ("Average"); avg = calculateAverage (l); System.out.println ("Average = " + avg); } static double calculateAverage (List<Double> l) { double sum = 0.0; Iterator<Double> iter = l.iterator (); while (iter.hasNext ()) sum += iter.next (); return sum / l.size (); } } Although Listing 2 is similar to Listing 1, there are fundamental differences. For example, List<Double> l = new ArrayList<Double> (); replaces List l = new ArrayList ();. Specifying Double between angle brackets tells the compiler that l references a homogeneous list of Double objects—Double is the element type. It is necessary to specify <Double> after both List and ArrayList to prevent non-Double objects from being stored in the list, in calculateAverage()’s parameter list to prevent this method from being able to store non-Doubles in the list, and after Iterator to eliminate a (Double) cast when retrieving objects from the list. Along with four instances of <Double> that provide type information to the compiler, Listing 2 also uses autoboxing to simplify the code. For example, the compiler uses autoboxing with this type information to expand l.add (101.0); to l.add (new Double (101.0));, and to expand sum += iter.next (); to sum += ((Double) iter.next ()).doubleValue ();. Because the compiler uses the extra type information provided by <Double> to verify that the list can only contain Doubles, the (Double) cast is no longer needed (although it can be specified). Eliminating this cast reduces source code clutter. Furthermore, this type information aids the compiler in detecting attempts to store non-Double objects in the list: AfterGenerics.java:18: cannot find symbol symbol : method add(java.lang.String) location: interface java.util.List<java.lang.Double> l.add ("Average"); ^ 1 error The <Double> specification is an example of generics, a set of language enhancements that promote type safety through generic types and generic methods. The following two sections explore each of these enhancement categories; they provide you with an overview of what generic types and generic methods have to offer. 2. Generic Types | Next Section Generic Types A generic type is a class or interface with a type-generalized implementation. The class or interface name is followed by a formal type parameter section, which is surrounded with angle brackets (<>). Although space characters can appear between the type name and the open angle bracket ( <), it is conventional to avoid placing spaces between these syntax elements: class classname<formal-type-parameter-section> { } interface interfacename<formal-type-parameter-section> { } The formal type parameter section declares one or more formal type parameters, in which each parameter describes a range of types. The E in public class ArrayList<E> and T extends Number in class Vertex<T extends Number> are examples of formal type parameters. If multiple formal type parameters are present, a comma separates each parameter from its predecessor: <type-parm1, type-parm2, type-parm3, ...>. For example, public interface Map<K,V>’s formal type parameter section declares K and V formal type parameters. Each formal type parameter identifies a type variable. Examples of type variables include E in public class ArrayList<E>, T in class Vertex<T extends Number>, and K and V in public interface Map<K,V>. Formal type parameters and type variables are often identical. By convention, type variables are represented by single uppercase letters to distinguish them from the names of their enclosing classes or interfaces. It is common to use T and surrounding letters such as S to name type variables; the Collections API also uses E to denote an element type, K to denote a key type, and V to denote a value type. A generic class can specify type variables as the types of its non-static fields, non-static method parameters, and local variables; and as non-static method return types. For example, ArrayList specifies type variable E as the type of its private transient E[] elementData field and as the return type of its public E get(int index) method. Listing 3 presents another example. Listing 3 GenericClass.java // GenericClass.java public class GenericClass<T> { T field; public T getField () { return field; } public void setField (T field) { this.field = field; } public void someOtherMethod () { T local = field; // Use local in some way. } } A generic type is instantiated by replacing its type variables with actual type arguments. This instantiation is also known as a parameterized type. For example, GenericClass<T> is a generic type, GenericClass<String> is a parameterized type, and String is an actual type argument. Think of this instantiation as turning Listing 3’s generic class into this class: public class GenericClass { String field; public String getField () { return field; } public void setField (String field) { this.field = field; } public void someOtherMethod () { String local = field; // Use local in some way. } } Type Variable Bounds A type variable can be specified as being unbounded or upper-bounded in the formal type parameter section. For unbounded type variables (such as E in ArrayList<E>), you can pass any actual type argument ranging from Object down to the lowest subclass or interface to the type variable. However, if you need to restrict the range of types that a type variable can take on (such as being passed only Number and its subclasses), you must specify an upper bound. You can specify single or multiple upper bounds. These upper bounds let you set an upper limit on what types can be chosen as actual type arguments. A type variable can be assigned a single upper bound via extends. For example, in class Foo<T extends Number>, T is assigned Number as its single upper bound. This type variable can receive only Number and subtypes as actual type arguments—Foo<Float> is legal; Foo<String> is illegal. More than one upper bound can be assigned to a type variable. The first (leftmost) bound is a class or an interface; all remaining bounds must be interfaces. An ampersand (&) is used to separate each bound from its predecessor. The following generic class accepts only types that subclass Number and implement Comparable (which is redundant since Number implements this interface): class CMP<T extends Number & Comparable<T>> { CMP (T first, T second) { System.out.println (first.compareTo (second)); } } Type Variable Scope As revealed in Listing 3, a type variable’s scope (visibility) is the entire class or interface in which its formal type parameter section is present; static members are exceptions. This scope also extends to the formal type parameter section, where a type variable can be declared in terms of itself (as shown below) or a previously declared type variable: class CMP<T extends Comparable<T>> { CMP(T first, T second) { System.out.println (first.compareTo (second)); } } The example’s formal type parameter section employs T extends Comparable<T> to require actual type arguments passed to T to implement the Comparable interface. Because String and Integer implement this interface, new CMP<String> ("ABC", "DEF"); and new CMP<Integer> (new Integer (1), new Integer (1)); are legal. A type variable’s scope can be overridden by declaring a same-named type variable in the formal type parameter section of a nested class. In other words, the nested class’s type variable hides the outer class’s type variable. Because this scenario can lead to confusion, it is best to choose different names for these type variables: // Outer’s T and Inner’s T are two different type variables. // Outer’s T can be any type; Inner’s T is restricted to // Number and subtypes of Number (such as Float or Integer). class Outer<T> { class Inner<T extends Number> { } } // It is less confusing to specify a different name for the // nested class’s type variable. Here, S has been chosen to // make the distinction. class Outer<T> { class Inner<S extends Number> { } } // The following Outer and Inner class instantiations prove // that there are two different type variables. Outer<String> o = new Outer<String> (); Outer<String>.Inner<Float> i = o.new Inner<Float> (); // T is assigned type argument String; S is assigned Float. Five Kinds of Actual Type Arguments When instantiating a generic type, which results in a parameterized type, an actual type argument is supplied for each formal type parameter. These arguments, which are specified as a comma-separated list between a pair of angle brackets, can be concrete types, concrete parameterized types, array types, type variables, or wildcards (depending on context and the generic type): concrete type: The actual type argument is the name of a class or an interface that is passed to the type variable. Example: List<Integer> list = new ArrayList<Integer> ();. Each List element is an Integer. concrete parameterized type: The actual type argument is the name of a parameterized type that is passed to the type variable. Example: List<List<Integer>> list = new ArrayList<List<Integer>> ();. Each List element is a List of Integers. array type: The actual type argument is an array. Example: List<int []> list = new ArrayList<int []> ();. Each List element is an array of ints. type variable: The actual type argument is a type variable. Example: List<T> list = new ArrayList<T> ();. Each list element is the type specified by T when the enclosing type is instantiated. wildcard: The actual type argument is a ?. I’ll discuss this argument when I explore the wildcard type. In addition to this list of actual type arguments, it is also possible to specify no arguments. The resulting type, which is known as a raw type, exists to allow legacy Java code (that is, code written prior to generics) to be compiled with the J2SE 5.0 compiler (albeit with warning messages). Example: List l = new ArrayList ();. Wildcard Type Having studied Java’s object-oriented capabilities, you understand that a subtype is a kind of supertype—an ArrayList is a kind of List, String and Integer are kinds of Objects, and so on. However, are List<String> and List<Integer> kinds of List<Object>s? Examine the following code: List<Integer> li = new ArrayList<Integer> (); List<Object> lo = li; lo.add (new Object ()); Integer i = li.get (0); The first line is correct; the second line is not correct. If it were correct, the list of integers would also be a list of objects, the third line would succeed, and the fourth line would throw a ClassCastException—you cannot cast Object to Integer. Because type safety would be violated, List<Integer> is not a kind of List<Object>. This example can be generalized: For a given subtype x of type y, and given G as a generic type declaration, G<x> is not a subtype of G<y>. This fact might be the hardest thing to learn about generics because we are used to thinking about subtypes as being kinds of supertypes. Also, it can trip you up when writing code. Consider a first attempt at writing a method that outputs any collection: public static void outputCollection (Collection<Object> c) { for (Object o: c) System.out.println (o); } This method can be used to output any collection whose actual type argument is Object –- Set<Object> set = new TreeSet<Object> (); outputCollection (set); and List<Object> list = new ArrayList<Object> (); outputCollection (list); are legal, but List<String> list = new ArrayList<String> (); outputCollection (list); is illegal. The problem is due to the fact that Collection<Object> is only the supertype of all other collection types whose actual type arguments are Object. If you need to pass other actual type arguments, you need to replace Object in the previous outputCollection() method with a ?. This character identifies the wildcard type, which accepts any actual type argument: public static void outputCollection (Collection<?> c) { for (Object o: c) System.out.println (o); } By simply changing to the wildcard type, List<String> list = new ArrayList<String> (); outputCollection (list); is now legal. Because Collection<?> is the supertype of all kinds of collections, you can pass any actual type argument to the method, assign each collection element to Object (which is safe), and access the element. But you cannot add elements to the collection: public static void copyCollection (Collection<?> c1, Collection<?> c2) { for (Object o: c1) c2.add (o); } The method above, which attempts to copy c1’s elements to a presumably empty c2, will not compile. Although c1’s elements can be assigned to Object, c2’s element type is unknown. If this type is anything other than Object (such as String), type safety is violated. You need a generic method to copy c1’s elements to c2. 3. Generic Methods | Next SectionPrevious Section Generic Methods A generic method is a static method or a non-static method with a type-generalized implementation. A formal type parameter section (surrounded by angle brackets) precedes the method’s return type; its syntax and meaning is identical to the generic type’s formal type parameter section. The overall syntax for a generic method is as follows: < formal-type-parameter-section > return-type method-name ( parameter-list ) { } The Collections API contains many examples of generic methods. For example, java.util.Collections specifies a public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) method that replaces a specific value in a List with another value. Another example is the copyCollection() method that is presented in Listing 4. Listing 4 GenericMethod.java // GenericMethod.java import java.util.*; public class GenericMethod { public static void main (String [] args) { List <String> list = new ArrayList<String> (); list.add ("ABC"); list.add ("DEF"); list.add ("ABC"); outputCollection ("List", list); Set<String> set = new TreeSet<String> (); copyCollection (list, set); outputCollection ("Set", set); } static <T> void copyCollection (Collection<T> c1, Collection<T> c2) { for (T o: c1) c2.add (o); } static void outputCollection (String header, Collection<?> c) { System.out.println (header); System.out.println (); for (Object o: c) System.out.println (o); System.out.println (); } } Listing 4’s copyCollection() method solves the problem of how to add elements to a collection. Instead of employing wildcards and casting to Object, it employs a type variable (T). Because both collections use the same type variable, it is safe to add one collection’s elements to another collection. If you recall Listing 3’s getField() and setField() methods, you might be wondering how they compare with Listing 4’s copyCollection() method. After all, these three methods employ type variables. Unlike Listing 3’s methods, however, copyCollection() is not declared within a generic type. This is true for static utility methods like those methods in the Collections class. Also, copyCollection() has a formal type parameter section. Although the compiler infers the type for this method’s single type variable during the method’s invocation, you could explicitly specify the actual type argument if desired. This would lead to GenericMethod.<String>copyCollection (list, set);. 4. Conclusion | Next SectionPrevious Section Conclusion You can learn more about generics by studying Sun’s "Generics in the Java Programming Language" PDF-based tutorial (around 70 kilobytes). Another excellent resource that you need to study is Angelika Langer’s Java Generics FAQ.