Generics is parameterised types denoted with a diamond operator <>, it allows the creation of generic classes, interfaces, and methods that can work with different types. It promotes type safety, explicit type casting and code reusability since the same generic code can be used for multiple types without duplicating the implementation.

Type parameter naming convention

  • E: Element (used extensively by the Java Collections Framework)
  • T: Type
  • K: Key (i.e. in a Map<K,V> for key-value pair)
  • N: Number
  • V: Value
  • S,U,V etc: 2nd, 3rd, 4th types

Generic method, class and interface

Generic method

Generic methods are methods that introduce their own type parameters. It follows the format:

public static <T, S> T functionName(T anyThing, S anotherThing) {
	return anyThing;
}
  • <T, S>: This declares two type parameters T and S that can be used in the method.
  • T: This specifies that the return type of the method is T.
  • T anyThing: This is the first parameter of the method of type T.
  • S anotherThing: This is the second parameter of the method of type S.
public class Demo {
	public static <T> void thingToPrint(T thing) {
		System.out.println(thing);
	}
	
	public static void main(String[] args) {
		thingToPrint(100);
		thingToPrint("Hello");
		thingToPrint(new Point3D(1, 3, 2));
	}
}

Now the method thingToPrint() takes a generic type T, you can technically name the generic type anything you want other than T, but use the naming convention provides better readability. The type parameter T can be any thing that is non-primitive (but meanwhile, Java provides autoboxing mechanism that automatically convert primitive types to their correspond wrapper type).

  • In the first method call, thingToPrint() takes a primitive int, the int will be auto converted to Integer type. Now the T is specified to Integer as the parameter thing is an Integer.
  • In the second method call, thingToPrint() takes a String object. The T is specified to String.
  • In the third method call, thingToPrint() takes a Point3D object. The T is specified to Point3D.

Generic class

This is a sample of a generic class.

public class MyClass<T> {
    T fieldName;
    
    public MyClass(T name) {
        this.fieldName = name;
    }
}
  • The class declaration with <T> indicates that the class can work with a generic type T.
  • T fieldName declares a field of the generic type T.

Now, we create two instances of the class MyClass with different constructor parameters.

public class GenericClassExample {
    public static void main(String[] args) {
        MyClass<String> myStringClass = new MyClass<>("Hello");
        System.out.println("String field: " + myStringClass.fieldName);
        
        MyClass<Integer> myIntegerClass = new MyClass<>(123);
        System.out.println("Integer field: " + myIntegerClass.fieldName);
    }
}
String field: Hello
Integer field: 123

Generic interface

The generic interface also supports multiple types.

Interface InterfaceName <T> { }

Wildcards

In Java generics, the question mark ? used is called the wildcard. It is used when you don’t know or care about what the generic type is. Different from other generic types such as <T> that are used when you want to define classes, interfaces, or methods that operate on a specific type parameter, ensuring type safety and code reusability; wildcards ? are used for flexibility when you want to handle collections of unknown types or types that can vary (extends for subtypes or super for super types).

Note

Wildcards can be used for reading but not for adding elements to a collection because of the type safety concerns, this will be discussed in the subsequent the bounded generics.

The below code printList() method takes a list of unknown type. The wildcard ? means the method can accept a list of any type.

import java.util.List;
 
public class Main {
	// takes a list of known type
    public static void printList(List<?> list) {
        for (Object obj : list) {
            System.out.println(obj);
        }
    }
    
    public static void main(String[] args) {
        List<String> strings = List.of("one", "two", "three");
        List<Integer> integers = List.of(1, 2, 3);
        
        printList(strings);  
        printList(integers); 
    }
}
one two three
1 2 3

Bounded type generics

Bounded type generics allows you to restrict the type that can be used as type argument in a generic class, method or interface. It enables you to specify that a type parameter must be a subtype of a particular class or implement specific interfaces. If an unrelated class is used as argument, a compilation error will be thrown.

Upper bounded generics

An upper bounded type parameter restricts the type argument to be a specific class or its subtypes. It is denoted by the extends keyword.

public class MyClass<T extends SomeClass> { }

In this sample, the type parameter T must be SomeClass or a subclass of SomeClass.

The upper bounded generics also accepts multiple bounds for a type parameter. In the below sample, the T must be a subclass of SomeClass and also implement SomeInterface. You can restrict it with multiple interfaces with multiple & signs.

public class MyClass<T extends SomeClass & SomeInterface> { }

You can also use upper bounded wildcard <? extends T> to accept type T or any subclass of T. In the below example, you can use an upper bounded wildcard to write a method that can take a collection of Animal or any subclass of Animal.

class Animal { }
 
class Dog extends Animal { }
 
class Cat extends Animal { }
import java.util.List;
 
public class Main {
    public static void addAnimal(List<? extends Animal> animals) {
        // Can read items as type Animal
        for (Animal animal : animals) {
            System.out.println(animal);
        }
        
        // Cannot add new items to the list
        // animals.add(new Dog()); // Compile-time error
    }
}

In the forgoing description, wildcard is not for adding elements to a collection. According to this example, if you try to add an element to the list, the compiler can’t guarantee what specific subtype of Animal is in the list. For example, if you have a List<Dog>, you can’t add a Cat to it.

Lower bounded generics

The lower bounded generics is used to specify the argument is the super type of a specific class. The lower bounded generics follows this format:

<T super SomeClass>

In the below example, the list can be of any type that is a super type of Integer (e.g., Number, Object), and you can safely add Integer elements to the list.

public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
    list.add(3);
}

Back to parent page: Java Standard Edition (Java SE) and Java Programming

Web_and_App_DevelopmentProgramming_LanguagesJavaGenerics

Reference: