First contact generic completion
When using container classes
ArrayList<Integer> list = new ArrayList<>();
list.add(123);
Integer i = list.get(0);
Define < > as Integer. When writing code, you can only pass Integer type data to list.
And you can receive the elements of the list directly through an Integer type variable.
`
ArrayList<Test> list = new ArrayList<>(); Test test = list.get(0);
Also defined as a custom type.
`
ArrayList list = new ArrayList(); String str = (String) list.get(0);
You can also not define a type. The default is Object.
How can the ArrayList class insert any data of the same type? Through polymorphism?
If it is polymorphic, how do you do Integer I = list get(0); Don't you need to convert Object to Integer?
It's actually through generics.
JDK 1.5 introduces generics. Generics are added to the definitions of all container classes.
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable
ArrayList::add(e),ArrayList::get(index)
public boolean add(E e) {...} public E get(int index) {...}
Prior to JDK 1.5, ArrayList could only be cast.
public void add(Object obj){...} public Object get(int index){...}
JDK1.5 front... ArrayList list = new ArrayList(); list.add(123); list.add("haha"); Integer o1 = (Integer) list.get(0); //ClassCastException Integer o2 = (Integer) list.get(1);
This can easily cause ClassCastException type conversion exceptions.
·
What are generics?
generic paradigm
concept
That is, a wide range of types, which can be passed as parameters.
Types in Java are divided into basic data types and reference data types. Generics can only use reference data types (arrays also apply). No ArrayList < int >, ArrayList < double >
benefit
-
Delay determining type until class / method / interface is used. Corresponding generic class, generic method and generic interface
-
Type safety: advance the type conversion check to the compilation time, prohibit all operations violating type safety, and effectively avoid ClassCastException.
Integer i = list.get(0); //The development environment and compiler will prompt for type errors list.add("haha"); String str = list.get(1);
- Simplify code and improve readability.
ArrayList list = new ArrayList(); list.add(123); Integer o1 = (Integer) list.get(0); ---------------------------- ArrayList<Integer> list = new ArrayList<>(); list.add(123); Integer i = list.get(0); //Redundant cast code is not required
- The element types in the collection class must be the same, and the enhanced for loop can be used; for-each
for(Integer i:list){ ... }
Generics have nothing to do with polymorphism
Generic: the actual type can be determined after compilation.
Polymorphism: the actual type cannot be determined during compilation until the runtime. Polymorphism is based on Inheritance and calling virtual methods
Generic class
Before the class name, you can define the generic < T > in the class. When using the UseGeneric class, you need to pass in the type parameter to instantiate the type of T, and the type of the member variable key is also determined.
public class UseGeneric<T>{ private T key; public T getKey() { return key; } public void setKey(T key) { this.key = key; } }
The test class GenericTest instantiates the generic type T as String, and the key also determines that the type is String.
public class GenericTest { public static void main(String[] args) { UseGeneric<String> useGeneric = new UseGeneric<>(); useGeneric.setKey("123"); String key = useGeneric.getKey(); } }
Basic principles
After decompilating javap of GenericTest::main method:
public static void main(java.lang.String[]) throws java.lang.Exception; descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: new #2 // class generic/UseGeneric 3: dup 4: invokespecial #3 // Method generic/UseGeneric."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String 123 11: invokevirtual #5 // Method generic/UseGeneric.setKey:(Ljava/lang/Object;)V 14: aload_1 15: invokevirtual #6 // Method generic/UseGeneric.getKey:()Ljava/lang/Object; 18: checkcast #7 // class java/lang/String 21: astore_2 22: return LocalVariableTable: Local variable table Start Length Slot Name Signature 0 23 0 args [Ljava/lang/String; 8 15 1 useGeneric Lgeneric/UseGeneric; 22 1 2 key Ljava/lang/String; LocalVariableTypeTable: Field signature table Start Length Slot Name Signature 8 15 1 useGeneric Lgeneric/UseGeneric<Ljava/lang/String;>;
In the Code attribute of the method table, you can see:
15:invokevirtual calls the virtual method getKey() to see that the return value of the method is java/lang/Object.
Note: after compilation, T will be deleted Generic erase Type java/lang/Object
18: Checkcast type conversion check: check whether the actual type of key is String. If not, throw a strong conversion exception ClassCastException. (in fact, because of the existence of generics, checkcast must succeed in the type conversion operation here.)
After T is erased by generics, the compiler also helps us add type conversion code:
String key = useGeneric.getKey(); —> String key = (String) useGeneric.getKey();
`
In addition to the Code attribute, the main method table also has;
1.LocalVariableTable 2.LocalVariableTypeTable
Of which 1 Is a local variable table used to store information such as variable name and variable descriptor 2 It is a field feature Signature table that stores information such as the Signature attribute that records generic information.
Understanding the Java virtual machine:
After adding generics in JDK 5, the Signature attribute is added to the Class file specification and can appear in the attributes of Class, field table and method table structure. After that, if the generic Signature of any Class, interface, initialization method or member contains type variables or parameterized types, the Signature property will record the generic Signature information for it.
Because the generic information is stored in the Signature attribute. Therefore, even if the generics are erased during compilation, the fields and methods in the Class file can be deleted during program operation Get generic information dynamically of
`
generic method
The getKey method is not a generic method, but a method in a generic class
public class UseGeneric<T>{ private T key; public T getKey() { return key; } }
Whether a method is a generic method has nothing to do with its class. Only methods that declare and use type parameters are generic methods.
public <T> T compareAndReturn (T t1,T t2){ return t1.equals(t2) ? t1 : null; }
`
public <H> void test(ArrayList<H> list){ list.add(123); //compile error list.add(list.get(0)); }
If the type of T is unknown during compilation, it will be erased as Object. We can do nothing but call the method of Object class.
Moreover, because the type of T is unknown, for example, in the case of test() method, we cannot make operations that may violate type safety.
By limiting the upper bound < T extends xx > when defining type parameters, it indicates that t can only be a subclass of xx. T is erased as xx after compilation
public class Test{ public <T extends Number> T compareAndReturn (T t1,T t2){ //T is erased as Number return t1.doubleValue() > t2.doubleValue() ? t1 : t2; } }
Qualification of type parameters
Upper bound of generics
The upper bound can be an entity class
< T extends Son>
When declaring a generic, you can specify the upper bound of the generic, indicating that after the generic is erased, T will be modified to Son.
The compareAndReturn method will look like this after compilation:
public Number compareAndReturn (Number t1,Number t2){ return t1.doubleValue() > t2.doubleValue() ? t1 : t2; }
When using this method, the type parameters passed in are limited to Number and its subclasses, and the actual type of the returned value is also Number and its subclasses.
Double aDouble = compareAndReturn(100.1, 100.2);
The upper bound can be an interface
< T extends Comparable>
public <T extends Comparable<T>> T compareAndReturn (T t1,T t2){ return t1.compareTo(t2) > 0 ? t1 : t2; }
< T extends Comparable > indicates that the type parameter passed in is limited to the implementation class of Comparable and can only be compared with the same type.
The upper bound can be other generics
//Custom method public class EasyArrayList<E> { public <T> void put(ArrayList<T> list){ for(T t:list){ add(t); // need cast to E } } } public static void main(String[] args) { EasyArrayList<Number> list = new EasyArrayList<>(); EasyArrayList<Integer> integers = new EasyArrayList<>(); //Inserting elements of different types is not allowed list.put(integers);//compile error }
After modifying the put method, you can add a collection of subclasses of Number.
public <T extends E> void put(ArrayList<T> list){ for(T t:list){ add(t); } }
generic interface
Interface and capability oriented programming
There are interfaces before implementation classes. Interfaces Abstract capabilities, and specific classes implement interfaces to obtain capabilities. For example, Comparator interface, Comparable comparability interface, an interface that can be implemented by anyone. Implementing the compare method means that the entity class has the ability to compare; Implementing the Comparable interface means that entity classes can be compared.
Before no generics public interface Comparator{ boolean compare(Object o1,Object o2); } class A implements Comparator{ int key; int compare(Object o1,Object o2){ ... } }
With generics, the readability is improved, and the implementation class can more clearly understand how to implement the method.
public interface Comparator<T>{ boolean compare(T o1,T o2); } class A implements Comparator{ int key; int compare(A o1,A o2){ if(o1.key == o2.key) return 0; return o1.key > o2.key ? 1 : -1; } }
Generic wildcard
Infinite wildcard <? >
Why wildcards?
When instantiating a parameter type, if you are not sure, you can use wildcards? Represents any type.
public void test(ArrayList<?> list){ ... }
The test method can receive List objects with various types of parameters
test(new ArrayList<Integer>()); test(new ArrayList<String>());
More concise type parameter qualification can be achieved by using wildcards:
public <T extends E> void put(ArrayList<T> list){ for(T t:list){ add(t); } } ------------------------------- //Restrict the incoming ArrayList generic to subclasses of E public void put(ArrayList<? extends E> list){ for (E e : list) { add(e); } }
This looks like infinity wildcards are powerful. But in fact, it hurts.
public void test(ArrayList<?> guys){ Object o = guys.get(0);//compile success }
To get the data in the list, you have to use object to receive it. Object o = list.get(0) this runs counter to the original intention of generics.
Finite definite wildcards and supertype wildcards are given? The meaning of existence.
`
Before understanding these two wildcards, know the PECS principles.
PECS(Producer Extends Consumer Super)
"PECS" is from the collection's point of view. If you are only pulling items from a generic collection, it is a producer and you should use extends; if you are only stuffing items in, it is a consumer and you should use super. If you do both with the same collection, you shouldn't use either extends or super.
If the current container is only used to obtain elements, the container is equivalent to a producer. You should use <? extends T>
If the current container is only used to fill elements, the container is equivalent to a consumer. You should use <? super T>
Finite wildcard <? extends >
public void test(ArrayList<? extends Father> guys){ Father guy = guys.get(0);//compile success guys.add(new Son()); // compile error }
The producer collection is instantiated as a collection of Father and one of its subclasses. It could be Son or daugher's Daughter
ArrayList<Son> guys = new ArrayList<>(); ArrayList<Daughter> guys = new ArrayList<>();
If it is not possible to determine which of the above two during compilation, the compiler cannot guarantee type safety if data is filled into the collection. Therefore, the compiler directly prohibits filling data into the "producer" collection to avoid type errors in the following cases:
ArrayList<Son> sons = new ArrayList<>(); sons.add(new Daughter()); ArrayList<Daughter> daughters = new ArrayList<>(); daughters.add(new Sons());
However, the compiler allows the following operations:
ArrayList<? extends Father> guys = new ArrayList<>(); ArrayList<Son> sons = new ArrayList<>(); sons.add(new Son()); guys = sons;
The collection sons of subclasses whose parameter type is Father can be assigned to fathers.
The compiler knows that sons is a collection of Son subclasses and can point to a subclass collection object on the premise of ensuring type safety. This is equivalent to informing the compiler during compilation that only Son type data may exist in the current collection:
ArrayList<? extends Father> guys ... -> ... -> ... -> new ArrayList<Son>();
The "producer" collection can ensure that all elements in the collection are subclasses of Father. Therefore, the data in the collection can be received uniformly as Father static type.
Father guy = guys.get(0);
As can be seen from reflection, the generic type is <? Extend Father > after being erased, the static type is Father
Class<? extends Father> clazz = ...Cannot determine during compilation; Father genericFather = clazz.newInstance();
Super type wildcard <? super >
ArrayList<? super Father> guys = new ArrayList<>(); guys.add(new Father());//compile success guys.add(new Son());//compile success guys.add(new GrandFaher());//compile error guys.add(new Object())//compile error
< ? Super Father > means that the type parameter is instantiated as Father and its parent class.
The compiler only knows that the current "consumer" collection may be a collection of the Father class or its parent class
ArrayList<Father> guys = new ArrayList<>(); ArrayList<GrandFather> guys = new ArrayList<>();
The compiler cannot determine, so it simply prohibits adding data that cannot guarantee type safety to the current collection. But! The compiler can ensure that the data added to Father and its subclasses are type safe. Therefore, the "consumer" collection can arbitrarily add data of subclasses of limited types.
ArrayList<Father> fathers = new ArrayList<>(); ArrayList<GrandFather> grandFathers = new ArrayList<>(); fathers.add(new Son()); grandFathers.add(new Daughter());
It is also allowed by the compiler to create consumer collections in the following ways:
ArrayList<? super Father> guys = new ArrayList<>(); ArrayList<GrandFather> fathers = new ArrayList<>(); guys = fathers;
After the guys pointer points to the new collection again (grandFather object collection - > father object collection), there is only grandFather data in the current collection. The collection can also add any subclass object:
guys.add(new Son()); guys.add(new Daughter()); guys.add(new Father());
Therefore, the data in the current collection can only be received by the unified parent class Object of all classes. Because it is impossible to determine what type of collection the guys are initialized to and what kinds of data exist in the collection during compilation. In order to ensure type safety, you can only let the Object boss out of the mountain.
Object o = guys.get(0);
From the reflection, we can see that the generic <? After super father > is erased, the static type is Object
Class<? super Father> clazz = ...Cannot determine during compilation; Object guy = clazz.newInstance()
Generic erase
The Java compiler implements generic erasure by type conversion.
Generics only work during compilation and only exist in type information during runtime.
Why generic erasure?
Backward compatibility, for example, lower versions of collection classes do not have generics
ArrayList list = new ArrayList();
After the updated version, the lower version of java code can continue to run
ArrayList list = new ArrayList(); amount to ArrayList<Object> list = new ArrayList<>();
ArrayList<Integer> list = new ArrayList<>(); Erase ArrayList list = new ArrayList(); Integer i = list.get(0);
Get generic information at runtime
The Signature constant in the class file information stores generic information.
For example, a field contains generic information
public class GenericTest { UseGeneric<String> useGeneric = new UseGeneric<>(); }
javap view class file information
generic.UseGeneric<java.lang.String> useGeneric; descriptor: Lgeneric/UseGeneric; flags: ACC_STATIC Signature: #26 // Lgeneric/UseGeneric<Ljava/lang/String;>;
For example, the local variable or parameter of a method has generic information, and the relationship between the Signature constant and the local variable exists in the LocalVariableTypeTable
public void test(ArrayList<String> list){ UseGeneric<Integer> useGeneric = new UseGeneric<>(); }
javap view class file information
public void test(java.util.ArrayList<java.lang.String>); descriptor: (Ljava/util/ArrayList;)V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=2 0: new #21 // class generic/UseGeneric 3: dup 4: invokespecial #22 // Method generic/UseGeneric."<init>":()V 7: astore_2 8: return LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lgeneric/GenericTest; 0 9 1 list Ljava/util/ArrayList; 8 1 2 useGeneric Lgeneric/UseGeneric; LocalVariableTypeTable: Start Length Slot Name Signature 0 9 1 list Ljava/util/ArrayList<Ljava/lang/String;>; 8 1 2 useGeneric Lgeneric/UseGeneric<Ljava/lang/Integer;>; Signature: #63 // (Ljava/util/ArrayList<Ljava/lang/String;>;)V
Get generic information at runtime
Field usegeneric = new usegeneric < > ();
//Gets the Type information of the field useGeneric Type useGeneric = GenericTest.class.getDeclaredField("useGeneric").getGenericType(); //Strong conversion to its subclass, parameterized type information, ParameterizedType ParameterizedType type = (ParameterizedType) useGeneric; //Call ParameterizedType method and getActualTypeArguments to get generic information System.out.println(type.getActualTypeArguments()[0]); //output class java.lang.String
Method parameters
public void test(ArrayList<String> list, HashMap<String,Thread> map){ UseGeneric<Integer> useGeneric = new UseGeneric<>(); }
//Get method information Method test = GenericTest.class.getDeclaredMethod("test", ArrayList.class, HashMap.class); //Gets generic information for method parameters for (Type genericParameterType : test.getGenericParameterTypes()) { //Force to ParameterizedType ParameterizedType pt = (ParameterizedType) genericParameterType; for (Type actualTypeArgument : pt.getActualTypeArguments()) { System.out.println(actualTypeArgument); } } //output class java.lang.String class java.lang.String class java.lang.Thread
Reflection cannot get the local variable information, so it cannot get the generic information of the local variable.
`
Although the runtime can get generic information through reflection, it has little effect
Generic and static types
Static members in generic class C cannot use generics declared by the current class.
Generic class C needs to specify a type parameter every time it is created and used. If the type of static member changes with it, it violates the semantics of static - binding with type, and all instances of the class correspond to unique static members.
For example, the following code:
public class UseGeneric<T>{ @CompileError private static T key; public T getKey() { return key; } public void setKey(T key) { this.key = key; } @ComplieError public static T test(){ ... } @ComplieError public static <H extends T> void test1(){ ... } @ComplieSuccess public static <D,J,W> J staticTest(ArrayList<D> list){ ... } }
Static members cannot use type parameters of type. However, static methods can customize type parameters.
reference resources:
Old horse says programming - generics