I The concept of generics (why generics are needed)?
First, let's look at the following short code:
1 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 list type collection is defined. First, two string type values are added to it, and then an Integer type value is added. This is completely allowed because the default type of list is Object. In the subsequent loop, errors like / / 1 are easy to occur because you forget to add Integer values to the list or other encoding reasons. Because the compilation phase is normal, the "java.lang.ClassCastException" exception will appear at runtime. Therefore, it is difficult to find such errors in the coding process.
In the above coding process, we found two main problems:
1. When we put an Object into the collection, the collection will not remember the type of the Object. When we take the Object out of the collection again, the compilation type of the Object is changed to Object type, but its runtime type is still its own type.
2. Therefore, when taking out the collection elements at / / 1, you need to artificially force the type to be converted to the specific target type, and it is easy to encounter "java.lang.ClassCastException".
So is there any way to make the collection remember the types of elements in the collection, and as long as there is no problem during compilation, there will be no "java.lang.ClassCastException" exception at runtime? The answer is to use generics.
II What is generics?
Generic, or "parameterized type". When referring to parameters, the most familiar thing is that there is a formal parameter when defining the method, and then the arguments are passed when the method is called. So what about parameterized types? As the name suggests, it is to parameterize the type from the original specific type, which is similar to the variable parameters in the method. At this time, the type is also defined in the form of parameters (which can be called type parameters), and then the specific type (type arguments) is passed in during use / call.
It seems a little complicated. First, let's take a look at the example above, which uses generic writing.
1 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 prompt compilation error 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 }
After the generic writing method is adopted, a compilation error will occur when you want to add an Integer type object at / / 1. Through list < String >, it is directly limited that the list collection can only contain elements of String type, so there is no need for forced type conversion at / / 2, because at this time, the collection can remember the type information of the element, and the compiler can confirm that it is of String type.
Combined with the above generic definition, we know that in List < String >, String is a type argument, that is, the corresponding List interface must contain type parameters. And the return result of the get() method is directly this parameter type (that is, the corresponding passed in type argument). Let's take a look at the specific definition of the List interface:
1 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 the generic definition is adopted in the List interface, E in < E > represents type parameters and can receive specific type arguments. In this interface definition, wherever e appears, it represents the same type arguments accepted from the outside.
Naturally, ArrayList is the implementation class of the List interface, and its definition form is:
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 //... Omit other specific definition processes 17 18 }
Therefore, from the perspective of source code, we understand why an Integer type object compilation error is added at / / 1, and the type obtained by get() at / / 2 is directly a String type.
III Custom generic interfaces, generic classes, and generic methods
From the above content, you have understood the specific operation process of generics. I also know that interfaces, classes and methods can also be defined using generics and used accordingly. Yes, it can be divided into generic interfaces, generic classes and generic methods.
Custom generic interfaces, generic classes and generic methods are similar to the List and ArrayList in the above Java source code. As follows, let's look at the simplest generic class and method definitions:
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 System.out.println("name:" + name.getData()); 7 } 8 9 } 10 11 class Box<T> { 12 13 private T data; 14 15 public Box() { 16 17 } 18 19 public Box(T data) { 20 this.data = data; 21 } 22 23 public T getData() { 24 return data; 25 } 26 27 }
During the definition of generic interfaces, generic classes and generic methods, our common parameters in the form of T, E, K and V are often used to represent generic formal parameters, because we receive type arguments passed in from external use. So, is the type of corresponding object instance generated for simultaneous interpreting different incoming arguments?
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 8 System.out.println("name class:" + name.getClass()); // com.qqyumidi.Box 9 System.out.println("age class:" + age.getClass()); // com.qqyumidi.Box 10 System.out.println(name.getClass() == age.getClass()); // true 11 12 } 13 14 }
Therefore, we find that when using generic classes, although different generic arguments are passed in, different types are not really generated. There is only one generic class passing in different generic arguments in memory, that is, it is the original most basic type (Box in this example). Of course, logically, we can understand it as multiple different generic types.
The reason is that the concept of generics in Java is proposed for the purpose of only acting on the code compilation stage. In the compilation process, the relevant information of generics will be erased after correctly checking the generics results, that is, the successfully compiled class file does not contain any generics information. Generic information does not enter the runtime phase.
This can be summed up in one sentence: generic types are logically regarded as multiple different types, which are actually the same basic types.
IV Type wildcard
Following the above conclusion, we know that Box < number > and Box < integer > are actually Box types. Now we need to continue to explore a question. Logically, can Box < number > and Box < integer > be regarded as generic types with parent-child relationship?
To clarify this problem, let's continue to look at the following example:
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<Number> name = new Box<Number>(99); 6 Box<Integer> age = new Box<Integer>(712); 7 8 getData(name); 9 10 //The method getData(Box<Number>) in the type GenericTest is 11 //not applicable for the arguments (Box<Integer>) 12 getData(age); // 1 13 14 } 15 16 public static void getData(Box<Number> data){ 17 System.out.println("data :" + data.getData()); 18 } 19 20 }
We find that an error message appears at code / / 1: the method GetData (box < number >) in the T-type generictest is not applicable for the arguments (box < integer >). Obviously, through the prompt information, we know that box < number > cannot be logically regarded as the parent class of box < integer >. So why?
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<Integer> a = new Box<Integer>(712); 6 Box<Number> b = a; // 1 7 Box<Float> f = new Box<Float>(3.14f); 8 b.setData(f); // 2 9 10 } 11 12 public static void getData(Box<Number> data) { 13 System.out.println("data :" + data.getData()); 14 } 15 16 } 17 18 class Box<T> { 19 20 private T data; 21 22 public Box() { 23 24 } 25 26 public Box(T data) { 27 setData(data); 28 } 29 30 public T getData() { 31 return data; 32 } 33 34 public void setData(T data) { 35 this.data = data; 36 } 37 38 }
In this example, it is obvious that error prompts will appear at / / 1 and / / 2. Here we can use the method of counter evidence to explain.
Assuming that box < Number > can logically be regarded as the parent class of box < integer >, there will be no error prompt at / / 1 and / / 2, so the problem arises. What type is it when retrieving data through the getData() method? Integer? Float? Or Number? Due to the uncontrollable sequence in the programming process, type judgment and forced type conversion must be carried out when necessary. Obviously, this contradicts the concept of generics, so logically, box < Number > cannot be regarded as the parent of box < integer >.
OK, let's go back to the first example of "type wildcard". We know the deep-seated reason for its specific error prompt. So how to solve it? Headquarters can define a new function. This is obviously contrary to the concept of polymorphism in Java. Therefore, we need a reference type that can be logically used to represent the parent classes of box < integer > and box < number >, so the type wildcard came into being.
Type wildcards are generally used? Instead of specific type arguments. Note that this is a type argument, not a type parameter! And box <? > Logically, it is box < integer >, box < number > And all the parent classes of box < concrete type arguments >. Thus, we can still define generic methods to fulfill such requirements.
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name); 10 getData(age); 11 getData(number); 12 } 13 14 public static void getData(Box<?> data) { 15 System.out.println("data :" + data.getData()); 16 } 17 18 }
Sometimes, we may also hear the upper limit of type wildcard and the lower limit of type wildcard. What is the specific situation?
In the above example, if you need to define a method whose function is similar to getData(), but there are further restrictions on type arguments: it can only be Number class and its subclasses. In this case, the upper limit of type wildcard is required.
1 public class GenericTest { 2 3 public static void main(String[] args) { 4 5 Box<String> name = new Box<String>("corn"); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name); 10 getData(age); 11 getData(number); 12 13 //getUpperNumberData(name); // 1 14 getUpperNumberData(age); // 2 15 getUpperNumberData(number); // 3 16 } 17 18 public static void getData(Box<?> data) { 19 System.out.println("data :" + data.getData()); 20 } 21 22 public static void getUpperNumberData(Box<? extends Number> data){ 23 System.out.println("data :" + data.getData()); 24 } 25 26 }
At this point, it is obvious that the call at code / / 1 will give an error prompt, while the call at / / 2 / / 3 is normal.
The upper limit of the type wildcard is through the form box <? Extensions number > formal definition. The corresponding lower limit of type wildcard is box <? Super number > form, whose meaning is just opposite to the upper limit of type wildcard, will not be described here.
V Extra discourse
The examples in this article are simply given to illustrate some ideas in generics, and do not necessarily have practical usability. In addition, when it comes to generics, I believe you use them most in collections. In fact, in the actual programming process, you can use generics to simplify development and ensure code quality. It should also be noted that there is no so-called generic array in Java.
For generics, the most important thing is to understand the idea and purpose behind it.