Detailed explanation of Java generic mechanism; Do you know all this?

Posted by Yves on Sun, 30 Jan 2022 21:25:01 +0100

Special invited author of enjoying learning class: Dream chasing youth
Reprint, please state the source!

###Foreword
When we do development, we always emphasize the concept of data type. In Java, it is divided into basic type and reference data type. There are eight basic data types. In addition to classes, we can also use the way of interface inheritance implementation to reuse code, reduce coupling and improve the flexibility of development. Genericity extends the concept of interface. Genericity means that a wide range of types, whether classes, interfaces or methods, can be applied to a wide range of types, so that the code and the data types they operate no longer need to be bound together. The same set of code can be truly applicable to multiple data types and realize more flexible code reuse, And it can improve the readability and security of the code. Speaking of this, you may be confused. Next, let's look at a simple generic, as follows:

public class Pair<T> {
    T first;
    T second;
    public Pair(T first, T second){
        this.first = first;
        this.second = second;
    }
    public T getFirst() {
    	return first;
    }
    public T getSecond() {
    	return second;
    }
}

It can be seen that Pair class is a generic class, which is different from ordinary classes in that:

1. There is one more after the class name

2. The parameters first and second are generic T types respectively

So what is this t? T is a kind of general reference, which represents type parameters. Genericity is type parameterization. The processed data is not fixed, but can be dynamically specified as parameters. So how to use the defined generic class? As follows:

Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getFirst();
Integer max = minmax.getSecond();

You can see that Integer in Pair is the actual type parameter of generic T defined before. Of course, T here can be any type. We can specify Integer or any type here. Similarly, the number of parameter types of generic types is not fixed. We can declare multiple dynamic generic types of different types. The two generic types are separated by commas, as follows:

public class Pair<U, V> {
    U first;
    V second;
    public Pair(U first, V second){
        this.first = first;
        this.second = second;
    }
    public U getFirst() {
    	return first;
    }
    public V getSecond() {
    	return second;
    }
}

The improved Pair class can be used as follows:

Pair<String,Integer> pair = new Pair<String,Integer>("Zhang San",100);

Basic principles of generics

Seeing the above case, we probably know how to define a simple generic type, so we can't help wondering, that is, what is a generic type? Why do we have to define a type parameter? We all know that we are familiar with Java polymorphism. We can define a general parent type and pass specific subtypes. Can't we also achieve such an operation? Similarly, the base class of all classes - Object also exists in Java. Can't we use Object directly? As follows:

public class Pair {
    Object first;
    Object second;
    public Pair(Object first, Object second){
        this.first = first;
        this.second = second;
    }
    public Object getFirst() {
    	return first;
    }
    public Object getSecond() {
    	return second;
    }
}

The code used should be changed as follows:

Pair minmax = new Pair(1,100);
Integer min = (Integer)minmax.getFirst();//Field cast
Integer max = (Integer)minmax.getSecond();//Field cast

This is actually possible. In fact, the generic mechanism provided by Java is actually implemented at the bottom. The reason for this design has something to do with the jvm virtual machine compilation mechanism when java was originally designed. You should know that Java came to jdk1 when generic design Version 4, and we all know that Java has a compiler and Java virtual machine. The compiler will help us convert java code into * * Class * *, and the virtual machine is responsible for loading * * Class * *, for generic classes, the java compiler will convert the code of the generic part into ordinary code, that is, like the Object type takeover above, erase the T of the type, replace it with Object, and perform the necessary type conversion. Therefore, in the process of executing Java bytecode in the Java virtual machine, it is actually the same as the Object operation and does not know the generic type, Generics do not exist. So since generics will still be converted to objects for generic erasure, why does Java support and design a generic mechanism in 1.5?

Benefits of generics

In fact, to understand this, we might as well consider the benefits of generics? At the same time, think about where the defects will exist if we use Object programming? As anyone familiar with generics knows, generics have two benefits:

1. Better security

2. Better readability

We also know that in the development and compilation stage of Java language, the ide will check the code. When there is a problem with our syntax, the ide will identify the error in the compilation stage to reduce the number of potential bugs of the program. But let's take a look at the code of Object operation:

Pair pair = new Pair("Zhang San",1);
Integer id = (Integer)pair.getFirst();
String name = (String)pair.getSecond();

It can be seen that no matter whether the id is of Integer type or the name is of String type, in the compilation stage, because the type is Object, we will carry out forced conversion. In the compilation stage, these operations are syntactically reasonable and will not report errors. However, if there are type errors in these fields, The ClassCastException exception exception will not be prompted until the program runs here. However, if we use the generic mechanism and indicate that the types are String and Integer, if the types we use are inconsistent and an error has been reported during compilation, we can run successfully only after modification, as follows:

Pair<String,Integer> pair = new Pair<>("Zhang San",1);
Integer id = pair.getFirst(); //Compilation error
String name = pair.getSecond(); //Compilation error

Therefore, it is obvious that if the generic type is used and the corresponding generic type is added to the suffix of the class, we can clearly know what the specific type is, improve the readability of development, and because the ide will check the type, the security will be higher

generic method

Of course, the scope of generics is relatively wide. We can not only define it in the declaration of classes / interfaces, but also act on methods, isolate them from the generics of classes, and realize more fine-grained generic operations. It should also be noted that the generic definition of a class is not directly related to the generic definition of a method. They are independent of each other, that is, the generic type of a class can be defined as t, and the method can also be defined as generic T, but the two t do not belong to the same. First, let's look at a case of generic methods:

public static <T> int indexOf(T[] arr, T elm){
    for(int i=0; i<arr.length; i++){
            if(arr[i].equals(elm)){
            return i;
    	}
    }
    return -1;
}

It can be seen that indexOf method is a generic method. When using it, we can:

indexOf(new Integer[]{1,3,5}, 10)

The same generic method has all the same characteristics as the generic class. You can also define multiple generic parameters on the method, such as:

public static <U,V> Pair<U,V> createPair(U first, V second){
    Pair<U,V> pair = new Pair<>(first, second);
    return pair;
}

However, different from generic classes, you only need to pass in the value of determining the type, and you do not need to declare the generic type suffix, as follows:

createPair("Zhang San",1);

Upper bound definition of generics

In the previous study, we all know that generic erasure will be converted to Object type, but can we narrow the scope of Object? That is, the upper limit of the parent type of generic type is actually supported in Java, and the upper limit definition supported in generic type is expressed by the extends keyword. Of course, the parent type here can be interface, class or type parameter. Let's introduce it respectively:

Interface as parent type

For example, when we encounter a scenario in development, we must implement the Comparable interface to realize dynamic type comparison. At this time, the code is as follows:

public static <T extends Comparable> T max(T[] arr){
    T max = arr[0];
    for(int i=1; i<arr.length; i++){
            if(arr[i].compareTo(max)>0){
            max = arr[i];
    	}
    }
    return max;
}

max is the value of the corresponding subscript of the array of generic type T. however, if the code is written in this way, it will be warned by the compiler. Because the Comparable interface itself is also a generic interface, we suggest to specify the Generic upper bound of the Comparable interface when we write it. The modification is as follows:

public static <T extends Comparable<T>> T max(T[] arr){
...................
}

This method can realize the recursive type restriction passing of generic types

The upper bound is a concrete class

Remember the generic type used by our Pair class in the above example. We can implement a subclass:

public class NumberPair<U extends Number, V extends Number> extends Pair<U, V> {
    public NumberPair(U first, V second) {
        super(first, second);
    }
}	

When we limit the corresponding type range, we can treat the first and second variables as Number types. For example, we have a summation method inside:

public double plus(){
	return getFirst().doubleValue() + getSecond().doubleValue();
}

So after we define it, our usage is as follows:

NumberPair<Integer, Double> pair = new NumberPair<>(10, 12.34);
double sum = pair.plus();

It can be seen that after the scope of generic types is limited, the compiler will check more strictly. If the type is wrong, an error will be reported directly, and the type converted when the generic type is erased is the type of the upper bound of the specified scope

Generic wildcard

We mentioned some examples above, that is, the parameter type is used as the upper bound of the range, but this writing method is cumbersome. Is there a more simplified writing method? Of course, generics support wildcard forms, which can simplify the generic writing of the upper bound of the range. A simple wildcard generic is as follows:

public void addAll(DynamicArray<? extends E> c) {
    for(int i=0; i<c.size; i++){
    	add(c.get(i));
    }
}

What type can I see in dynamic array? Extensions E > type,? Indicates a wildcard, <? Extensions E > indicates a finite wildcard, which needs to match the subtype of generic e or e. as for the specific type, it is completely unknown. When we use it, the code is as follows:

DynamicArray<Number> numbers = new DynamicArray<>();
DynamicArray<Integer> ints = new DynamicArray<>();
ints.add(100);
ints.add(34);
numbers.addAll(ints);

Here, when E is type Number,? It can be matched to dynamic array, so the wildcard has the same effect as the upper bound of the range. What's the difference between the two?

1. The writing method is limited to defining type parameters and declaring a type parameter t (generic type must be specified when using)

2.<? Extensions E > can be used to instantiate type parameters and can be used to instantiate type parameters in generic variables. However, the current type can be unknown. You only need to know the upper limit of the range, that is, the subclass belonging to generic e (when using, you can not specify the generic type or directly pass the subclass type)

So when do we use wildcards and when do we need to define the type parameter range? First, let's understand the classification of wildcards and the usage of all kinds of wildcards

Infinite wildcard

In generics, in addition to the above finite definite wildcards, there are infinite definite wildcards and super type wildcards. Let's first understand the infinite definite wildcards, and use the infinite definite wildcards to find elements in a simple dynamic array. The code is as follows:

public static int indexOf(DynamicArray<?> arr, Object elm){
    for(int i=0; i<arr.size(); i++){
        if(arr.get(i).equals(elm)){
       		return i;
        }
    }
    return -1;
}

It can be seen that the above generic type uses the indefinite wildcard. Of course, this wildcard can also be replaced by the generic type T. The effect is the same, but the indefinite wildcard is simpler to use. Of course, no matter which of the above wildcards, there is a restriction - it can only be read, not written. Let's take a look at the example first:

DynamicArray<Integer> ints = new DynamicArray<>();
DynamicArray<? extends Number> numbers = ints;
Integer a = 200;
numbers.add(a); //Code error, not allowed to add
numbers.add((Number)a); //Code error, not allowed to add
numbers.add((Object)a); //Code error, not allowed to add

You can see that all three methods try to insert when the generic type is not determined, and all of them fail. Here is Java's optimization of type checking with generic types? Wildcard, or <? The generic types of extensions E > are uncertain, so there will be a problem of type safety after allowing insertion. Of course, in addition to this, wildcards are not allowed if the return value depends on a reference type parameter, as follows:

public static <T extends Comparable<T>> T max(DynamicArray<T> arr){
    T max = arr.get(0);
        for(int i=1; i<arr.size(); i++){
            if(arr.get(i).compareTo(max)>0){
            	max = arr.get(i);
        	}
    	}
	return max;
}

If the code here uses wildcards, unexpected problems will occur, so wildcard operations cannot be used. From the above, we can conclude that the relationship between infinite wildcards and generic type parameters is as follows:

1. Generic types that can be modified by indefinite wildcards can be replaced by generic type parameters

2. Wildcards can reduce generic type parameters, make the code more concise and readable

3. If there are dependencies between type parameters, or the return value depends on the passed type parameters, only generic type parameters can be used here

Supertype wildcard

Above, we know that there can be inheritance relationships in generics, so we can specify the upper bound of the parent class of generics or use finite wildcards. To a certain extent, we can realize the flexible simplification of our development, but there are also such scenarios. For example, we know the implementation of a specific subclass, However, we hope that the parent type of any level can be used as a general operation. At this time, we are not sure that the type is a superclass. Can we still use generics? The answer is yes, generics are in Java 1 Super type wildcard operation is added in 6 in the form of <? Super E >, first let's look at a simple scenario without supertype wildcards, as follows:

//A copy method is defined
public void copyTo(DynamicArray<E> dest){
    for(int i=0; i<size; i++){
    	dest.add(get(i));
    }
}	

The operation we want to do is very simple. As long as we pass the elements of the current container into the corresponding container, we may want to use this method at this time:

DynamicArray<Integer> ints = new DynamicArray<Integer>();
ints.add(100);
ints.add(34);
//Build a dynamic array of the parent type Number
DynamicArray<Number> numbers = new DynamicArray<Number>();
ints.copyTo(numbers);

According to the Java characteristics, Integer is a subtype of Number. It is entirely reasonable to copy the instance array object of Integer into the array of parent type. However, due to the use of generics here and the inconsistency of specified type parameters, the java compiler will prompt compilation errors. However, after we use supertype wildcards, the problem is still solved, as follows:

public void copyTo(DynamicArray<? super E> dest){
    for(int i=0; i<size; i++){
    	dest.add(get(i));
    }
}

Wildcard comparison

Now that we have a certain understanding of the three wildcards, we compare and summarize the three wildcards as follows:

1. The meaning of the three wildcards is to make java dynamic code more flexible and accept a wider range of types

2.<? The super E > wildcard method is more suitable for the scenario of flexible writing, so that the java compiler will not catch the error that the subtype writes to the container of the parent type, and cannot be replaced by the generic parameter type

3.<?> And <? The extensions E > method is more suitable for flexible reading, so that the code can read the objects of E and any subclass. The wildcard operation here is exactly the same as the generic type parameter operation and can be replaced with each other

Details and points for attention in the use of generics

After learning this, you may understand the principle of generics. In fact, it is realized through the type erasure feature of java. It will still be converted to Object type or limited parent type during actual compilation, but the use of generics is not applicable in any scenario. Let's list the details and limitations of generics:

When using generic classes, interfaces, and generic methods

Note:

1. A basic type cannot be used as an instantiation type parameter, but its packing type should be used

2. Runtime type information is not applicable to generic types, such as "string" Class is not allowed to be passed as a generic instance

3. There may also be some conflicts in type erasure. For example, when the parent type implements an interface and the parent type is a generic type, if the subclass wants to implement a method of the parent interface, there will be an error in re implementing the interface

When defining generic classes, interfaces, and generic methods

Also note:

1. Objects cannot be created through type parameters, for example: T t = new T(). If you really need to create objects, it is recommended to use Class type as type parameter instead of generic type

2. The type of generic class cannot be used as static variable or static method

###Finally

If you like this article, you can pay attention to our official account and get information at the first time.

Author welfare

The following is a set of "interview dictionary" prepared by Xiaobian for the upcoming golden nine silver ten, which has targeted answers to both technical and HR questions.

With this, interview stepping on thunder? It doesn't exist!

You need this "interview book", Click here to get it for free ! Give back to fans with sincerity!!!




Cannot be used as a static variable or static method**

###Finally

If you like this article, you can pay attention to our official account and get information at the first time.

Author welfare

The following is a set of "interview dictionary" prepared by Xiaobian for the upcoming golden nine silver ten, which has targeted answers to both technical and HR questions.

With this, interview stepping on thunder? It doesn't exist!

You need this "interview book", Click here to get it for free ! Give back to fans with sincerity!!!

[external chain picture transferring... (img-20sITGvH-1623567415252)]
[external chain picture transferring... (img-twZQC0KQ-1623567415255)]
[external chain picture transferring... (img-ZTIJP6ad-1623567415256)]

Topics: Java Interview Programmer