woman in beige coat standing near green wall

Generics in Java

Generic Types

A generic type is a generic class or interface which is parameterized over types. When defining a generic-type class or interface, what we have to do is describing type parameters, \(T_1, T_2,…,T_n\), which follow the class or interface name and are enclosed in angle brackets.

// Define a generic class
public class Hoge<T> {

Next, to invoke a generic-type class, we specify the actual type by replacing type parameters with type arguments.

// Invoke a generic class with a specific type argument
Hoge<String> hoge;

Additionally, we can also substitute a parameter type with a parameterized type.
A parameterized type is equivalent to an invocation of a generic type, whose type parameters are specified by type arguments

// Invoke a generic class with a parameterized type
Hoge<List<String>> hoge;

And then, to instantiate this class, we call its constructor with angle brackets.
Note that as long as the compiler can determine, we don’t have to describe type arguments inside the brackets to instantiate.

// Instantiate a generic class
Hoge<String> hoge = new Hoge<>();

Generic Methods

A generic method is a method which introduces type parameters in its declaration. To declare a generic method, what we have to adhere to is describing type parameters, which appear before the method’s return type, and are enclosed in angle brackets.

When invoking the method, we have to specify type arguments with angle brackets just before the method’s name. However, similar to instantiating a generic type class, if the compiler can determine the type arguments, we don’t need to provide either type arguments or brackets.

// Generic method
public <T> void doSth(List<T> list){...}

Strengths of Generics

Generic classes, interfaces and methods enable us to define generic algorithms which can be applied to various types, rather than begin specific to concrete types.

Additionally, the compiler can perform strong type checking to parameterized-type instances. Otherwise, we wouldn’t be able to detect until runtime exceptions occurs.

Inheritance and Subtypes

Let’s consider a scenario where a method accepts a parameterized-type variable, MyClass<Number> arg, as its argument. A common misunderstanding is that we can assign a variable whose type is MyClass<Integer> or MyClass<Double>, based on the parent-child relationships between parameter arguments.

Contrary to this intuition, there’s no relationship between MyClass<Parent> and MyClass<Child> regardless of whether or not Parent and Child have some relationship. The correct statement is as follows: ArrayList<Child> is a subtype of List<Child>, NOT a subtype of ArrayList<Parent>.

Bounded Type Parameters

When restricting parameter arguments to classes which extend or implement specific parents, you can use bounded type parameters.

To declare bounded type parameters, in the type parameter section enclosed in angle brackets, which appears in the declaration of generic classes or methods, we need to have a type parameter followed by extends keyword and its upper bound.

// Generic class with a bounded type parameter
public class Hoge<T extends Fuga> {
// Generic method with a bounded type parameter
public <T extends Foo> void doSth(List<T> list){...}

We can also restrict multiple bounds, by having extends keyword followed by 0-1 class and multiple interfaces, which are delimited with &. If they include a class, it must be described first as follows: <T extends Class & IF1 & IF2>.


The restriction over generic methods just mentioned earlier can be achieved in another way; upper-bounded wildcards.

What you need to do is removing a type parameter section, and replacing a type parameter with the wildcard character (?), which is followed by extends keyword, and a class or interface, which imposes an upper limit on an input variable.

// Upper-bounded wildcard
public void doSth(List<? extends Foo> list){...}

On the other hand, lower-bounded wildcards impose a lower bound on a variable with super keyword.

// Lower-bounded wildcard
public void doSth(List<? super Bar> list){...}