Generics in Java

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.

Related Article: Visibility (controlling access to members of a class)

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 {
     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 integerParam = new Param();

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

Param stringArrayParam;
Param int2dArrayParam;
Param> 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 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 {
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 {
// …
}

public class Age extends AbstractParam {
// …
}

public class Height extends AbstractParam {
// …
}

public class ObjectParam extends AbstractParam {
// …
}

public class MultiParam extends AbstractParam {
// …
}

The following is the usage:

Email email = new Email();
email.setValue("[email protected]");
String retrievedEmail = email.getValue();

Age age = new Age();
age.setValue(25);
Integer retrievedAge = age.getValue();
int autounboxedAge = age.getValue();

Height heightInInt = new Height<>();
heightInInt.setValue(125);

Height heightInFloat = new Height<>();
heightInFloat.setValue(120.3f);

MultiParam 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 the anonymous inner class with empty curly braces ({}):

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

Note that using the diamond with anonymous inner classes is not allowed.

Multiple type parameters in Generics

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 MultiGenericParam {
     private T firstParam;
     private S secondParam;

     public MultiGenericParam(T firstParam, S secondParam) {
          this.firstParam = firstParam;
          this.secondParam = secondParam;
     }
     public T getFirstParam() {
          return firstParam;
     }

     public void setFirstParam(T firstParam) {
          this.firstParam = firstParam;
     }

      public S getSecondParam() {
          return secondParam;
      }

      public void setSecondParam(S secondParam) {
           this.secondParam = secondParam;
      }
}

The usage can be done as below:

MultiGenericParam aParam = new MultiGenericParam("value1",
"value2");
MultiGenericParam dayOfWeekDegrees = new MultiGenericParam(1,
2.6);

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 Shoe {}
class IPhone {}
interface Fruit {}
class Apple implements Fruit {}
class Banana implements Fruit {}
class GrannySmith extends Apple {}

      public class FruitHelper {

           public void eatAll(Collection fruits) {}
           public void addApple(Collection apples) {}
}

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

public class GenericsTest {
     public static void main(String[] args){
     FruitHelper fruitHelper = new FruitHelper() ;
     List fruits = new ArrayList();
     fruits.add(new Apple());      // Allowed, as Apple is a Fruit
     fruits.add(new Banana());     // Allowed, as Banana is a Fruit
     fruitHelper.addApple(fruits); // Allowed, as "Fruit super Apple"
     fruitHelper.eatAll(fruits);   // Allowed

     Collection bananas = new ArrayList<>();
     bananas.add(new Banana());       // Allowed
     //fruitHelper.addApple(bananas); // Compile error: may only     contain Bananas!
     fruitHelper.eatAll(bananas);     // Allowed, as all Bananas are Fruits

     Collection apples = new ArrayList<>();
     fruitHelper.addApple(apples);  // Allowed
     apples.add(new GrannySmith()); // Allowed, as this is an Apple
     fruitHelper.eatAll(apples);    // Allowed, as all Apples are Fruits.

Collection grannySmithApples = new ArrayList<>();
fruitHelper.addApple(grannySmithApples); //Compile error: Not allowed.
// GrannySmith is not a supertype of Apple

apples.add(new GrannySmith());           //Still allowed, GrannySmith is an Apple
fruitHelper.eatAll(grannySmithApples);   //Still allowed, GrannySmith is a Fruit

Collection<Object> objects = new ArrayList<>();
fruitHelper.addApple(objects); // Allowed, as Object super Apple
objects.add(new Shoe());       // Not a fruit
objects.add(new IPhone());     // Not a fruit
//fruitHelper.eatAll(objects); // Compile error: may contain a Shoe, 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".

(Producer has only write access, and Consumer has only read access)

The Diamond

Version ≥ Java SE 7

Java 7 introduced the Diamond1 to remove some boiler-plate around generic class instantiation. With Java 7+ you can write:

List list = new LinkedList<>();

Where you had to write in previous versions, this:

List list = new LinkedList();

One limitation is for Anonymous Classes, where you still must provide the type parameter in the instantiation:

// This will compile:
Comparator caseInsensitiveComparator = new Comparator() {
       @Override
       public int compare(String s1, String s2) {
             return s1.compareToIgnoreCase(s2);
       }
};

// But this will not:

Comparator caseInsensitiveComparator = new Comparator<>() {
      @Override
      public int compare(String s1, String s2) {
            return s1.compareToIgnoreCase(s2);
      }
};

Version > Java SE 8

Although using the diamond with Anonymous Inner Classes is not supported in Java 7 and 8, it will be included as a new feature in Java 9.

Footnote:

1 – Some people call the <> usage the “diamond operator”. This is incorrect. The diamond does not behave as an operator and is not described or listed anywhere in the JLS or the (official) Java Tutorials as an operator. Indeed, <> is not even a distinct Java token. Rather it is a < token followed by a > token, and it is legal (though bad style) to have whitespace or comments between the two. The JLS and the Tutorials consistently refer to <> as “the diamond”, and that is therefore the correct term for it.

LEAVE A REPLY

Please enter your comment!
Please enter your name here