Skip to content
On this page

What are generics

Before explaining what generics are, let's first look at ArrayList provided by the Java standard library. It can be regarded as a "variable length" array because it is more convenient to use than an array.

In fact, ArrayList is internally an Object[] array, and when used to store a currently allocated length, it can function as a "variable array":

java
public class ArrayList {
  private Object[] array;
  private int size;
  public void add(Object e) {...}
  public void remove(int index) {...}
  public Object get(int index) {...}
}

If you use the above ArrayList to store String type, there will be several disadvantages:

  • Forced transformation is needed;
  • Inconvenient and error-prone.

For example, the code must be written like this:

java
ArrayList list = new ArrayList();
list.add("Hello");
// After obtaining the Object, it must be forced to be converted to String.:
String first = (String) list.get(0);

ClassCastException is easy to occur because it is easy to "miscast":

java
list.add(new Integer(123));
// ERROR: ClassCastException:
String second = (String) list.get(1);

To solve the above problem, we can write a separate ArrayList for String :

java
public class StringArrayList {
    private String[] array;
    private int size;
    public void add(String e) {...}
    public void remove(int index) {...}
    public String get(int index) {...}
}

In this way, what is stored must be String , and what is taken out must also be String . There is no need for forced conversion, because the compiler will forcefully check the type of input:

java
StringArrayList list = new StringArrayList();
list.add("Hello");
String first = list.get(0);
// Compilation error: Non-String types are not allowed:
list.add(new Integer(123));

The problem is temporarily solved.

However, the new problem is that if you want to store Integer , you need to write a separate ArrayList for Integer :

java
public class IntegerArrayList {
    private Integer[] array;
    private int size;
    public void add(Integer e) {...}
    public void remove(int index) {...}
    public Integer get(int index) {...}
}

In fact, you need to write a separate ArrayList for all other classes:

  • LongArrayList
  • DoubleArrayList
  • PersonArrayList
  • ...

This is impossible, the JDK has thousands of classes, and it doesn't know the classes written by others.

In order to solve the new problem, we must turn ArrayList into a template: ArrayList<T> , the code is as follows:

java
public class ArrayList<T> {
    private T[] array;
    private int size;
    public void add(T e) {...}
    public void remove(int index) {...}
    public T get(int index) {...}
}

T can be any class. In this way, we have achieved: writing a template once can create ArrayList of any type:

java
// Create an ArrayList that can store Strings:
ArrayList<String> strList = new ArrayList<String>();
// Create an ArrayList that can store Float:
ArrayList<Float> floatList = new ArrayList<Float>();
// Create an ArrayList that can store Person:
ArrayList<Person> personList = new ArrayList<Person>();

Therefore, generics are to define a template, such as ArrayList<T> , and then create the corresponding ArrayList<Type> for the used class in the code:

java
ArrayList<String> strList = new ArrayList<String>();

Type checking by the compiler:

java
strList.add("hello"); // OK
String s = strList.get(0); // OK
strList.add(new Integer(123)); // compile error!
Integer n = strList.get(0); // compile error!

In this way, we not only achieve writing once, universal matching, but also ensure type safety through the compiler: this is generics.

Upward transformation

ArrayList<T> in the Java standard library implements the List<T> interface, which can be upcast to List<T> :

java
public class ArrayList<T> implements List<T> {
    ...
}

List<String> list = new ArrayList<String>();

That is, the type ArrayList<T> can be upcast to List<T> .

Special attention should be paid: ArrayList<Integer> cannot be upcast to ArrayList<Number> or List<Number> .

Why is this? Assuming that ArrayList<Integer> can be upcast to ArrayList<Number> , observe the code:

java
// Create ArrayList<Integer> type:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// Add an Integer:
integerList.add(new Integer(123));
// "Upcast" to ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// Add a Float because Float is also a Number:
numberList.add(new Float(12.34));
// Get the element with index 1 (i.e. the added Float) from ArrayList<Integer>:
Integer n = integerList.get(1); // ClassCastException!

After we transform an ArrayList<Integer> into ArrayList<Number> type, the ArrayList<Number> can accept Float type, because Float is a subclass of Number . However, ArrayList<Number> is actually the same object as ArrayList<Integer> , that is, ArrayList<Integer> type. It is impossible to accept Float type, so ClassCastException will occur when getting Integer .

In fact, in order to avoid this error, the compiler does not allow the conversion of ArrayList<Integer> into ArrayList<Number> .

Special attention

ArrayList<Integer> and ArrayList<Number> have no inheritance relationship at all.

A diagram is used to represent the inheritance relationship of generics, that is, when T remains unchanged, it can be transformed upward, T itself cannot be transformed upward:

  List<Integer>     ArrayList<Number>
    ▲                            ▲
    │                            │
    │                            X
    │                            │
ArrayList<Integer>  ArrayList<Integer>

Summary

Generics are about writing template code to adapt to any type;

The advantage of generics is that there is no need to cast the type when using it, it checks the type through the compiler;

Pay attention to the inheritance relationship of generics: ArrayList<Integer> can be upcast to List<Integer> ( T cannot be changed!), but ArrayList<Integer> cannot be upcast to ArrayList<Number> ( T cannot be changed into parent class).

What are generics has loaded