Chapter 12 |
Generics: classes and method
Almost all data structures that we have studied so far contain <T>
(e.g. List<T>
). What does <T>
mean? Is List<String>
and List<Integer>
the same interface or not?
By convention, a single capital letter such as E or T is used to denote a formal generic type. The term generics means parameterized types. Parameterized types are important because they enable us to create classes, interfaces, and methods in which the type of data is specified as a parameter (Java knows what type of data will be used in some operations). A class, interface, or method that operates on a parameterized type is called generic (e.g. generic class or generic method).
It is important to understand that Java always gives a possibility to create generalized classes, interfaces, and methods using type Object
. Nevertheless, Object
is the superclass of all other classes and can be referred to any type object. With generics, all casts are automatic and implicit.
Here is an example of a class which can hold objects of different data types (excluding the primitive data types):
public class Holder<T> { private T holdObject; public T getHoldObject() { return holdObject; } public void setHoldObject(T holdObject) { this.holdObject = holdObject; } } public class Test { public static void main(String[] args) { Holder<Integer> intHoldObject = new Holder<>(); intHoldObject.setHoldObject(99); Holder<String> strHoldObject = new Holder<>(); strHoldObject.setHoldObject("hurraa!"); } }
Here, <T>
represents a formal generic type, which is replaced later in the client class with an actual concrete type (Integer
or String
). The name of the formal generic type can be anything; however, it is a good practice to use capitalised mnemonic names.
In the client class, when an object is created, the parameter type is initialised. Replacing a generic type is called a generic instantiation. Generally, we can think about it as if the variable T
is replaced with a specific type of data.
Note: one class may have several parameter types (separated by comma). For example, in the Map<K, V>
interface, the first parameter sets the parameter type for the key and the second one sets the parameter type for the value. Remember, generic types must be reference types. We cannot replace a generic type with a primitive data type such as int
, double
, or char
. If primitive data types are needed, use the wrapper classes, e.g. Integer
, Double
, Character
, etc.
The key benefit of generics is ability to detect errors at compile time rather than at runtime. A generic class or method permits to specify allowable types of objects that the class or method can work with. If we attempt to use an incompatible object, the compiler will detect that error.
Bounded parameter types
A generic type can be specified as a subtype of another type. Such a generic type is called bounded.
public class NumberHolder<N extends Number> { private N numberHolder; public N getNumberHolder() { return numberHolder; } public void setNumberHolder(N numberHolder) { this.numberHolder = numberHolder; } }
Here, the bounded parameter type N
can only be any subclass of java.lang.Number
. The compiler allows to use NumberHolder<Integer>
, but not NumberHolder<String>
. Setting the bound is efficient because now the N
type variables of the NumberHolder
class can use any method of the class Number
(the compiler guarantees that N
is always Number
). The bounded parameter type can be both the interfaces and the classes (in both cases use the keyword extends
- even if it seems to be weird).
Generic methods
Sometimes it is not efficient to set a generic parameter type to the class. Generic can be a method, too. To declare a generic method, place the generic type <T> immediately after the return type in the method header:
public static <T> List<T> reverse(List<T> list) { List<T> result = new ArrayList<T>(); for (int i = list.size()-1; i >= 0; i--) { result.add(list.get(i)); } return result; }
Chapter 12 |