Data Structure | Generics | Erasure Mechanisms | Upper Bounds of Generics

Directory

?edit

1. Generics

1.1 The Object class leads to the concept of generics

2. Generic syntax

2.1 Generic writing code

3. Generic mechanism

3.1 Erase Mechanism

4. The upper bound of generics

4.1 Syntax of generic upper bounds

4.2 The use of generic upper bounds

5. Generic methods

5.1 Generic method syntax

5.2 Use of generic methods

1. Generic

In general classes and methods, only specific codes can be used to implement operations on the same type of data. For example, the same type is stored in an array, and this storage method is too rigid. Therefore, JDK1.5 introduces a new syntax: generics. Generally speaking, generics are multiple data types (flooding). From the code point of view, it is to realize the storage between different types, so We use generics when we want to store various kinds of data.

1.1Object class leads to generic concept

Before generics, we learned from the Object class that the parent class of all classes is the Object class, so we can set an array as the Object type, so that various elements can be stored in the array. So we can write code like this:

class MyArray {
    //Object type array
    Object[] arr = new Object[3];
    public void show() {
        //Initialize three different types of data respectively
        arr[0] = 1;
        arr[1] = 1.2;
        arr[2] = "abc";
        //Loop through the arr array
        for (int i = 0; i < arr. length; i ++ ) {
            System.out.println(arr[i]);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray. show();
    }
}

Output after running:

The above code works well because we directly initialize the elements in the Object class. When we use the get and set methods to implement, we will find the difference.

class MyArray {
    //Object type array
    public Object[] arr = new Object[3];
    //Provide the get method
    public Object getPos(int pos) {
        return this.arr[pos];
    }
    //Provide the set method
    public void setArr(int pos, Object value) {
        this.arr[pos] = value;
    }
}

public class Test {
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        // Set three different types of elements respectively
        myArray. setArr(0,1);
        myArray.setArr(1,1.2);
        myArray.setArr(2,"abc");
        //Output three different types of elements
        System.out.println(myArray.getPos(0));
        System.out.println(myArray.getPos(1));
        //When outputting the character type, an error is reported
        System.out.println(myArray.getPos(3));
    }
}

Output after running:

We found , an exception occurs when I add a string to the array. Therefore, when Object stores different types

Errors still occur. Therefore, we can think that since I am not allowed to store different types of data, then I just store the same type of data, and then we can use generics. It can store different types of data in different objects, but the type of each element in different objects is the same.

2. Generic syntax

(1) Grammar 1

//Generic class syntax format
class generic class name <type parameter list> {
    //content
}
// There can be multiple formal parameters in a generic class
class ClassName<T,S,B,U> {
    //content
}

In the above code, we can see that there is one more <> between the generic class and the ordinary class, and the rest is not much different. Note that multiple parameters can be written in <>.

(2) Syntax 2

//generic class inheritance class or generic class
class generic class name <type parameter list> extends class name {

}
// Generic class inherits generic class
class ClassName1<T,S,B,U> extends ClassName2<T> {

}

The above code indicates that a generic class can inherit a common class or a generic class.

2.1 Generic writing code

Therefore, we can write a generic code like this:

class MyArray<T> {
    //Create a generic array
    T[] arr= (T[])new Object[3];
    //get method
    public T getPos (int pos) {
        return this.arr[pos];
    }
    //set method
    public void setArr (int pos,T value) {
        this.arr[pos] = value;
    }
}
public class Test {
    public static void main(String[] args) {
        //myArray1 object sets int type data
        MyArray<Integer> myArray1 = new MyArray<>();
        myArray1.setArr(0,1);
        myArray1.setArr(1,2);
        myArray1.setArr(2,3);
        //myArray2 object sets String type data
        MyArray<String> myArray2 = new MyArray<>();
        myArray2.setArr(0,"a");
        myArray2.setArr(1,"b");
        myArray2.setArr(2,"c");
        // Output each subscript element through the get method
        System.out.print(myArray1.getPos(0) + " ");
        System.out.print(myArray1.getPos(1) + " ");
        System.out.print(myArray1.getPos(2) + " ");
        System.out.print(myArray2.getPos(0) + " ");
        System.out.print(myArray2.getPos(1) + " ");
        System.out.print(myArray2.getPos(2));
    }
}

Output after running:

The above code is a manifestation of generics. If we want to set what type of data, we can set what type of wrapper class in <>. I believe that the get and set methods in the above code are not difficult for everyone to understand, but many friends may not be clear about some problems when they see this kind of code for the first time, so I will explain it:

  1. after the class name represents a placeholder, indicating that the current class is a generic class. The content in <> can be filled in arbitrarily, you can enter E, K, N and so on. Note that you should fill in the meaning of the name, such as T for type and N for number.
  2. T[] arr = new T[3]; is not feasible, because generics cannot directly new an array, but we can force type conversion such as T[] arr = (T[]) new Object[];.
  3. When instantiating a generic class, <> can only be a reference type and not a basic type. Therefore, we usually fill in the wrapper class, and the type of the value in the object must be consistent.
  4. When instantiating a generic class object, the content in the preceding <> must not be omitted, and the content in the following <> can be omitted. Such as: Array array = new Array<> ();.

3. Generic mechanism

Generics is a runtime mechanism, it will point out some errors to us at compile time, and it will also help us to perform mandatory type conversion when getting elements.

(1) Point out errors when compiling

 public static void main(String[] args) {
        MyArray<int> myArray = new MyArray<>();
    }

Error reporting:

The above error means that the <> content type in the generic class cannot be defined as int. As we know above, the generic type <> must be a reference type, so int cannot be used as an internal parameter of <>. Another example is the following code:

 public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();
        myArray.setArr(0,"abc");
    }

Error reporting:

The above code , we passed a String type of data when passing parameters to the set method, which did not conform to the properties of the object myArray. Therefore, an error occurs.

(2) Help with type conversion

class MyArray<T> {
    //Create a generic array
    public Object[] arr = new Object[3];
    //get method
    public T getPos (int pos) {
        return (T)arr[pos];
    }
    //set method
    public void setArr (int pos,T value) {
        arr[pos] = value;
    }
}
public class Test {
    public static void main(String[] args) {
        //Integer generic class
        MyArray<Integer> myArray = new MyArray<>();
        //Automatically help us with type conversion
        myArray.setArr(0,23);
        //String generic class
        MyArray<String> myArray1 = new MyArray<>();
        //Automatically help us with type conversion
        myArray1.setArr(0,"abc");
    }
}

In the above code, the second parameter value of the formal parameter list of the set method is the generic T type, but it is in the main method. When I passed parameters to the setArr method, I directly passed an integer and a string. The compiler didn’t report an error, that’s because the generic type automatically helped us perform the mandatory type conversion, that is, the T type was converted into an integer type and a string type respectively.

3.1 Erasure Mechanism

Through the above explanations, we know that generics will display errors when we compile, which will help us to force type conversion. Shows that generics are a compile-time mechanism. So what will our generics look like after running? In fact, our generic type will be erased as Object type after compilation.

class MyArray<T> {
    //Create a generic array
    T[] arr= (T[])new Object[3];
    //get method
    public T getPos (int pos) {
        return this.arr[pos];
    }
    //set method
    public void setArr (int pos,T value) {
        this.arr[pos] = value;
    }
}
public class Test {
    public static void main(String[] args) {
        //Instance an object whose generic type is Integer class
        MyArray<Integer> myArray = new MyArray<>();
        myArray. setArr(0,1);
        myArray. setArr(1,2);
        //Instance an object whose generic type is String class
        MyArray<String> myArray1 = new MyArray<>();
        myArray1.setArr(0,"abc");
        myArray1.setArr(1,"def");
    }
}

After debugging:

We can find that when we create an array, it is T[] arr= (T[])new Object[3];, but the compiler automatically programs the Object type for us in the background. Therefore, we can know that the compiler will erase the generic type and convert it to the Object type for us after running.

So, we can create a generic array like this:

class MyArray<T> {
    //Create a generic array
    public Object[] arr = new Object[3];
    //get method
    public T getPos (int pos) {
        return (T)arr[pos];
    }
    //set method
    public void setArr (int pos,T value) {
        arr[pos] = value;
    }
}

The above code initializes the array is an authentic initialization, and the original T[] arr = (T[]) new T[3]; is not very authentic, but it can also achieve the effect.

4. The upper bound of generics

There is a program that requires finding the maximum value of an array through generics, so there is the following code:

class MaxArray<T> {
    public void findMax(T[] arr) {
        T max = arr[0];
        for (int i = 0; i < arr. length; i ++ ) {
            if (max > arr[i]) {
                max = arr[i];
            }
        }
        System.out.println(max);
    }
}
public class Test {
    public static void main(String[] args) {
        MaxArray<Integer> maxArray =new MaxArray<>();
        Integer[] integers = {1,3,4,5,10,8,9,5,20};
        maxArray. findMax(integers);
    }
}

Error reporting after running:

The reason for the error in the above code is the judgment in if. When judging between basic types, arithmetic symbols can be used. When judging between basic types and reference types, we have to use the Comparable method to judge. However, we found that the above-mentioned generic type does not use the Comparable interface, so we can make the generic type inherit this interface to realize the operation, so such an operation represents the concept of the upper bound of the generic type.

4.1 Syntax of upper bounds of generics

class generic class <parameter list extems type bounds> {
        //content
    
       }

The above code is the creation of a parameter type inheriting a type boundary in a generic class, an example:

class Array<T extends Number> {
    //content
}

Only subtypes of Number are accepted as type arguments of T , so we can only use with respect to subtypes of Number, such as Integer. The String type cannot. like:

class Num<T extends Number> {
    //All three of the following will do
    Num<Integer> num1 = new Num<>();
    Num<Byte> num2 = new Num<>();
    Num<Double> num3 = new Num<>();
    // will report an error
    Num<String> num4 = new Num<>();
}

Error reporting:

The jdk-8 help manual describes the following classes as Number subclasses.

4.2 Use of generic upper bound

It is still the code in 4.1. Since we cannot use < to compare a basic type and a reference type, then we make the T type inherit the Comparable interface.

class MaxArray<T extends Comparable<T>> {
    public void findMax(T[] arr) {
        T max = arr[0];
        for (int i = 0; i < arr. length; i ++ ) {
            //Use the compareTo method in the Comparable interface
            if (max. compareTo(arr[i]) < 0) {
                max = arr[i];
            }
        }
        System.out.println(max);
    }
}
public class Test {
    public static void main(String[] args) {
        MaxArray<Integer> maxArray =new MaxArray<>();
        Integer[] integers = {1,3,4,5,10,8,9,5,20};
        maxArray. findMax(integers);
    }
}

Output after running:

In the above code, the T type in the generic class inherits the Comparable interface, so the compareTo method in the Comparable interface can be used in the if judgment. The value returned by this method is less than 0 means that the former is smaller than the latter, the return value equal to 0 means that the former is equal to the latter, and the return value greater than 0 means that the former is greater than the latter.

5. Generic method

We have learned the use of generic classes above, so generics also have methods. We can turn an ordinary method into a generic method to use, so what is the specific use of the generic method? Let me explain below:

5.1 Generic method syntax

The grammatical format of a generic method is: Method Qualification Modifier Return Value Type Method Name (Parameter List){ //Content }.

Example:

public <T> T maxNumber(T[] arr) {
    //content
        }

5.2 Use of generic methods

Find the largest number in an array:

class Array {
    public <T extends Comparable<T>> T maxNum(T[] num){
        T max = num[0];
        for (int i = 0; i < num. length; i ++ ) {
            if (max. compareTo(num[i]) < 0) {
                max = num[i];
            }
        }
        return max;
    }
}
public class Test {
    public static void main(String[] args) {
        Array array = new Array();
        Integer[] arr = {1,23,4,5,6,7};
        Integer max=array.<Integer>maxNum(arr);
        System.out.println(max);
    }
}

Output after running:

The above code shows the use of generic methods. We can see that the syntax of generic methods is relatively abstract, which is the difficulty of generic methods.

This is the end of this blog, thank you for reading.

Next Preview: ArrayList and Sequence List