VIRGIN FOREST OF JAVA
不要埋头苦干,要学习,学习,再学习。。。。。
powered by R.Zeus

by Budi Kurniawan
  

  Generics are the most important feature in J2SE 5.0. They enable you to write a type (a class or an interface) and create an instance of it by passing a reference type or reference types. The instance will then be restricted to only working with the type(s). For instance, the java.util.List interface in Java 5 has been made generic. When creating a List object, you pass a Java type to it and produce a List instance that can only work with objects of that type. That is, if you pass String, the List instance can only hold String objects; if you pass Integer, the instance can only store Integer objects. In addition to parameterized types, you can create parameterized methods, too.

  The first benefit of generics is stricter type checking at compile time. This is most apparent in the Collections framework. In addition, generics eliminate most type castings you had to perform when working with the Collections framework in pre-5 JDKs.

  This article teaches you to use and write generic types. It starts with the section "Life without Generics," which reminds us what we missed in earlier versions of JDKs. Then, it presents some examples of generic types. After the discussions of the syntax and the use of generic types with bounds, this article concludes with a section that explains how to write generic types.


    Life Without Generics


  All Java classes derive from
java.lang.Object, which means that all Java objects can be cast to Object. Because of this, in JDKs prior to version 5, many methods in the Collections framework accept an Object argument. That way, the collections became general-purpose utility types that could hold objects of any type. That imposed unpleasant consequences.

For example, the add method of the List class in pre-5 JDKs accepted an Object argument:

public boolean add(java.lang.Object element)

As a result, you could pass an object of any type to add. The use of Object is by design. Otherwise, it could only work with a specific type of object, and there would then have to be different List types; e.g., StringList, EmployeeList, AddressList, etc.

The use of Object in add is fine, but consider the get method, which returns a member element of a List. Here is its signature prior to JDK 5.

public java.lang.Object get(int index)

  throws IndexOutOfBoundsException

 

get returns an Object. Here is where the unpleasant consequences start to kick in. Suppose you have stored two String objects in a List:

List stringList1 = new ArrayList();

stringList1.add("Java 5");

stringList1.add("with generics");

 

When retrieving a member from stringList1, you get an Object. In order to work with the original type of the member element, you must first downcast it to String.

String s1 = (String) stringList1.get(0);

In addition, if you ever add a non-String object to stringList1, the code above will throw a ClassCastException.

With generic types, you can also create List instances with special purposes. For example, you can create a List instance that only accepts String objects, another that only accepts Employee objects, and so on. This also applies to other types in the Collections framework.

Introducing Generic Types

Just like a method can have parameters, a generic type can accept parameters, too. This is why a generic type is often called a parameterized type. Instead of passing primitives or object references in parentheses as it is with methods, you pass reference type(s) in angle brackets to generic types.

Declaring a generic type is like declaring a non-generic one, except that you use angle brackets to enclose the list of type variables for the generic type (MyType<typeVar1, typeVar2, ...>).

For example, to declare a java.util.List in JDK 5, you write List<E> myList;.

E is called a type variable, meaning a variable that will be replaced by a type. The value substituting for a type variable will then be used as the argument type or the return type of a method or methods in the generic type. For the List interface, when an instance is created, E will be used as the argument type of add and other methods. E will also be used as the return type of get and other methods. Here are the signatures of add and get.

boolean add<E o>

E get(int index)

Note: A generic type that uses a type variable E allows you to pass E when declaring or instantiating the generic type. Additionally, if E is a class, you may also pass a subclass of E; if E is an interface, you may also pass a class implementing E.

If you pass String to a declaration of List, as in:

List<String> myList;

then the add method of myList will expect a String object as its argument and its get method will return a String. Because get returns a specific object type, no downcasting is required.

Note: By convention, you use a single uppercase letter for type variable names.

To instantiate a generic type, you pass the same list of parameters as when you declare it. For instance, to create an ArrayList that works with String, you pass String in angle brackets.

List<String> myList = new ArrayList<String>();

As another example, java.util.Map is defined as:

public interface Map<K,V>

K is used to denote the type of map keys and V the type of map values. The put and values methods have the following signatures:

V put(K key, V value)

Collection<V> values()

Note: A generic type must not be a direct or indirect child class of java.lang.Throwable, because exceptions are thrown at run time, and therefore it is not possible to predict what type of exception that might be thrown at compile time.

As an example, Listing 1 compares List in JDK 1.4 and JDK 5.

package com.brainysoftware.jdk5.app16;

import java.util.List;

import java.util.ArrayList;

 

public class GenericListTest {

  public static void main(String[] args) {

    // in JDK 1.4

    List stringList1 = new ArrayList();

    stringList1.add("Java 1.0 - 5.0");

    stringList1.add("without generics");

    // cast to java.lang.String

    String s1 = (String) stringList1.get(0);

    System.out.println(s1.toUpperCase());

 

    // now with generics in JDK 5

    List<String> stringList2 = new ArrayList<String>();

    stringList2.add("Java 5.0");

    stringList2.add("with generics");

    // no need for type casting

    String s2 = stringList2.get(0);

    System.out.println(s2.toUpperCase());

  }

}

In Listing 1, stringList2 is a generic List. The declaration List<String> tells the compiler that this instance of List can only hold String objects. Of course, on other occasions, you can create instances of List that work with other types of objects. Note, though, that when retrieving member elements of the List instance, no downcasting is necessary because its get method returns the intended type, namely String.

Note: With generic types, type checking is done at compile time.

What's interesting here is the fact that a generic type is itself a type and can be used as a type variable. For example, if you want your List to store lists of Strings, you can declare the List by passing List<String> as its type variable, as in:

List<List<String>> myListOfListsOfStrings;

To retrieve the first String from the first List in myList, you would use:

String s = myListOfListsOfStrings.get(0).get(0);

The next listing presents a ListOfListsTest class that demonstrates a List (named listOfLists) that accepts a List of String objects.

package com.brainysoftware.jdk5.app16;

import java.util.ArrayList;

import java.util.List;

public class ListOfListsTest {

  public static void main(String[] args) {

    List<String> listOfStrings = new ArrayList<String>();

    listOfStrings.add("Hello again");

    List<List<String>> listOfLists = new ArrayList<List<String>>();

    listOfLists.add(listOfStrings);

    String s = listOfLists.get(0).get(0);

    System.out.println(s); // prints "Hello again"

  }

}

Additionally, a generic type can accept more than one type variable. For example, the java.util.Map interface has two type variables. The first defines the type of its keys and the second the type of its values. Here's an example of how to use a generic Map.

package com.brainysoftware.jdk5.app16;

import java.util.HashMap;

import java.util.Map;

public class MapTest {

  public static void main(String[] args) {

    Map<String, String> map = new HashMap<String, String>();

    map.put("key1", "value1");

    map.put("key2", "value2");

    String value1 = map.get("key1");

  }

}

In this case, to retrieve a String value indicated by key1, you do not need to perform type casting.

Using Generic Types Without Type Parameters

Now that the collection types in J2SE 5.0 have been made generic, what about legacy codes that used the same types? Fortunately, they will still work in Java 5, because you can use generic types without type parameters. For example, you can still use the List interface the old way, as demonstrated by the following part of the earlier GenericListTest.

List stringList1 = new ArrayList();
stringList1.add("Java 1.0 - 5.0");
stringList1.add("without generics");
String s1 = (String) stringList1.get(0);

A generic type used without parameters is called a raw type. This means that code written for JDK 1.4 and earlier versions will continue working in Java 5.

One thing to note, though, is that the JDK 5 compiler expects you to use generic types with parameters. Otherwise, the compiler will issue warnings, thinking that you may have forgotten to define type variables with the generic type. For example, compiling this code gives you the following warning, because the first List was used as a raw type.

Note: com/brainysoftware/jdk5/app16/GenericListTest.java
        uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
 

You have these options at your disposal if you do not want to get warnings when working with raw types:

  • Compile with the -source 1.4 flag.
  • Use the @SupressWarnings("unchecked") annotation.
  • Upgrade your code to use List<Object>. Instances of List<Object> can accept any type of object and behave like a raw type List. However, the compiler will not complain.

Warning: Raw types are available for backward compatibility. New development should shun raw types--it is possible that future versions of Java will not allow them.

Using the ? Wildcard

I mentioned that if you declare a List<aType>, the List instance works with instances of aType, and you can store objects of one of these types:

  • An instance of aType.
  • An instance of a subclass of aType, if aType is a class.
  • An instance of a class implementing aType, if aType is an interface.

Note, however, that a generic type is a Java type by itself, just like java.lang.String or java.io.File. Passing different lists of type variables to a generic type results in different types. For example, list1 and list2 below reference different types of objects.

List<Object> list1 = new ArrayList<Object>();
List<String> list2 = new ArrayList<String>();

list1 references a List of java.lang.Objects and list2 references a List of Strings. Even though String is a subclass of Object, List<String> has nothing to do with List<Object>. Therefore, passing a List<String> to a method that expects a List<Object> will raise a compile-time error. The following listing shows this.

package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
 
public class AllowedTypeTest {
  public static void doIt(List<Object> l) {
  }
  public static void main(String[] args) {
    List<String> myList = new ArrayList<String>();
    // this will generate a compile error
    doIt(myList);
  }
}

This code won't compile, because you are trying to pass the wrong type to the method doIt. doIt expects an instance of List<Object> and you are passing it an instance of List<String>.

The solution to this problem is the ? wildcard. List<?> means a list of objects of any type. Therefore, the doIt method should be changed to:

public static void doIt(List<?> l) {
}

There are circumstances where you want to use the wildcard. For example, if you have a printList method that prints the members of a List, you may want to make it accept a List of any type. Otherwise, you would end up writing many overloads of printList. The following listing shows a printList method that uses the ? wildcard.

package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
 
public class WildCardTest {
 
  public static void printList(List<?> list) {
    for (Object element : list) {
      System.out.println(element);
    }
  }
  public static void main(String[] args) {
    List<String> list1 = new ArrayList<String>();
    list1.add("Hello");
    list1.add("World");
    printList(list1);
 
    List<Integer> list2 = new ArrayList<Integer>();
    list2.add(100);
    list2.add(200);
    printList(list2);
  }
}

This code demonstrates that List<?> in the printList method means a List of any type. Note, however, that it is illegal to use the wildcard when declaring or creating a generic type, like this:

List<?> myList = new ArrayList<?>(); // illegal

If you want to create a List that can accept any type of object, use Object as the type variable, as in the following line of code:

List<Object> myList = new ArrayList<Object>();

Using Bounded Wildcards in Methods

In the previous section, you learned that passing different type variables to a generic type creates different Java types, despite a parent-child relationship between the type variables. In many cases, you may want a method to accept a List of different types. For example, if you have a getAverage method that returns the average of numbers in a list, you may want to pass a list of integers, or a list of floats, or a list of another number type. However, if you write List<Number> as the argument type to getAverage, you won't be able to pass a List<Integer> instance or a List<Double> instance, because List<Number> is a different type from List<Integer> or List<Double>. You can use List as a raw type or use a wildcard, but this deprives you of type-safety checking at compile time, because you can also pass a list of anything, such as an instance of List<String>. You could use List<Number>, but you must always pass a List<Number> to the method. This would make your method less useful, because you probably work with List<Integer> or List<Long> more often than you do with List<Number>.

J2SE 5.0 adds another rule to circumvent this restriction, by allowing you to define an upper bound of a type variable. This way, you can pass a type or its subtype. In the case of the getAverage method, you may be able to pass a List<Number> or a List of instances of a Number subclass, such as List<Integer> or List<Float>.

The syntax for using an upper bound is as follows: GenericType<? extends upperBoundType>. For example, for the getAverage method, you would write: List<? extends Number>. The following example illustrates the use of such a bound.

package com.brainysoftware.jdk5.app16;
import java.util.ArrayList;
import java.util.List;
public class BoundedWildcardTest {
  public static double getAverage(List<? extends Number> numberList)
  {
    double total = 0.0;
    for (Number number : numberList)
      total += number.doubleValue();
    return total/numberList.size();
  }
 
  public static void main(String[] args) {
    List<Integer> integerList = new ArrayList<Integer>();
    integerList.add(3);
    integerList.add(30);
    integerList.add(300);
    System.out.println(getAverage(integerList)); // 111.0
    List<Double> doubleList = new ArrayList<Double>();
    doubleList.add(3.0);
    doubleList.add(33.0);
    System.out.println(getAverage(doubleList)); // 18.0
  }
}

Thanks to the upper bound, the getAverage method in this listing allows you to pass a List<Number> or a List of instances of any subclass of java.lang.Number.

Lower Bounds

The extends keyword is used to define an upper bound of a type variable. Though usable in very few applications, it is also possible to define a lower bound of a type variable, by using the super keyword. For example, using List<? super Integer> as the type to a method argument indicates that you can pass a List<Integer> or a List of objects whose class is a superclass of java.lang.Integer.

Writing Generic Types

The previous sections concentrated on using generic types, notably the ones in the Collections framework. Now it's time to learn to write your own generic types.

Basically, writing a generic type is not much different from writing other types, except for the fact that you declare a list of type variables that you intend to use somewhere in your class. These type variables come in angle brackets after the type name. For example, the Point class in the following is a generic class. A Point object represents a point in a coordinate system and has an X component (abscissa) and a Y component (ordinate). By making Point generic, you can specify the degree of accuracy of a Point instance. For example, if a Point object needs to be very accurate, you can pass Double as the type variable. Otherwise, Integer will suffice.

package com.brainysoftware.jdk5.app16;
public class Point<T> {
  T x;
  T y;
  public Point(T x, T y) {
    this.x = x;
    this.y = y;
  }
  public T getX() {
    return x;
  }
  public T getY() {
    return y;
  }
  public void setX(T x) {
    this.x = x;
  }
  public void setY(T y) {
    this.y = y;
  }
}

In this example, T is the type variable for the Point class. T is used as the return value of both getX and getY, and as the argument type for setX and setY. In addition, the constructor also accepts two T type variables.

Using Point is just like using other generic types. For example, the following code creates two Point objects: point1 and point2. The former passes Integer as the type variable, the latter, Double.

Point<Integer> point1 = new Point<Integer>(4, 2);
point1.setX(7);
Point<Double> point2 = new Point<Double>(1.3, 2.6);
point2.setX(109.91);

Summary

Generics enable stricter type checking at compile time. Used especially in the Collections framework, generics make two contributions. First, they add type checking to collection types at compile time, so that the type of objects that a collection can hold is restricted to the type passed to it. For example, you can now create an instance of java.util.List that holds strings and will not accept Integers or other types. Second, generics eliminate the need for type casting when retrieving an element from a collection.

Generic types can be used without type variables; i.e., as raw types. This provision makes it possible to run pre-Java 5 code with JRE 5. For new applications, you are advised against using raw types, as future releases of Java may not support them.

You have also learned that passing different type variables to a generic type results in different Java types. That is, List<String> is a different type than List<Object>. Even though String is a subclass of java.lang.Object, passing a List<String> to a method that expects a List<Object> generates a compile error. Methods that expect a List of anything can use the ? wildcard. List<?> means a List of objects of any type.

Finally, you have seen that writing generic types is not that different from writing ordinary Java types. You just need to declare a list of type variables in angle brackets after the type name. You then use these type variables as the types of method return values or as the types of method arguments. By convention, a type variable name consists of a single uppercase letter.

Budi Kurniawan is a senior J2EE architect and author.


Generics in J2SE 5.0-http://www.onjava.com/pub/a/onjava/2005/07/06/generics.html

posted on 2005-08-13 18:31 R.Zeus 阅读(311) 评论(0)  编辑  收藏 所属分类: J2SE

只有注册用户登录后才能发表评论。


网站导航: