Chapter 15

Introduction to Generics

In the previous lesson you saw an example of a collection that stores objects of different types (see Listing 14-2). During the run time, that program would test the actual type of each object and cast it to an appropriate type — Customer or Order. If some code adds an element of another (unexpected) data type, this will result in a casting error, IllegalCastException.

Starting from Java 5 you can use generics, which enables you to use parameterized data types — you can declare an object, collection, or method without specifying a concrete data type, shifting the definition of concrete types to the code that will be using these objects, collections, or methods. And the good part is that by using such generic notation you’ll get help from Java compiler, which will not allow you to use objects of the “wrong” types that don’t match the declaration. In other words, you can catch improper data types earlier, during the compilation phase.

Generics with Classes

Not only Java methods can have parameters, but classes can have them as well. Consider the ArrayList from Listing 14-2, which is a kitchen sink–like storage that can hold pretty much any object. But if you add the parameterized type Customer in angle brackets to the declaration of the customers collection (see Listing 15-1), any attempt to place an Order object there will generate the following compiler error:

The method add(Customer) in the type ArrayList<Customer> is not applicable
for the arguments (Order).

Think of it this way: ArrayList can be used to store any objects, and using generics allows you to put a constraint on the types of objects allowed in a specific instance of ArrayList. This is an example of a parameterized object, which is just one use for generics.

download.eps

Listing 15-1: Using generics in the collection

import java.util.ArrayList;
import java.util.ArrayList;
 
public class TestGenericCollection {
 
       public static void main(String[] args) {
 
              ArrayList<Customer> customers = new ArrayList<Customer>();
              Customer cust1 = new Customer("David","Lee");
              customers.add(cust1);
              Customer cust2 = new Customer("Ringo","Starr");
              customers.add(cust2);
              Order ord1= new Order();
 
              customers.add(ord1); // Compiler error
 
        }
}

Getting an error during compilation is better than getting run-time cast exceptions. What makes the ArrayList class capable of rejecting the unwanted data types? Open the source code of the ArrayList itself (pressing F3 in Eclipse shows the source code of any class or interface, if available). It starts as follows:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable 

This magic <E> after the class name tells the Java compiler that some types of elements will be stored in this class, but which ones will remain unknown until a concrete instance of ArrayList is created. In Listing 15-1 the type parameter <Customer> replaces <E>, and from now on the collection customers will accept only instances of Customer objects.

I’d like to stress that this <E> notation is used only during the declaration of the type. The code in Listing 15-1 does not include <E>. The compiler replaces <E> with Customer and erases the parameterized data type. This process is known as type erasure — it’s done for compatibility with code written in older versions of Java that didn’t have generics.

Now you can simplify the code from Listing 14-2 by removing casting (see Listing 15-2). Why? Because with generics, when the compiler sees a specific type, it automatically generates the bytecode, which performs casting internally! That’s why you don’t even need to cast the data returned by the method get(i) from Object to Customer any longer. Besides, you’re guaranteed that the collection customers will have only Customer instances.

download.eps

Listing 15-2: Iterating through customers without casting

ArrayList<Customer> customers = new ArrayList<Customer>();
 
// The code to populate customers is omited for brevity 
 
// Iterate through the list customers and do something with each 
//  element of this collection. No casting required. 
 
for (Customer c: customers){
       c.doSomething();
}

Defining Generics

If you’ll be creating your own class for storing other objects, you can use any letter(s) in angle brackets to declare that your class will use parameterized types. Some developers use <E> for element; some prefer <T> for type. Such a letter will be replaced by a concrete type when used with generics’ notation during concrete variable declaration. Open the source code of the Java class Hashtable and you’ll see <K,V>, which stands for key and value:

public class Hashtable<K,V> extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {...}

Again, what types will be used for storing keys and values is decided when the Hashtable is used. You can use a parameterized type for declaring variables where you’d use regular data types. Listing 15-3 shows a fragment from the source code of the interface java.util.List. The interface declaration uses <E> as a data type.

download.eps

Listing 15-3: Fragment from java.util.List interface

package java.util;
 
public interface List<E> extends Collection<E> {
 
    Iterator<E> iterator();
 
    <T> T[] toArray(T[] a);
 
    boolean add(E e);
 
    boolean containsAll(Collection<?> c);
 
    boolean addAll(Collection<? extends E> c);
 
    boolean addAll(int index, Collection<? extends E> c);
 
    boolean removeAll(Collection<?> c);
 
    E set(int index, E element);
 
    void add(int index, E element);
 
    ListIterator<E> listIterator();
    ListIterator<E> listIterator(int index);
 
    List<E> subList(int fromIndex, int toIndex);
}

Wild Cards

Listing 15-3 contains question marks that represent unknown types. It’ll be easier to explain them with an example. Let’s turn the for loop from Listing 15-2 into a function. In Eclipse, highlight the code of the for loop, click the right mouse button, and select Refactor Extract Method. In the pop-up window enter the method name processCustomers and click OK.

download.eps

Listing 15-4: Refactored class TestGenericCollection

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
 
public class TestGenericCollection {
 
       public static void main(String[] args) {
 
              ArrayList<Customer> customers = new ArrayList<Customer>();
              Customer cust1 = new Customer("David","Lee");
              customers.add(cust1);
              Customer cust2 = new Customer("Ringo","Starr");
              customers.add(cust2);
              Order ord1= new Order();
              //customers.add(ord1); // Compiler error
 
              // Iterate through the list customers and do something with each 
              //  element of this collection. No casting required. 
 
              processData(customers);
        
       }
 
       private static void processData(ArrayList<Customer> customers) {
              for (Customer c: customers){
                    c.doSomething();
              }
       }
 
}

What if you want to make the method processData() more generic and useful not only for a collection of Customer objects but for others too? In the pre-generics era you’d be using instanceof and writing something similar to Listing 15-5.

download.eps

Listing 15-5: Back to casting

private static void processData(ArrayList data) {
 
               for (Object o: data){
                    ((Customer) o).doSomething();
              }
      }

But now, armed with the new knowledge, you can change the signature of the method process Data to

private static void processData(ArrayList<Object> data){...}

Unfortunately, this solution won’t work, because there is no such thing as inheritance of parameterized types. In other words, even though the class Customer is a subclass of Object, this relationship does not apply to <Customer> and <Object>. This is when the question mark that represents an unknown type becomes handy. The next step in making processData() more generic is to change the method signature to the following:

private static void processData(ArrayList<?> data){...}

Using such a method signature is different from simply declaring the method argument data of type ArrayList, which would require casting, as in Listing 15-5. With the wild-card notation you state, “At this point the type of data is not known, but whenever some code uses the method processData() it’ll be known and properly compiled so casting won’t be needed.”

The next challenge you face is to compile the code calling the method doSomething() on the objects of unknown types.

Bounded Wild Cards

Bounded wild cards enable you to specify generic types that are not completely unknown, but rather “somewhat unknown.” You can say, for example, that the method processData() from the previous section can work only with the objects that extend a specific class. Consider an example in Listing 15-6, which declares two classes — Customer and Order — that extend Data.

download.eps

Listing 15-6: Customer and Order extending Data

public abstract class Data {
       abstract void doSomething();
}
 
public class Customer extends Data{
       private String fName;
      private String lName;
 
    public Customer (String fName, String lName){
       this.fName=fName;
       this.lName=lName;
    }
    
       void doSomething(){
            System.out.println("Customer " + fName + " " +lName + 
". In doSomething()");
       }
}
 
public class Order extends Data{
      void doSomething(){
             // Do something specific to Order processing
       }
}

You can rewrite the method processData() using the bounded wild card <? extends Data>, as in the following example:

private static void processData(ArrayList<? extends Data> data) {
       for (Data d: data){
          d.doSomething();
       }
}

If I didn’t show you the code in Listing 15-6 and asked, “What does the bounded type <? extends Data > mean?”, the right answer would have been, “It represents any subclass of the class Data or any class that implements the interface Data.” The word extends may sound a little misleading in the latter case, but the designers of Java generics decided to keep this keyword for both classes and interfaces.

If you want to use multiple bounds, they should be separated by ampersands:

<? extends Data & Payable >

You can achieve yet another interesting effect by using the keyword super in the generics. For example, the next declaration means that you are allowed to use any superclass of Data:

<? super Data & Payable >

The super keyword is quite handy when you need to ensure that arguments of a method belong to the same inheritance hierarchy. For example, the method doSomething() can be used for processing such lists as Person and RetiredEmployee (assuming that RetiredEmployee is a subclass of Employee).

<T>  void doSomething(ArrayList<? super T> all, ArrayList<? extends T> best){
   //do something
} 

Generic Methods

While declaring a method you can either predefine data types for its arguments and return values or use generics. For example, the method toArray() from Listing 15-3 starts with a declaration of a new parameterized type (<T> in that case), which has to be placed in angle brackets right before the return type in the method signature. The very fact that a method declares a new type makes it generic. the following declaration of the toArray()method takes an array of objects of type T and returns an array of T objects:

<T> T[] toArray(T[] a);

If you have an ArrayList of integers, you can declare and convert it to an array as follows:

ArrayList<Integer> myNumericList = new ArrayList<Integer>();
...
Integer myNumericArray = new Integer[myNumericList.size()];
myNumericArray = myNumericList.toArray();

If you need to use the same method toArray() with a list of customers, the data type <T> magically transforms (by compiler) into the Customer type:

ArrayList<Customer> myCustomerList = new ArrayList< Customer >();
...
Customer myCustomerArray = new Customer [myCustomerList.size()];
myCustomerArray = myCustomerList.toArray();

As in examples from the “Bounded Wild Cards” section, you are allowed to put constraints on the type. For example, you can restrict the toArray() method to work only with types that implement the Comparable interface:

<T extends Comparable> T[] toArray(T[] a);

What to Read Next

This book is a concise tutorial that doesn’t have room to cover Java generics in greater detail. If you’d like to get a deeper understanding of this topic, refer to the book Java Generics and Collections, 2006, written by Maurice Naftalin and Philip Wadler and published by O’Reilly, ISBN 0-596-52775-6.

Bruce Eckel devoted almost a hundred pages to generics in the fourth edition of his book Thinking in Java, 2006, published by Prentice Hall, ISBN 0-131-87248-6.

Try It

Create a simple program that uses generics with the class RetiredEmployee (which extends the class Employee) from Listing 7-2. Write a generic method that will accept a collection of RetiredEmployee objects and copy it into a collection of Employee objects.

Lesson Requirements

You should have Java installed.

note.ai

You can download the code and resources for this Try It from the book’s web page at www.wrox.com. You can find them in the Lesson15 folder in the download.

Step-by-Step

1. Create a new Eclipse project called Lesson 15.

2. Create a class called RetiredEmployee that extends Employee.

3. Create an executable Java class, TestGenericMethod, that will accept a List of RetiredEmployee objects and copy it into a List of Employee objects. This method should print on the system console the name of each Employee from the resulting collection.

4. Run the TestGenericMethod program and observe the printed names.

cd.ai

Please select Lesson 15 on the DVD with the print book, or watch online at www.wrox.com/go/fainjava to view the video that accompanies this lesson.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.229.143