Detailed explanation of generics in Java (generic syntax, erasure mechanism, upper bound of generics)


Table of contents

1. What are generics

2. Why use generics in Java?

3. Generic syntax

4. Use of generic classes

5. Generic compilation mechanism (erasure mechanism)

6. Upper bound of generics


1. What are generics

Generics are a new feature introduced in Java SE 5 that canenable classes and methods in Java to have a wider type range. In layman's terms, it allows us to specify one or more type parameters when defining classes and methods, so that these type parameters can be used directly in the code without considering the specific type. Generics can enhance code security, readability, and reusability. For example, you can use generics to implement container classes (such as ArrayList, HashMap), etc. When using generics, you need to specify the generic type when writing the code so that the type safety of the code can be checked during compilation.

2. Why use generics in Java?

General classes and methods can only use specific types: either basic types or custom classes. If you want to write code that can be applied to multiple types, this rigid restriction will be very restrictive to the code.

----- Source "Java Programming Thoughts" Introduction to Generics

Generics in Java is the ability to specify type parameters while writing code. Using generics can make code more versatile and type-safe. By using generics, programmers can write a method or class that can accept different types of parameters when instantiated. Generics parameterize data types and pass them around, which can reduce code duplication and improve code readability and maintainability.

Suppose we want to implement an array, so that elements of any data type can be stored in it, and want to store Plastic shape, but also want to store character type, but also want to store reference type What should I do? We can think of the Object class we have met before, Object class is The parent class of all classes, then can we set the array to Object class?

class MyArray {
    public Object[] array = new Object[10];
    public Object getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,Object val) {
        this.array[pos] = val;
    }
}
    public static void main(String[] args) {
        MyArray myArray = new MyArray();
        myArray.setVal(0, 10);//整形可以存放
        myArray.setVal(1, "hello");//字符串也可以存放
        String ret = myArray.getPos(1);//编译报错,原因是因为我们数组的类型是Object类型
        //但是我们这里接收的元素却是String类型
        //也就是说我们相当于进行了向下转型,所以这里会报错
        //如果我们进行强制转化就可以解决这个问题
        //String ret = (String) myArray.getPos(1);
        System.out.println(ret);
    }

 We will find that in this case, the overall syntax is actually inflexible. Although any data in the current array can be stored, in more cases, we still hope that it can only hold one data type instead of holding it at the same time. There are so many types.

Sothe main purpose of genericsis tospecify the current container and what type it should hold Object, let the compiler do the checking. At this time, you need topass the type as a parameter, pass in whatever type you need .

3. Generic syntax

After fully understanding the necessity and role of generics, let’s take a look at how to use it: In Java, the way to use generics is through in the class name or Add angle brackets after the method name, and then specify the type parameters within the angle brackets. The specific syntax is as follows:

class 泛型类名称<类型形参列表> {
    // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {
    // 这里可以使用类型参数
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
    // 可以只使用部分类型参数
}

A generic object can be instantiated through generics

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

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

Filling in type arguments can be omitted when the compiler can deduce the type arguments from the context.

MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 Integer

Generics can only accept classes,All basic data types must use wrapper classes 

4. Use of generic classes

For the array we just had, we can set it as a generic array as follows

class MyArray<T> {
    public T[] array = (T[])new Object[10];//1
    //public T[] array;
    public T getPos(int pos) {
        return this.array[pos];
    }
    public void setVal(int pos,T val) {
        this.array[pos] = val;
    }
}
    public static void main(String[] args) {
        MyArray<Integer> myArray1 = new MyArray<>();//2
        myArray1.setVal(0,10);
        myArray1.setVal(1,12);
        
        MyArray<String> myArray2 = new MyArray<>();//3
        myArray2.setVal(0,"hello");
        myArray2.setVal(1,"world");
        
        MyArray<Float> myArray3 = new MyArray<>();//4
        myArray3.setVal(0,1.23f);
        myArray3.setVal(1,3.14f);
    }

In the above code block

The <T> after the class name represents a placeholder, indicating that the current class is a generic class. Other commonly used names are:

  • E stands for Element
  • K stands for Key
  • V stands for Value
  • N stands for Number
  • T stands for Type
  • S, U, V etc... ...

Note 1: Arrays of generic types cannot be new, which means the following code is wrong.

T[] arrary = new T[5];//是不对的

In comment 2, add <Integer> after the type to specify the current type. The same applies to comments 3 and 4.

5. Generic compilation mechanism (erasure mechanism)

Java's type erasure mechanism refers to replacing the type parameter of a generic with its boundary or Object type during compilation, thus achieving Generic code does not need to know the actual type parameters when running, which means that the type parameters of generics are erased at runtime. This mechanism is to be compatible with older versions of the Java language, and can also reduce code duplication and make the code more concise.

For example,:

        Suppose there is a generic class List<T>, in which T can specify any type, but at runtime, the actual type of List<T> is List<Object>. Then, when we use List<T>, the compiler will automatically erase the type parameter T, and then replace List<T> with List<Object>, so that the Object type can be used to process elements at runtime.

        During compilation, the generic type parameter String is erased and List<String> is replaced by List<Object>. At runtime, the get method returns the Object type and needs to be cast to the String type, that is to say , we cannot get the specific value of the type parameter at runtime because the compiler has erased it.

How are generics compiled? This has been investigated as an interview question. The syntax of generics is actually very complicated and difficult to understand. We need to use its bytecode file to observe it. Use the command: javap -c View bytecode file

That is to say, during the compilation process, all T are replaced with ObjectErase mechanismThis mechanism is called:

This type erasure mechanism also brings some restrictions and challenges to development, such as the inability to obtain the specific types of generic parameters at runtime, and the creation of generic arrays is restricted. But with some tricks and design patterns, we can circumvent these limitations to a certain extent and make the code more flexible and extensible.

6. Upper bound of generics

When defining a generic class, sometimes it is necessary to impose certain constraints on the type variables passed in. This can be done through type boundaries. Syntax:

class 泛型类名称<类型形参 extends 类型边界> {
    //... ...
}

For example:

public class MyClass<T extends MyClass2> {
    // ...
}

In the above code, the upper bound of the generic type T is MyClass2, which means that when using MyClass, only MyClass2 or its subclass can be passed in as the actual type parameter of T. Doing so ensures greater flexibility and scalability when using generic types under the premise of type safety.

Suppose we have a generic classBox<T>, and we want to ensure that the type parameter T must be a class that implements the Comparable interface. We can use the generic upper bound<T extends Comparable<T>> to achieve this goal. The sample code is as follows:

public class Box<T extends Comparable<T>> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public boolean isGreaterThan(Box<T> otherBox) {
        return value.compareTo(otherBox.getValue()) > 0;
    }
}

In this example, we use<T extends Comparable<T>> to define the upper bound of the type parameter, ensuring that T must be implementedComparableInterface class. In this way, we can use the method in the isGreaterThan() method to compare the field with another The value of the object. value.compareTo()valueBox




  That’s it for this sharing. I hope my sharing can be helpful to you. I also welcome everyone’s support. Your likes are the biggest motivation for bloggers to update! If you have different opinions, you are welcome to actively discuss and exchange in the comment area, let us learn and make progress together! If you have relevant questions, you can also send a private message to the blogger. The comment area and private messages will be carefully checked. See you next time

Guess you like

Origin blog.csdn.net/m0_69519887/article/details/134625141