Creating a Generic Class


Generics

Generics are a facility of generic programming that extend Java's type system to allow a type or method to operate on objects of various types while providing compile-time type safety. In particular, the Java collections framework supports generics to specify the type of objects stored in a collection instance.


Creating a Generic Class

Generics enable classes, interfaces, and methods to take other classes and interfaces as type parameters.

This example uses generic class Param to take a single type parameter T, delimited by angle brackets (<>):

public class Param<T> {
	private T value;
 
	public T getValue() {
		return value;
	}
 
	public void setValue(T value) {
		this.value = value;
	}
}

To instantiate this class, provide a type argument in place of T. For example, Integer:

Param<Integer> integerParam = new Param<Integer>();

The type argument can be any reference type, including arrays and other generic types:

Param<String[]> stringArrayParam;
Param<int[][]> int2dArrayParam;
Param<Param<Object>> objectNestedParam;

In Java SE 7 and later, the type argument can be replaced with an empty set of type arguments (<>) called the diamond:

Version ≥ Java SE 7
 
Param<Integer> integerParam = new Param<>();

Unlike other identifiers, type parameters have no naming constraints. However their names are commonly the first letter of their purpose in upper case. (This is true even throughout the official JavaDocs.) Examples include T for "type", E for "element" and K/V for "key"/"value".

Extending a generic class

public abstract class AbstractParam<T> {
	private T value;
 
	public T getValue() {
		return value;
	}
 
	public void setValue(T value) {
		this.value = value;
	}
}

AbstractParam is an abstract class declared with a type parameter of T. When extending this class, that type parameter can be replaced by a type argument written inside <>, or the type parameter can remain unchanged. In the first and second examples below, String and Integer replace the type parameter. In the third example, the type parameter remains unchanged. The fourth example doesn't use generics at all, so it's similar to if the class had an Object parameter. The compiler will warn about AbstractParam being a raw type, but it will compile the ObjectParam class. The fifth example has 2 type parameters (see "multiple type parameters" below), choosing the second parameter as the type parameter passed to the superclass.

public class Email extends AbstractParam<String> {
 // ...
}
 
public class Age extends AbstractParam<Integer> {
 // ...
}
 
public class Height<T> extends AbstractParam<T> {
 // ...
}
 
public class ObjectParam extends AbstractParam {
 // ...
}
 
public class MultiParam<T, E> extends AbstractParam<E> {
 // ...
}

The following is the usage:

Email email = new Email();
email.setValue("test@example.com");
String retrievedEmail = email.getValue();
 
Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();
 
Height<Integer> heightInInt = new Height<>();
heightInInt.setValue(125);
 
Height<Float> heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);
 
MultiParam<String, Double> multiParam = new MultiParam<>();
multiParam.setValue(3.3);

Notice that in the Email class, the T getValue() method acts as if it had a signature of String getValue(), and the void setValue(T) method acts as if it was declared void setValue(String).

It is also possible to instantiate with anonymous inner class with an empty curly braces ({}):

AbstractParam<Double> height = new AbstractParam<Double>(){};
height.setValue(198.6);

Multiple type parameters

Java provides the ability to use more than one type parameter in a generic class or interface. Multiple type parameters can be used in a class or interface by placing a comma-separated list of types between the angle brackets. Example:

public class CustomGenericParam<A, B> {
    private A customFirstParam;
    private B customSecondParam;
 
    public CustomGenericParam(A customFirstParam, B customSecondParam) {
        this.customFirstParam = customFirstParam;
        this.customSecondParam = customSecondParam;
    }
 
    public A getCustomFirstParam() {
        return customFirstParam;
    }
 
    public void setCustomFirstParam(A customFirstParam) {
        this.customFirstParam = customFirstParam;
    }
 
    public B getCustomSecondParam() {
        return customSecondParam;
    }
 
    public void setCustomSecondParam(B customSecondParam) {
        this.customSecondParam = customSecondParam;
    }
}
 
public class CustomGenericParamExample {
    public static void main(String[] args) {
        CustomGenericParam<String, String> customParam1 = new CustomGenericParam<>("valueA", "valueB");
        CustomGenericParam<Integer, Double> customParam2 = new CustomGenericParam<>(1, 2.6);
 
        System.out.println("CustomParam1: " + customParam1.getCustomFirstParam() + ", " + customParam1.getCustomSecondParam());
        System.out.println("CustomParam2: " + customParam2.getCustomFirstParam() + ", " + customParam2.getCustomSecondParam());
    }
}

Deciding between 'T', '? super T', and '? extends T'

The syntax for Java generics bounded wildcards, representing the unknown type by ? is:

  • ? extends T represents an upper bounded wildcard. The unknown type represents a type that must be a subtype of T, or type T itself.
  • ? super T represents a lower bounded wildcard. The unknown type represents a type that must be a supertype of T, or type T itself.

As a rule of thumb, you should use

  • ? extends T if you only need "read" access ("input")
  • ? super T if you need "write" access ("output")
  • T if you need both ("modify")

Using extends or super is usually better because it makes your code more flexible (as in: allowing the use of subtypes and supertypes), as you will see below.

class Sneaker {}
class SamsungGalaxy {}
interface Edible {}
class Orange implements Edible {}
class Grape implements Edible {}
class GoldenDelicious extends Orange {}
 
public class FoodUtil {
    public void consumeAll(Collection<? extends Edible> edibles) {}
    public void addOrange(Collection<? super Orange> oranges) {}
}

The compiler will now be able to detect certain bad usage:

public class GenericsExample {
    public static void main(String[] args){
        FoodUtil foodUtil = new FoodUtil();
        List<Edible> edibles = new ArrayList<>();
        edibles.add(new Orange()); // Allowed, as Orange is Edible
        edibles.add(new Grape()); // Allowed, as Grape is Edible
        foodUtil.addOrange(edibles); // Allowed, as "Edible super Orange"
        foodUtil.consumeAll(edibles); // Allowed
 
        Collection<Grape> grapes = new ArrayList<>();
        grapes.add(new Grape()); // Allowed
        //foodUtil.addOrange(grapes); // Compile error: may only contain Oranges!
        foodUtil.consumeAll(grapes); // Allowed, as all Grapes are Edibles
 
        Collection<Orange> oranges = new ArrayList<>();
        foodUtil.addOrange(oranges); // Allowed
        oranges.add(new GoldenDelicious()); // Allowed, as this is an Orange
        foodUtil.consumeAll(oranges); // Allowed, as all Oranges are Edibles.
 
        Collection<GoldenDelicious> goldenDeliciousOranges = new ArrayList<>();
        //foodUtil.addOrange(goldenDeliciousOranges); // Compile error: Not allowed.
        // GoldenDelicious is not a supertype of Orange
        oranges.add(new GoldenDelicious()); // Still allowed, GoldenDelicious is an Orange
        foodUtil.consumeAll(goldenDeliciousOranges); // Still allowed, GoldenDelicious is an Edible
 
        Collection<Object> objects = new ArrayList<>();
        foodUtil.addOrange(objects); // Allowed, as Object super Orange
        objects.add(new Sneaker()); // Not an edible
        objects.add(new SamsungGalaxy()); // Not an edible
        //foodUtil.consumeAll(objects); // Compile error: may contain a Sneaker, too!
    }
}

Choosing the right T, ? super T or ? extends T is necessary to allow the use with subtypes. The compiler can then ensure type safety; you should not need to cast (which is not type safe, and may cause programming errors) if you use them properly.

If it is not easy to understand, please remember PECS rule:

  • Producer uses "Extends" and Consumer uses "Super".

Basic Programs