Generics

<> is used to define generic classes, interfaces, or methods by specifying a type parameter..

Creating references to them

Box<String> myStringBox = new Box<>("Apple");

By convention common type parameters include E (Elements), K (Key), N (Number), T (Type), V (Value), and S (or U or V) for multiple type parameters.

we cannot use primitive types (int, double, boolean, etc) as arguments to generic type parameters.

for objects of primitive types we can use wrapper classes

...pic of wrapper classes

Box<Integer> intBox = new Box<>(56);

Autoboxing allows wrapper classes to take primitive values and convert them to their corresponding wrapper object by automatically calling the valueOf() method.

Integer a = 56;  // Autoboxing, automatic call to `valueOf()`
Box<Integer> intBox1 = new Box<>(a);
Box<Integer> intBox2 = new Box<>(56);  // Autoboxing, automatic call to `valueOf()`
Box<Integer> intBox3 = new Box<>(Integer.valueOf(56));

We can also take the wrapper object and convert it back to its primitive value using one of the wrapper object’s Value() methods. This process is also automated for us and is known as unboxing

Integer a = 56;
int aPrimitive1 = a.intValue();  // Return primitive `int` value from `Integer` object
int aPrimitive2 = a;  // Unboxing, automatic call to `intValue()`

Interfaces

A generic interface can be implemented by a generic class and its generic type parameter can be used as the argument to the interface type parameter.

public interface Replacer<T> {
  void replace(T data);
}

public class Box <T> implements Replacer<T> {
  private T data;
 
  @ Override
  void replace(T data) {
    this.data = data; 
  }
}

We can also have a non-generic class implement a generic interface by specifying the type argument to the interface.

public class StringBag implements Replacer<String> {
  private String data;
 
  @ Override
  void replace(String data) {
    this.data = data; 
  }
}

Now let’s create interface type references similarly to how we created generic class references

Replacer<Integer> boxReplacer = new Box<>();  // Using generic `Box` implementation
Replacer<String> bagReplacer = new StringBag();  // Using non-generic `StringBag` implementation

Generic Methods

It’s important to note that generic methods need to include the type parameter,

public class StringBox {
  private String data;
 
  public <T> boolean isString(T item) {
    return item instanceof String; 
  }
}

StringBox box = new StringBox();
box.isString(5); // Returns false

Raw Types

Raw type means not providing a type argument to a generic class or interface

must be avoided

  • Incompatible type errors when retrieving data from raw types
  • potential runtime ClassCastException
Box box = new Box<>("My String");  // Raw type box
String s2 = (String) box.getData();  // No incompatible type error
String s1 = box.getData();  // Incompatible type error

Upper Bounds

It limits the type parameter to a parent type or any of its child types.

Classes and Interfaces

//we can use with Integer, Double.. but not String - error
public class Box <T extends Number> {
  private T data; 
}

Upper Bounds to Generic Methods

public static <T extends Number> boolean isZero(T data) {
  return data.equals(0);
}

Multiple Bounds

any upper bound that is a class, must come first followed by any interfaces

public class Box <T extends Number & Comparable<T>> {
  private T data; 
}

Wildcard

? Wildcards are used to define unknown types.

public class Util {
  public static void printBag(Bag<?> bag ) {
    System.out.println(bag.toString()); 
  }
}
Bag<String> myBag1 = new Bag("Hello");
Bag<Integer> myBag2 = new Bag(23);
Util.printBag(myBag1);  // Hello
Util.printBag(myBag2);  // 23

the wildcard version is simpler.

Wildcard Lower Bounds

A lower bound wildcard restricts the wildcard to a class or interface and any of its parent types.

public class Util {
 public static void getBag(Bag<? super Integer> bag ) {
   return bag;
 }
}

Some important things:

  • They cannot be used with generic type parameters, only wildcards.
  • A wildcard cannot have both a lower bound and upper bound.
Last Updated: