Download Generics Article

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
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.