The generic

preface

Java Generics is a new feature introduced in JDK 5. Generics provide compile-time type-safety checks that allow developers to detect illegal types at compile time. The nature of generics is parameterized type, that is, the data type being operated on is specified as a parameter. The benefit of generics is that type safety can be checked at compile time, and all casts are automatic and implicit.

The wildcard

Conventional T, E, K, V, these are essentially wildcards, no difference, just conventions of code. For example, in the above code, T can be replaced with any letter between A and Z without affecting the normal operation of the program. However, if T is replaced with other letters, the readability may be weaker. In general, T, E, K, V, right? Here’s the deal:

  • ? Represents an indeterminate Java type

  • T (type) indicates a specific Java type

  • K and V are key values in Java key values

  • E stands for Element

Unbounded wildcards?

You can use unrestricted wildcards (a question mark in Angle brackets, <? >), indicating that any type can be held

The upper bound wildcard <? extends E>

Upper bound: Declared with the extends keyword to indicate that the parameterized type may be the specified type or a subclass of that type.

Using extends in a type parameter means that the parameter in the generic must be E or a subclass of E. This has two benefits:

  • If the type passed is not E or a subclass of E, the compilation fails,
  • The E method can be used in generics, otherwise you have to cast to E to use it

The lower wildcard <? super E>

Lower bound: declared with super, indicating that the parameterized type may be the specified type or a parent of the type, up to Object

Using super in type parameters indicates that the parameters in the generic must be E or a parent of E

< T > use

  • Declare the type parameters of the generic class
        public class GenericDemo<T> {
            private T t;
        }
Copy the code
  • Declare generic methods
        public class GenericDemo<T> {
            private T t;
            public void set(T t) { this.t = t; }
            public T get(a) { return t; }
        
            public static <T> List<T> doSomething(List<T> list){
                returnlist; }}Copy the code

The first < T > declaration is a generic method, the second List< T > declaration returns a value, and the parameter List< T > List uses the declared generic

Set () and get are also generic methods that take the declared T type of the class as an argument

  • Declare that generic classes cannot use the unbounded wildcard

? And T are indeterminate types, the difference is that we can operate on T, but right? No, like this:

/ / can
T t = operate();

/ / can't? car = operate();Copy the code

<? The use of >

  • Wildcards are used to use defined generics
List<? > a2 =new ArrayList<String>();
//a2.add("gggg"); / / an error
Copy the code

List<? Word-wrap: break-word! Important; “> The wildcard captures a specific String, but instead of calling it String, the compiler gives it a temporary code name, like “capture#1.”

  • example

public class UnBoundedWildcards {
  static List list1;
  staticList<? > list2;static List<? extends Object> list3;

static void assign1(List list) {
    list1 = list;
    list2 = list;
    Warning: * Unchecked, as long as the List is cast from a primitive type to any generic type. * * /
    list3 = list;

}

static void assign2(List
        list) {
    list1 = list;
    list2 = list;
    /* list3 = list (); /* List3 = list ()
    list3 = list;
}

static void assign3(List<? extends Object> list) {
    list1 = list;
    list2 = list;
    list3 = list;
    /* Cause of type erasure */
}

public static void main(String[] args) {
    assign1(new ArrayList());
    assign2(new ArrayList());
    /* Warning: generic conversion exception, ArrayList although can be converted to List and List
       * */
    assign3(new ArrayList());

    // All generics can be converted to 
       is thus accepted by all
    assign1(new ArrayList<String>());
    assign2(new ArrayList<String>());
    assign3(newArrayList<String>()); List<? > wildList =new ArrayList();
    wildList = newArrayList<String>(); assign1(wildList); assign2(wildList); assign3(wildList); }}Copy the code

Looks like <? > is no different from native, <? > representations are of any type, and in this case, <? > < p style = “max-width: 100%; clear: both

In effect, it says: “I want to write code using Java generics, but I’m not going to use native types here, but in this case, generic parameters can hold any type.”

List a0 = new ArrayList();
List<Object> a1 = newArrayList<>(); List<? > a2 =new ArrayList<>();
List<? extends Object> a3 = new ArrayList<>();
List<? super Object> a4 = new ArrayList<>();

a0 = a1;
a0 = a2;
a0 = a3;
a0 = a4;
// You can see that the prototype container can accept the transformation of all parameter class containers

a1 = a0;
a1 = a2;//List
       Cannot convert bits List
a1 = a3;//List
       cannot convert bits List
a1 = a4;

a2 = a0;// There is no warning in this place that requirements are not strict
a2 = a1;
a2 = a3;
a2 = a4;

a3 = a0;// Warning: Generic conversions without checking are more stringent
a3 = a1;
a3 = a2;
a3 = a4;

a4 = a0;// Warning: Generic conversion without checking
a4 = a1;
a4 = a2;//List
       cannot be converted to List
      , scope expanded
a4 = a3;//List
       cannot be converted to List
      , scope expanded

a0.add(new Object());
a1.add(new Object());
a2.add(new Object());// compilation error, no solution wildcard actual range is smaller than Object
a3.add(new Object());// Compile error,? Extends Object cannot take Object as an argument because the scope of a generic is smaller than Object
a4.add(new Object());
Copy the code

You can see that <? Extends Object > compares to <? > more stringent requirements for primitive types.

An important use of unbounded wildcards

When you are dealing with multiple generic parameters, it is sometimes important to allow one parameter to be of any type while determining a specific type for the others.

public class UnBoundedWildcards2 {
    static Map map1;
    staticMap<? ,? > map2;staticMap<String, ? > map3;static void assign1(Map map) {
        map1 = map;
    }

    static void assign2(Map
        map) {
        map2 = map;
    }

    static void assign3(Map
       
         map)
       ,> {
        map3 = map;
    }

    public static void main(String[] args) {
        assign1(new HashMap());
        assign2(new HashMap());
        // warning
        // Unchecked assignment: 'java.util.HashMap' to 'java.util.Map
      
        '
      ,?>
        assign3(new HashMap());

        assign1(new HashMap<String, Integer>());
        assign2(new HashMap<String, Integer>());
        assign3(newHashMap<String, Integer>()); }}Copy the code

When all you have are unbounded wildcards, as seen in maps, the compiler seems unable to distinguish them from the native Map

code

// Top-level interface plant
interface Plant{}/ / animals
public interface Aninal {}// Parent food
class Food{}// Fruit inherits Food and realizes Plant
class Fruit extends Food implements Plant {}// The specific fruit type of apple
class Apple extends Fruit{}// The specific fruit type of banana
class Banana extends Fruit{}Copy the code

A generic class

public class Plate<T> {
  T t;
 public T get(a){
      return t;
  }

  public void add(T t){
      this.t = t; }}Copy the code

<? Extends E > is used

// The generic parameter is Apple Plate
Plate<Apple> applePlate = new Plate<>();
// Plate with a Fruit generic parameter
Plate<Fruit> fruitPlate = new Plate<>();
// Generic Plate with the parameter Food
Plate<Food> foodPlate = new Plate<>();

/* Declare a Plate reference with an upper bound bound on the wildcard, Fruit*/
Plate<? extends Fruit> upBoundsFruitPlate1 = applePlate;
Plate<? extends Fruit> upBoundsFruitPlate2 = fruitPlate;
//compile Error The compiler reports a type error
//Plate<? extends Fruit> upBoundsFruitPlate3=foodPlate;

 /******SCENE 1 UP BOUNDS **********/
// Declare a generic to be a reference to an overbound wildcard
Plate<? extends Fruit> p1;
// make it point to a reference of the generic type Apple
p1 = new Plate<Apple>();
// Try to remove the fruit from the Plate
Fruit f = p1.get();
//Compile Error
// Try to put apple Error in the plate
//p1.add(new Apple());
Copy the code

<? Super E > is used

// The generic parameter is Apple Plate
Plate<Apple> applePlate = new Plate<>();
// Plate with a Fruit generic parameter
Plate<Fruit> fruitPlate = new Plate<>();
// Generic Plate with the parameter Food
Plate<Food> foodPlate = new Plate<>();

/* A Plate reference that declares a lower bound to the wildcard character, Fruit*/
Plate<? super Fruit> downBoundsFruitPlate1 = foodPlate;
Plate<? super Fruit> downBoundsFruitPlate2 = fruitPlate;
//compile Error The compiler reports a type error
//Plate<? super Fruit> downBoundsFruitPlate3=applePlate;

/*****SCENE 2 DOWN BOUNDS**********/
// Define a generic that is a reference to a wildcard with a lower bound
Plate<? super Fruit> p2= new Plate<Fruit>();
// Try adding Apple OK
p2.add(new Fruit());
p2.add(new Apple());
//Compile Error
// Try to remove food Error from plate
// Food food = p2.get();
/ / strong
Food food = (Food) p2.get();
Fruit fruit = (Fruit) p2.get();
Apple A = (Apple) p2.get();

Copy the code
  • Upper bound is when operating on a generic parameter with an upper bound wildcard <? When a reference to extends T>, you can only operate on methods that return generic parameter correlation (methods that return T) and not on methods that set generic parameter correlation (modifying T).

  • The lower bound is when operating on a generic parameter with the next wildcard <? When super T> is drunk, you can only operate on methods that set generic parameter correlation (change T) and cannot operate on methods that return generic parameter correlation (return T).

You can see that the generic type is the upper bounded wildcard Plate<? The extends Fruit> p1,p1.get() method will pass, but the p1.add(new Apple()) compiler will fail.

The generic type is the lower bound wildcard Plate<? Super Fruit> p2 is the opposite, the p2.get() compiler will give an error, but p2.add(new Apple()) will run successfully.

At this time, readers may be confused to ask, P1 trace root reveal is not the reference to Plate, why p1.add(new Apple()) is not allowed? What’s wrong with god?

The reason: the generics are erased after the code compiles

Generic erasure

Java generics are pseudo-generics because all generic information is erased during compilation in Java, and the first prerequisite for understanding the concept of generics correctly is to understand type erasure. Java generics are basically implemented at the compiler level. The generated bytecode does not contain the type information in the generics. When the generics are used, the type parameters will be removed when the compiler compiles, and this process is called type erasing.

If you define types like List and List in your code, they all become lists when compiled, and the JVM sees only the List. The additional type information that comes with generics is invisible to the JVM. The Java compiler does everything it can to find errors at compile time, but it still can’t find conversion exceptions at run time. Type erasure is an important difference between the way Java generics are implemented and the way C++ templates are implemented.

For the Plate class above, we can decompilate to see the state of its generics after erasure (note that too advanced decompilation tools can still recover generics from compiled comments, so we use a lower level decompilation tool, such as JAD):

/ Decompiler for Plateclass Plate
{

    Plate()
    {
    }

    Object get(a)
    {
        return t;
    }

    void add(Object t)
    {
        this.t = t;
    }

    Object t;
}
Copy the code

Look again at the result of the above example of decompiling values from P1

Plate p1 = new Plate();
Fruit f = (Fruit)p1.get();
Copy the code

Because Java generics provide compile-time validation only, there is no generics information after compilation. So when Plate is compiled, its internal generic type T is converted to Object. However, since the compiler has already checked type safety, it is possible to forcibly convert Object to Fruit without worrying about type conversion errors when evaluating Plate.

However, which generic type a generic wildcard represents is determined at runtime, so the compiler is particularly strict about checking generic wildcards for safety. Consider the following:

 public void getFood(String favoriate){
        Plate<? extends Fruit> p1;
        if("Apple".equals(favoriate)){
            p1 = new Plate<Apple>();
        }else if("Banana".equals(favoriate)){
            p1 = new Plate<Banana>();
        }
        // ...
    }
Copy the code

P1 can point to either Plate, and this happens at runtime, so the compiler does not allow p1.add(new Apple()) code to compile for type-safety reasons. Because if P1 actually points to Plate, then it is possible to put an Apple in a banana-only Plate, thus breaking the type constraint of generics. Once you understand this, the access rules for the lower bound wildcards are understood. Students can follow this train of thought for themselves.

Two examples demonstrate type erasure of Java types

  • Primitive type equality

public class Test {

   public static void main(String[] args) {

       ArrayList<String> list1 = new ArrayList<String>();
       list1.add("abc");

       ArrayList<Integer> list2 = new ArrayList<Integer>();
       list2.add(123);
       //true Gets information about their classes through the getClass() method on list1 and List2 objects, and finds the result to be true. Note The generic String and Integer types have been erased, leaving only the primitive type.System.out.println(list1.getClass() == list2.getClass()); }}Copy the code
  • Add elements of other types through reflection

We define an ArrayList generic type instantiated as an Integer object. If we call add() directly, we can only store Integer data, but when we call add() with reflection, we can store strings. This shows that the Integer generic instances are erased after compilation. Only the original type is preserved.

 public static void main(String[] args) throws Exception {

        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(1);  // This call to add can only store integers, since instances of generic types are Integer

        list.getClass().getMethod("add", Object.class).invoke(list, "asd");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
             / / 1
             //asd}}Copy the code

Type Original type retained after erasure

A primitive type is the true type of the type variable in the bytecode after the generic information is erased. Whenever a generic type is defined, the corresponding primitive type is automatically supplied, the type variable is erased, and replaced with its qualified type (unqualified variables are replaced with Object).

ArrayList< Integer > when the type is erased, the original type becomes Object, so we can store strings by reflection.

If the type variable is qualified, the original type is replaced with the type variable class of the first boundary.

public class Pair<T extends Comparable> {}
Copy the code

Then the primitive type is Comparable.

Distinguish between primitive types and the types of generic variables.

You may or may not specify a generic when calling a generic method.

In the absence of a generic type, the type of a generic variable is the smallest level of the same parent class of several types in the method, up to Object

In cases where a generic type is specified, the types of the method must be types of instances of the generic or subclasses thereof

Type checking is for the reference, not the object it actually refers to.

// reference ArrayList
      
        list11
      
ArrayList<String> list11 = new ArrayList();
list11.add("1"); // The compiler passes
//list11.add(1); // Error compiling
String str1 = list11.get(0); // The return type is String

New ArrayList
      
       ()
      
ArrayList list22 = new ArrayList<String>();
list22.add("1"); // The compiler passes
list22.add(1); // The compiler passes
Object object = list22.get(0); // Return type is Object

new ArrayList<String>().add("11"); // The compiler passes
//new ArrayList
      
       ().add(22); // Error compiling
      
                
Copy the code

Type erasure and polymorphism conflicts and resolution

public class Pair<T> {

    private T value;

    public T getValue(a) {
        return value;
    }

    public void setValue(T value) {
        this.value = value; }}public class DateInter extends Pair<Date> {

   @Override
   public void setValue(Date value) {
       super.setValue(value);
   }

   @Override
   public Date getValue(a) {
       return super.getValue(); }}Copy the code

In the subclass, we override the two methods of the parent class. We want to limit the generic type of the parent class to Date, so that the arguments of the two methods of the parent class are of type Date.

We have no problem overriding these two methods in subclasses. In fact, as you can see from their @override tag, we have no problem at all. Is that really the case?

In fact, after the type is erased, all the generic types of the parent class are changed to the original type Object, so the parent class will compile like this:

class Pair {  
    private Object value;  

    public Object getValue(a) {  
        return value;  
    }  

    public void setValue(Object value) {  
        this.value = value; }}Copy the code

The setValue method has the same type of Object as the parent class and Date as the subclass. If it is an ordinary inheritance relationship, it will not be overridden at all, but overridden.

Let’s test it in a main method:

public static void main(String[] args) throws ClassNotFoundException {  
        DateInter dateInter = new DateInter();  
        dateInter.setValue(new Date());                  
        dateInter.setValue(new Object()); // Error compiling
}
Copy the code

If it is overloading, then there are two setValue methods in the subclass, one of Object type and one of Date type. However, we find that there is no such subclass that inherits the method of Object type parameter from the parent class. So it’s rewritten, not reloaded.

The reason for this is that we pass in the parent class of the generic type is Date, and the subclass overrides the two methods whose parameter type is Date to implement polymorphism in inheritance.

However, for some reason, the virtual machine cannot change the generic type to Date. Instead, the virtual machine can erase the generic type and change it to Object. So, our intention is to rewrite and implement polymorphism. However, after type erasure, can only become overloaded. Thus, type erasure conflicts with polymorphism. Does the JVM know what you mean? Yes!! But can it be implemented directly? No!! If not, how can we override the method we want for the Date type parameter?

The JVM uses a special method to accomplish this, called the bridge method.

First, we decompile the bytecode of the subclass DateInter using javap -c className. The result is as follows:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
       4: return  

  public void setValue(java.util.Date);  // We override the setValue method
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;) V
       5: return  

  public java.util.Date getValue(a);    // We override the getValue method
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  

  public java.lang.Object getValue(a);     // A clever method generated by the compiler at compile time
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date to call our overridden getValue Method;
       4: areturn  

  public void setValue(java.lang.Object);   // A clever method generated by the compiler at compile time
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 // class java/util/Date  
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date; To call our overridden setValue method)V
       8: return  
}
Copy the code

It turns out that the subclasses we intended to override setValue and getValue have four methods, the last two of which, not surprisingly, are the bridge methods generated by the compiler itself. As you can see, the bridge methods are of type Object, which means that the bridge methods in the subclass really override the two methods in the parent class. The @oveerride on top of our own setValue and getValue methods is just an illusion. The internal implementation of the bridge method is just to call the two methods that we overwrote ourselves.

Therefore, the virtual machine cleverly uses the bridge method to resolve the conflict between type erasing and polymorphism.

The bridge methods setValue and getValue have different meanings.

  • The setValue method is designed to resolve conflicts between type erasure and polymorphism.
  • GetValue, on the other hand, has universal meaning, how to say, if this is a common inheritance:

The setValue method of the parent class is as follows:

public Object getValue(a) {  
    return super.getValue();  
}
Copy the code

The subclass override method is:

public Date getValue(a) {  
    return super.getValue();  
}
Copy the code

In fact, this is a common rewriting of ordinary class inheritance, which is called covariation.

The bridge methods Object getValue() and Date getValue() in subclasses exist at the same time, but if the two methods are regular, their method signatures are the same, meaning that the virtual machine cannot distinguish them at all. If it is our own writing Java code, the code cannot be inspected by the compiler, but the virtual machine is allowed to do this, because the virtual machine through the parameter types and return type to determine a method, so the compiler in order to implement the generic polymorphism allow yourself to do something that looks “illegitimate”, and then the hands of the virtual machine.

Java supports override methods for covariant return types. This means that the overridden method may have a more specific return type. That is, the new return type can be used as long as it can be assigned to the return type of the method you want to override.

This is specified in section 8.4.5 of the Java language specification:

If the return type is a reference type, the return type may differ between methods that overwrite each other. The concept of return type substitutability supports covariant returns, that is, specialization of a return type to a subtype.

The method declaration d1 of return type R1 can be replaced with another method d2 of return type R2 if and only if:

  • If R1 is empty, R2 is empty.
  • If R1 is primitive, R2 is the same as R1.
  • If R1 is a reference type, then:
  • R1 R2 subtypes, or R1 by unchecked conversion (the first 5.1.9) converted to R2 subtypes, or R1 = | | R2

? And T

  • Difference 1: T is used to ensure consistency of generic parameters
// Use T to ensure consistency of generic parameters
public <T extends Number> void test(List<T> dest, List<T> src)

// Wildcards are indeterminate, so this method cannot guarantee that both lists have the same element type
public void test(List<? extends Number> dest, List<? extends Number> src)
Copy the code
  • Difference 2: Type parameters can be multi-qualified and wildcard? no

Set multiple Bounds with ampersand

public class MultipleBoundaries implements Plant.Aninal{
    public static <T extends Plant & Aninal> void test(T t){}}Copy the code

Specify that the generic type T must be a common subtype of Plant and Aninal, in which case the variable T has all qualified methods and attributes. In the case of wildcards, because it is not a deterministic type, it cannot be multiqualified.

  • Difference 3: Wildcards? You can use superclass qualification but not type parameters

The type parameter T has only one type qualification:

T extends A
Copy the code

But wildcards? Two kinds of qualification can be made:

? extends A
? super A
Copy the code

Usage scenarios

  • The type parameter < T > is used primarily to declare generic classes or generic methods

  • Unbounded wildcard <? > is primarily used with generic classes or generic methods

  • T is a definite type, and is usually used to define generic classes and generic methods,

? Is an indeterminate type that is usually used for calling code and parameters of generic methods and cannot be used to define classes and generic methods

Class< T > and Class<? > the difference between

public class TestClass<T> {
    // Define a Class
      . No class is passed in
    publicClass<? > clazz;Public Class
      
        clazzT; In this case, the current class TestClass
       
         must also specify T, so that no error is reported
       
      
    public Class<T> clazzT;

    /** * Generate multiLimit objects by reflection. We need to use a cast * MultiLimit MultiLimit = (MultiLimit) * Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance(); * for the above code at runtime, if the reflective type not MultiLimit class, so must be submitted to the * Java lang. ClassCastException error. In this case, you can use the following code instead to check for type problems directly at compile time: * *@param clazz
     * @param <T>
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public static <E> E createInstance(Class<E> clazz) throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException { A a = createInstance(A.class); B b = createInstance(B.class); }}class A {}class B {}Copy the code

Problems with generics in static methods and classes

Static methods and static variables in a generic class may not use the generic type parameters declared by the generic class

public class Test2<T> {    
    public static T one;   // Error compiling
    public static  T show(T one){ // Error compiling
        return null; }}Copy the code

Because the instantiation of generic parameters in a generic class is specified when the object is defined, static variables and static methods do not need to be invoked using objects. Object is not created, how to determine what type this generic parameter is, so of course it is wrong.

But be careful to distinguish between the following:

public class Test2<T> {    

    public static <T >T show(T one){ // This is correct
        return null; }}Copy the code

Because this is a generic method, the T used in the generic method is the T defined in the method itself, not the T in the generic class.

Download the source code

This article example source code download