Appearance
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).