Chapter 15

Generics

With generics you can write a parameterized type and create instances of the type by passing a reference type or reference types. The objects will then be restricted to the type(s). For example, the java.util.List interface is generic. If you create a List by passing java.lang.String, you’ll get a List that will only accept Strings; In addition to parameterized types, generics support 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.

This chapter teaches you how to use and write generic types. It starts with the section “Life without Generics” to remind us what we missed in earlier versions of JDK’s. Then, it presents some examples of generic types. After a discussion of the syntax, this chapter concludes with a section that explains how to write generic types.

Life without Generics

All Java classes derive from java.lang.Object, which means all Java objects can be cast to Object. Because of this, in pre-5 JDK’s, many methods in the Collections Framework accept an Object argument. This way, collections become general-purpose utility types that can hold objects of any type. This imposes unpleasant consequences.

For example, the add method in List in pre-5 JDK’s takes an Object argument:

public boolean add(java.lang.Object element)

As a result, you can 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 objects 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 an element in a List instance. Here is its signature prior to Java 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 and later");
stringList1.add("with generics");

When retrieving a member from stringList1, you get an instance of java.lang.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); 

With generic types, you can forget about type casting when retrieving objects from a List. And, there is more. Using the generic List interface, you can create specialized Lists, such as one that only takes Strings.

Generic Types

A generic type can accept parameters. This is why a generic type is often called a parameterized type. 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, you would write

List<E> myList;

E is called a type variable, namely 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 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.

public boolean add<E o>
public 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 that implements E.

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

List<String> myList;

the add method of the List instance referenced by myList will expect a String as its argument and its get method will return a String. Because get returns a specific type of object, 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 declaring it. For instance, to create an ArrayList that works with String, you pass String in angle brackets.

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

The diamond language change in Java 7 allows explicit type arguments to constructors of parameterized classes, most notably collections, to be omitted in many situations. Therefore, the statement above can be written more concisely in Java 7 or later.

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

In this case, the compiler will infer the arguments to the ArrayList.

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

public interface Map<K, V>

K is used to denote the type of the map’s keys and V the type of the map’s 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 runtime, and therefore it is not possible to check what type of exception that might be thrown at compile time.

As an example, Listing 15.1 compares List with and without generics.

Listing 15.1: Working with generic List

package app15;
import java.util.List;
import java.util.ArrayList;

public class GenericListDemo1 {
    public static void main(String[] args) {
        // without generics
        List stringList1 = new ArrayList();
        stringList1.add("Java");
        stringList1.add("without generics");
        // cast to java.lang.String
        String s1 = (String) stringList1.get(0);
        System.out.println(s1.toUpperCase());
    
        // with generics and diamond
        List<String> stringList2 = new ArrayList<>();
        stringList2.add("Java");
        stringList2.add("with generics");
        // no type casting is necessary
        String s2 = stringList2.get(0);
        System.out.println(s2.toUpperCase());
    }
}

In Listing 15.1, stringList2 is a generic List. The declaration List<String> tells the compiler that this instance of List can only store Strings. When retrieving member elements of the List, 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 write:

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

Listing 15.2 presents a class that uses a List that accepts a List of Strings.

Listing 15.2: Working with List of Lists

package app15;
import java.util.ArrayList;
import java.util.List;
public class ListOfListsDemo1 {
    public static void main(String[] args) {
        List<String> listOfStrings = new ArrayList<>();
        listOfStrings.add("Hello again");
        List<List<String>> listOfLists = 
                new ArrayList<>();
        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 variables. 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. Listing 15.3 presents an example that uses a generic Map.

Listing 15.3: Using the generic Map

package app15;
import java.util.HashMap;
import java.util.Map;
public class MapDemo1 {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        String value1 = map.get("key1");
    }
}

In Listing 15.3, to retrieve a value indicated by key1, you do not need to perform type casting.

Using Generic Types without Type Parameters

Now that the collection types in Java have been made generic, what about legacy codes? Fortunately, they will still work in Java 5 or later because you can use generic types without type parameters. For example, you can still use List the old way, as demonstrated in Listing 15.1.

List stringList1 = new ArrayList();
stringList1.add("Java");
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 to work in Java 5 or later.

One thing to note, though, starting from Java 5 the Java 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 the code in Listing 15.1 gave you the following warning because the first List was used as a raw type.

Note: app15/GenericListDemo1.java uses unchecked or unsafe operations.
Note: Recompile with –Xlint:unchecked for details.

You have these options at your disposal to get rid of the warnings when working with raw types:

  • compile with the –source 1.4 flag.
  • use the @SuppressWarnings("unchecked") annotation (See Chapter 17, “Annotations”)
  • 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 them. It is possible that future versions of Java will not allow raw types.

Using the ? Wildcard

I mentioned that if you declare a List<aType>, the List 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.

However, note 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 to different types of objects.

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

list1 references a List of java.lang.Object instances and list2 references a List of String objects. 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> raises a compile time error. Listing 15.4 shows this.

Listing 15.4: The AllowedTypeDemo1 class

package app15;
import java.util.ArrayList;
import java.util.List;

public class AllowedTypeDemo1 {
    public static void doIt(List<Object> l) {
    }
    public static void main(String[] args) {
        List<String> myList = new ArrayList<>();
        // this will generate a compile error
        doIt(myList);
    }
}

Listing 15.4 will not compile because you are passing the wrong type to the doIt method. doIt expects an instance of List<Object> and you are passing 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. Listing 15.5 shows the printList method that uses the ? wildcard.

Listing 15.5: Using the ? wildcard

package app15;
import java.util.ArrayList;
import java.util.List;

public class WildCardDemo1 {
    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<>();
        list1.add("Hello");
        list1.add("World");
        printList(list1);

        List<Integer> list2 = new ArrayList<>();
        list2.add(100);
        list2.add(200);
        printList(list2);
    }
}

The code in Listing 15.4 demonstrates that List<?> in the printList method means a List of any type.

Note, however, it is illegal to use the wildcard when declaring or creating a generic type, such as this.

List<?> myList = new ArrayList<?>(); // this is 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<>();

Using Bounded Wildcards in Methods

In the section “Using the ? Wildcard” above, you learned that passing different type variables to a generic type creates different Java types. In many cases, you might want a method that accepts 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 the method to be able to work with 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 is depriving you of type safety checking at compile time because you could 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 work with List<Integer> or List<Long> probably more often than with List<Number>.

There is another rule to circumvent this restriction, i.e. 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>

Listing 15.6 illustrates the use of such a bound.

Listing 15.6: Using a bounded wildcard

package app15;
import java.util.ArrayList;
import java.util.List;
public class BoundedWildcardDemo1 {
    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<>();
        integerList.add(3);
        integerList.add(30);
        integerList.add(300);
        System.out.println(getAverage(integerList)); // 111.0
        List<Double> doubleList = new ArrayList<>();
        doubleList.add(3.0);
        doubleList.add(33.0);
        System.out.println(getAverage(doubleList)); // 18.0
    }
}

Thanks to the upper bound, the getAverage method in Listing 15.6 will allow 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. 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.

Generic Methods

A generic method is a method that declares their own type parameters. The type parameters of a generic method are declared in angle brackets and appear before the method’s return value. The scope of a generic method’s type parameters is limited to the method. Static and non-static generic methods are allowed, as well as generic constructors.

Generic methods can be declared within a generic type or a non-generic type.

For example, the emptyList method of the java.util.Collections class is a generic method. Look at the method signature:

public static final <T> List<T> emptyList()

emptyList has one type parameter, T, that appears after the keyword final and before the return value (List<T>).

Unlike a generic type where you have to explicitly specify the parameter types when instantiating the type, the parameter type(s) for a generic method are inferred from the method invocation and corresponding declaration. That is why you can simply write the following without specifying a parameter type for the generic method.

List<String> emptyList1 = Collections.emptyList();
List<Integer> emptyList2 = Collection.emptyList();

In both statements, the Java compiler infers the parameter type for emptyList from the reference variables that receive the return values.

NoteType inference is a language feature that enables the compiler to determine the type parameter(s) for a generic method from the corresponding declaration.

If you so wish, you can explicitly specify the type parameters of a generic method, in which case you pass the type parameters within angle brackets before the method name.

List<String> emptyList1 = Collections.<String>emptyList();
List<Integer> emptyList2 = Collection.<Integer>emptyList();

A type parameter of a generic method can have an upper or lower bound as well as use a wildcard. For example, the binarySearch method of Collections specifies both an upper bound and a lower bound:

public static <T> int binarySearch(List<? extends T> list, T key, 
        Comparator<? super T> c) 

Writing Generic Types

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 Listing 15.7 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 would suffice.

Listing 15.7: The generic Point class

package app15;
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 Listing 15.7, 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<>(4, 2);
point1.setX(7);
Point<Double> point2 = new Point<>(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 hold strings and will not accept Integer 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 codes with JRE 5 or later. For new applications, you should not use raw types as future releases of Java may not support them.

In this chapter you have also learned that passing different type variables to a generic type results in different Java types. This is to say that List<String> is a different type from 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.

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

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