Daily Struggle: Understanding Java Generics

Keywords: Java encoding

Java Summary Series: Java Generics

Turn from Links from copy

First, the concept of generics (why generics are needed)?

First, let's look at the following short code:

 public class GenericTest {
 2 
 3     public static void main(String[] args) {
 4         List list = new ArrayList();
 5         list.add("qqyumidi");
 6         list.add("corn");
 7         list.add(100);
 8 
 9         for (int i = 0; i < list.size(); i++) {
10             String name = (String) list.get(i); // 1
11             System.out.println("name:" + name);
12         }
13     }
14 }

A set of List types is defined, in which two string type values are added, and then an Integer type value is added. This is completely allowed, because the default type of list is Object type at this point. In subsequent loops, errors like //1 can easily occur because you forget that Integer values or other encoding were added to the list before. Because the compilation phase is normal, there will be "java.lang.ClassCastException" exception at run time. Therefore, it is difficult to detect such errors in the coding process.

In the coding process mentioned above, we find two main problems:

1. When we put an object into a collection, the collection will not remember the type of the object. When we remove the object from the collection again, the compiler type of the object becomes the Object type, but its runtime type is its own type.

2. Therefore, when collecting elements at //1, it is necessary to transform the mandatory human type to the specific target type, and it is easy to have "java.lang.ClassCastException" exception.

So is there any way that a collection can remember the various types of elements in the collection, and can achieve that as long as there are no compilation problems, there will be no "java.lang.ClassCastException" exception at runtime? The answer is to use generics.

2. What is generics?

Generics are "parameterized types". When it comes to parameters, the most familiar thing is to define a method with tangible parameters, and then pass arguments when calling the method. So how do you understand parameterized types? As the name implies, the type is parameterized from the original specific type, similar to the variable parameter in the method, in which case the type is also defined as a parameter form (which can be called a type parameter), and then the specific type (type parameter) is passed in when using/calling.

It seems a bit complicated. First, let's look at the example above, which uses generic writing.

public class GenericTest {
 2 
 3     public static void main(String[] args) {
 4         /*
 5         List list = new ArrayList();
 6         list.add("qqyumidi");
 7         list.add("corn");
 8         list.add(100);
 9         */
10 
11         List<String> list = new ArrayList<String>();
12         list.add("qqyumidi");
13         list.add("corn");
14         //List. add (100); // 1 prompts compilation errors
15 
16         for (int i = 0; i < list.size(); i++) {
17             String name = list.get(i); // 2
18             System.out.println("name:" + name);
19         }
20     }
21 }

With generic writing, compilation errors occur when you want to add an Integer-type object at //1. List directly restricts the list set to contain only String-type elements, so that no type conversion is required at //2, because at this time, the set can remember the type information of the element, and the compiler can confirm that it is String-type.

Combined with the generic definition above, we know that String is a type argument in List, that is to say, the corresponding List interface must contain a type parameter. And the return result of the get() method is directly the parameter type (that is, the corresponding incoming type argument). Now let's look at the specific definition of the List interface:

public interface List<E> extends Collection<E> {
 2 
 3     int size();
 4 
 5     boolean isEmpty();
 6 
 7     boolean contains(Object o);
 8 
 9     Iterator<E> iterator();
10 
11     Object[] toArray();
12 
13     <T> T[] toArray(T[] a);
14 
15     boolean add(E e);
16 
17     boolean remove(Object o);
18 
19     boolean containsAll(Collection<?> c);
20 
21     boolean addAll(Collection<? extends E> c);
22 
23     boolean addAll(int index, Collection<? extends E> c);
24 
25     boolean removeAll(Collection<?> c);
26 
27     boolean retainAll(Collection<?> c);
28 
29     void clear();
30 
31     boolean equals(Object o);
32 
33     int hashCode();
34 
35     E get(int index);
36 
37     E set(int index, E element);
38 
39     void add(int index, E element);
40 
41     E remove(int index);
42 
43     int indexOf(Object o);
44 
45     int lastIndexOf(Object o);
46 
47     ListIterator<E> listIterator();
48 
49     ListIterator<E> listIterator(int index);
50 
51     List<E> subList(int fromIndex, int toIndex);
52 }

We can see that after generic definition is adopted in List interface, E in List interface represents type parameters, and can receive specific type parameters. In this interface definition, where E appears, the same type parameters accepted from outside are expressed.

Naturally, ArrayList serves as an implementation class of the List interface in the form of:

 1 public class ArrayList<E> extends AbstractList<E> 
 2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
 3     
 4     public boolean add(E e) {
 5         ensureCapacityInternal(size + 1);  // Increments modCount!!
 6         elementData[size++] = e;
 7         return true;
 8     }
 9     
10     public E get(int index) {
11         rangeCheck(index);
12         checkForComodification();
13         return ArrayList.this.elementData(offset + index);
14     }
15     
16     //... Eliminate other specific definitions
17 
18 }

From the source code point of view, we understand why Integer type object compilation errors are added at //1, and the type to get() at //2 is String type directly.

Posted by latinofever on Thu, 18 Apr 2019 16:36:33 -0700