Why generics

Two pieces of code to see why we need generics

In practice, there are often requirements for numeric summation, such as the addition of int, and sometimes the summation of long. If you also need a sum of double, you need to re-override the add method whose input is double

   private int addInt(int x,int y){
        return x+y;
    }

   private float addFloat(float x,float y){
       return x + y;
   }    
Copy the code

Defines a collection of type List to which two string values are added, followed by a value of type Integer. This is perfectly permissible, because the list defaults to type Object. Later in the loop, it is easy to make an error similar to the //1 error because you forget that you added a value of type Integer to the list earlier, or for other coding reasons. “Occurs because the compile phase is normal, and run the Java. Lang. ClassCastException” exception. Therefore, such errors are not easily detected during coding.

        list.add("test1");
        list.add("test2");
        list.add(100);

        for (int i = 0; i < list1.size(); i++) {
            String name = (String) list1.get(i); // 1
            System.out.println("name1:" + name);
        }
Copy the code

In the above coding process, we found that there were two main problems:

  1. When we put an Object into a collection, the collection does not remember the Object’s type. When we retrieve the Object from the collection again, the compiled type of the Object is changed to Object, but its runtime type remains the same.
  2. Thus, / / 1 pop set elements in the need of human force type into a specific target type, and easy to appear “Java. Lang. ClassCastException” exception

So the beauty of generics is that

  • Suitable for multiple data types to execute the same code
  • Types in generics are specified at usage time and do not require casts

What are generics

Generics, or parameterized types. When it comes to parameters, you’re most familiar with defining tangible arguments to a method and then passing the arguments when the method is called. So what about parameterized types? As the name implies, a type is parameterized from an original concrete type, similar to a variable parameter in a method. In this case, the type is also defined as a parameter (called a type parameter), and then passed in the concrete type (type argument) when the/is used. The nature of generics is to parameterize types (to control the types of parameters that are specifically restricted by the different types specified by generics without creating new types). That is, in the use of generics, the data type of the operation is specified as a parameter, which can be used in classes, interfaces, and methods, called generic classes, generic interfaces, and generic methods, respectively.

Generic classes and generic interfaces

Introduce a type variable T (any other uppercase letter will do, but the usual ones are T, E, K, V, etc.), enclose it in <>, and place it after the class name. Generic classes are allowed to have multiple type variables.

public class NormalGeneric<T> { private T data; private T getData(){ return data; } private void setData(T t){ data = t; } } public class NormalGeneric1<T, K> { private T data; private K result; private T getData() { return data; } private void setData(T t) { data = t; } public K getResult() { return result; } public void setResult(K result) { this.result = result; }}Copy the code

The definition of a generic interface is basically the same as that of a generic class. A class that implements a generic interface can be implemented in two ways:

  1. When no generic argument is passed:
public class ImplGenertor<T> implements Genertor<T> { @Override public T next() { return null; }}Copy the code

When you create an instance of a class, you need to specify a specific type:

   NormalGeneric<String> normalGeneric = new NormalGeneric<>();
Copy the code
  1. Pass in the generic argument
public class ImplGenertor2 implements Genertor<String> { @Override public String next() { return null; }}Copy the code

When you create an instance of a class, it’s the same as a normal class.

Generic methods

Generic methods indicate the specific type of the generic when calling the method. Generic methods can be used anywhere and in any scenario, including ordinary classes and generic classes. Notice the difference between generic and generic methods defined in generic classes.

Public and the return value, which indicates that this is a generic method and declares a generic T, which can appear anywhere in the generic method and can be combed as many as you want

Generic method

public class GenericMethod {
    private <T> T test(T...a){
        return a[a.length/2];
    }
    public static void main(String[] args){
       GenericMethod method = new GenericMethod();
       System.out.println(method.test("test1","test2","test3","test4"));
    }
}
Copy the code

Common methods:

public class NormalGeneric<T> { private T data; Private T getData(){return data; } private void setData(T t){ data = t; }}Copy the code

4. Qualify type variables

Sometimes, we need to impose constraints on type variables, such as calculating the minimum and maximum of two variables.

    public static <T> T min(T a,T b){
        if(a.comapareTo(b)>0) return a; else return b;
    }
Copy the code

How about making sure that the two variables passed in must have compareTo methods? The solution to this problem is to restrict T to classes that implement interface Comparable

    public static <T extends ArrayList&Comparable> T min(T a, T b){
        if(a.compareTo(b)>0) return a; else return b;
    }
Copy the code

T Extends Comparable where T represents the subtype of the type to be bound and Comparable represents the binding type. The subtypes and binding types can be classes or interfaces. If at this point we try to pass in an instance of a class that does not implement the interface Comparable, a compilation error will occur. T,V extends Comparable & Serializable Note that only one class is allowed in a restricted type, and if there is a class, that class must be the first one in the restricted list. This class qualification applies to both generic methods and generic classes

We have generic classes

public class NormalGeneric<T> {
Copy the code
  • Generic parameters cannot be instantiated with primitive types
//      NormalGeneric<double> normalGeneric = new NormalGeneric<>();
        NormalGeneric<Double> normalGeneric = new NormalGeneric<>();
Copy the code
  • Runtime type queries apply only to primitive types
        NormalGeneric<Double> normalGeneric = new NormalGeneric<>();
//        if(normalGeneric instanceof  NormalGeneric<Double>)
//        if(normalGeneric instanceof  NormalGeneric<T>)
        NormalGeneric<String> stringGeneric = new NormalGeneric<>();
        System.out.println(normalGeneric.getClass() == stringGeneric.getClass());
        System.out.println(normalGeneric.getClass().getName());
Copy the code
  • The static context type variable of a generic class is invalid
Private static T instance; Private static <T> T getInstance(){}Copy the code

You cannot reference a type variable in a static field or method. Because generics are not known until the object is created, object creation code executes the static part first, constructors second, and so on. So the static part is executed before the object is initialized. If you refer to a generic in the static part, the virtual machine will no doubt know what it is because the class has not been initialized yet.

  • Cannot create an array of parameterized types
  Restrict<Double>[] restrictArray;
  Restrict<Double>[] restricts = new Restrict<Double>[10];
Copy the code
  • Type variables cannot be instantiated
//    public Restrict() {
//        this.data = new T();
//    }
Copy the code
  • Cannot capture an instance of a generic class
Public Class ExceptionRestrict {/* A generic class does not extend Exception/Throwable*/ /private Class Problem<T> extends Exception; Public <T extends Throwable> void doWork(T x){// try{// //}catch(T x){// //do STH; // public <T extends Throwable> void doWork(T x){// try{// //}catch(T x){// //do STH; */ public <T extends Throwable> void doWorkSuccess(T x) throws T{try{}catch(Throwable e){throw x; }}}Copy the code

Inheritance rules for generic types

Now we have a class and a subclass

public class Employee {

public class Worker extends Employee {
}

Copy the code

There is a generic class

public class Pair<T> 
Copy the code

Pair and Pair are not inheritance relationships

But generic classes can inherit or extend other generic classes, such as List and ArrayList

    private static class ExtendPair<T> extends Pair<T>{

    }
Copy the code

Generic wildcards

Because of what we said earlier, Pair has nothing to do with Pair if we have a generic class and a method

public static void print(GenericType<Fruit> p){ System.out.println(p.getData().getColor()); } public class GenericType<T> {private T data; public T getData() { return data; } public void setData(T data) { this.data = data; }}Copy the code

Now we have inherited classes

public class Food {
}
public class Fruit extends Food {
}
public class Apple extends Fruit {
}
public class HongFuShi extends Apple {
}

Copy the code

This is what happens:

 public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
       GenericType<Fruit> a = new GenericType<>();
        print(a);
       GenericType<Orange> b = new GenericType<>();
        //print(b);
    }
Copy the code

To solve this problem, a wildcard type was proposed, right? There are two ways to use it:? Extends X represents an upper bound for a type whose argument is a subclass of X. Okay? Super X represents the lower bound of the type, and the type argument is the superclass of X. The names of these two methods, super in particular, are very confusing, so let’s take a closer look at these two methods.

  • ? extends X

Represents the arguments passed to the method, which must be subclasses of X (including X itself)


    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a);
        GenericType<Orange> b = new GenericType<>();
        print2(b);
        //print2(new GenericType<Food>());
        GenericType<? extends Fruit> c =  new GenericType<>();

        Apple apple =  new Apple();
        Fruit fruit = new Fruit();
        //c.setData(apple);
        //c.setData(fruit);
        Fruit x = c.getData();
    }
    
Copy the code

For GenericType, however, if it provides methods for parameters of type GET and set, set methods are not allowed to be called, resulting in compilation errors

        Fruit x = c.getData();
//        c.setData(apple);
Copy the code

Why? It’s very simple,? Extends X is an upper bound on a type. The type argument is a subclass of X, so it’s safe to say that the get method returns an X (whether X or a subclass of X) that the compiler knows for sure. But the set method only knows that it is passing in an X, not a subclass of X. Summary: Primarily used to securely access data, X and its subtypes can be accessed, and non-NULL data cannot be written.

  • ? super X

Represents the argument passed to the method, which must be a superclass of X (including X itself)

 public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitGenericType = new GenericType<>();
        GenericType<Apple> appleGenericType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
        GenericType<Orange> orangeGenericType = new GenericType<>();
        printSuper(fruitGenericType);
        printSuper(appleGenericType);
Copy the code

For GenericType, however, the set method can be called if it provides methods for parameters of type GET and set, and only X or a subclass of X can be passed in

public class GenericType<T> { private T data; public T getData() { return data; } public void setData(T data) { this.data = data; }} // The lower bound of the GenericType parameter is Apple GenericType<? super Apple> x = new GenericType<>(); x.setData(new Apple()); x.setData(new HongFuShi()); // x.setData(new Fruit()); Object data = x.gettdata ();Copy the code

The get method only returns a value of type Object, okay? Super X is the lower bound of the type, and the type argument is X’s superclass (including X itself), so it is safe to say that get must return X’s superclass. No, but it’s safe to say that Object must be its superclass, so the get method returns Object. The compiler knows for sure. For the set method, the compiler does not know the exact type it needs, but X and subclasses of X can be safely converted to X. Summary: Mainly used to safely write data, X and its subtypes can be written.

  • An unqualified wildcard character

There is no restriction on the type. As a parent of all types, such as Pair<? >; For example: ArrayList al=new ArrayList(); ArrayList al=new ArrayList(); Collection elements can be of any type. This makes no sense, usually in methods, just to illustrate usage. In use:? GetFirst () : return value can only be assigned to Object,; void setFirst(?) The setFirst method cannot be called, even with Object;

How do virtual machines implement generics

The idea of generics began to take root in the Template of C++ language. When there was no version of generics in Java language, type generalization could only be realized through the combination of Object being the parent class of all types and type casting. Since all types in the Java language inherit from java.lang.Object, it is possible to turn an Object into any Object. But because of the infinite possibilities, only the programmer and the running virtual machine know what type of Object this Object is. At compile time, there is no way for the compiler to check whether an Object was successfully cast, and if the programmer is left to ensure that this operation is correct, much of the risk of ClassCastException is transferred to the program runtime. Generics seem to be used in C# and Java in the same way, but the implementation is fundamentally different. In C# generics exist in program source code, in compiled IL (Intermediate Language, where generics are a placeholder), and in the runtime CLR. List < int > and List < String > are two different types. They are generated at runtime and have their own virtual method tables and type data. This implementation is called type inflation, and generics implemented based on this method are called true generics. Generics in the Java language, on the other hand, exist only in the program source code. In the compiled bytecode file, they are replaced with the original Raw Type (also known as Raw Type), and forced transformation code is inserted in place. Therefore, for the Runtime Java language, ArrayList < int > is the same class as ArrayList < String >, so generics technology is actually a syntactic sugar of the Java language. The implementation method of generics in the Java language is called type erasing, and generics based on this method are called pseudo-generics. When you compile a piece of Java code into a Class file and then decompile it using a bytecode decompiler, you will find that the generics are gone and the program is written as it was before Java generics, and the generic types are returned to their native types

With the introduction of Java generics, method calls under various scenarios (virtual machine parsing, reflection, and so on) can have an impact on the old infrastructure and new requirements, such as how to retrieve incoming parameterized types in generic classes. As a result, the JCP organization modified the virtual machine specification to introduce new attributes such as Signature and LocalVariableTypeTable to solve the problem of identifying parameter types that come with generics. Signature is the most important of these attributes. Its function is to store the characteristic signature of a method at the bytecode level [3]. The parameter type stored in this property is not the native type, but contains the information of the parameterized type. The modified VM specifications require that all VMS that can recognize Class files of version 49.0 or higher correctly recognize the Signature parameter. In addition, we can also conclude from the appearance of Signature attribute that the so-called erasure is only the bytecode in the Code attribute of the method. In fact, the metadata still retains the generic information, which is the basic basis of the parameterized type through reflection.