What is Generic in Java Core Technology

What is generic? We must have seen T, K, V, etc. in the engineering code, this is generic, then let’s see what the official website says @泛型(Generic)

When you take an element out of a  Collection , you must cast it to the type of element that is stored in the collection. Besides being inconvenient, this is unsafe. The compiler does not check that your cast is the same as the collection's type, so the cast can fail at run time.
Generics provides a way for you to communicate the type of a collection to the compiler, so that it can be checked. Once the compiler knows the element type of the collection, the compiler can check that you have used the collection consistently and can insert the correct casts on values being taken out of the collection.

What does this official statement mean: When you take out an element from a collection, you must cast it to the type of the element stored in the collection. Apart from inconvenience, this is not safe. The compiler does not check whether the cast is the same type as the collection, so the cast may fail at runtime.
Generics provide a way to pass the type of a collection to the compiler so that it can be checked. Once the compiler knows the element type of the collection, the compiler can check whether you are using the collection consistently, and can insert the correct coercion for the values ​​taken from the collection.

What does this official obscure language mean? In short, it is one sentence: Generic programming means that the written code can be reused by many different types of objects.

Java Generic (Generic) is a new feature introduced in J2SE1.5. Its essence is a parameterized type , which means that the data type being manipulated is designated as a parameter (type parameter). This parameter type can be used in classes, In the creation of interfaces and methods, they are called generic classes, generic interfaces, and generic methods, respectively.

What is the goal of learning after understanding the generic concept?

1. Understand the rules and type erasure of generics.
2. Understand the type and limit the wildcards of two generics.
3. Understand the way to use generics in API design (custom generic classes, generic interfaces, generic methods)
4. Master the use and principles of generics.
5. Master the application of generics in middleware or open source frameworks

Let's discuss these issues one by one

Generic rules

Before JDK5.0, there was no concept of generics, so how did you write the code at that time?

import java.io.File;
import java.util.ArrayList;
/**
 * @author mac
 * @date 2020/10/31-11:05
 */
public static void main(String[] args) {
   ArrayList arrayList = new ArrayList();
   arrayList.add(1);
   arrayList.add("a");
   // 这里没有错误检查。可以向数组列表中添加任何类的对象
   arrayList.add(new File("/"));
   // 对于这个调用,如果将get的结果强制类型转换为String类型,就会产生一个错误
   // Exception in thread "main" java.lang.ClassCastException: java.io.File cannot be cast to java.lang.String
   String file = (String) arrayList.get(2);
   System.out.println(file);
}

Before JDK5.0, if the return value of a method is Object, and a collection contains Object, then the return value or element can only be obtained by force conversion. If there is a type conversion error, the compiler cannot detect it, which greatly increases Probability of program error!

public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<String>();
    arrayList.add("a");
    String s = (String) arrayList.get(0);
    // 6、7行代码编译不通过,不会导致运行后才发生错误
    arrayList.add(1);
    arrayList.add(new File("/"));
    String file = (String) arrayList.get(2);
}

It can be seen from the use of generics that generics are a type of constraint . In short, generics make types (classes and interfaces) as parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide you with a way to reuse the same code with different inputs. The difference is that the input of a formal parameter is a value, while the input of a type parameter is a type.
JDK checks the types at compile time and provides type safety at compile time. It adds compile-time type safety to the collection framework and eliminates heavy type conversion work.

public class Person {
    int gender;
}
public class Driver extends Person {
    String name;
    int skilllevel;
}
public static void main(String[] ars) {
   List<Person> ls = new Arraylist<>();
   //这里会不会编译报错?
   List<Driver> list = ls;
}

However, the application of generics is not without pits, such as the above code, you can see that the compilation error, this is a generic rule that does not allow subtyping-assuming it is allowed, can it be changed to the following conditions, all in the JDK the class is a subclass of Object, if allowed to subclass
type of, then ls can not be stored in any type of elements yet, this generic type constraints and completely contrary, so check JDK on generics There are very strict constraints.

In order to prevent the confusion of subtyping, generics have the concept of wildcards

Wildcards in generics

Unbounded wildcard

In the above generic example, we all specify a specific type, at least Object. Suppose there is a scenario. You don’t know what this type is. It can be Object or Person. This kind of scenario requires the use of wildcards, as
shown below, usually represented by a ?.

public void addAll(Collection<?> col){
    ...
}

Upper bound wildcard

Based on the above scenario, adding that I want to restrict the subclass of this type to Person, as long as it is a subclass of Person, if the generic is written as <Person>, then it can only be forced as shown below, then the generic is lost The meaning is back to the original starting point. What to do at this time?

List<Person> list = new ArrayList<>();
list.add(new Driver());
Person person = list.get(0);
Driver driver = (Driver) person; // 针对这种情况于是有了有界通配符的推出。

// 在泛型中指定上边界的叫上界通配符<? extends XXX>
public void count(Collection<? extends Person> persons) {
}
public void count2(Collection<Person> persons) {
}
public void testCount() {
    List<Driver> drivers = new ArrayList<>();
    // 符合上界通配符规则,编译不报错
    count(drivers);
    // 违反子类型化原则,编译报错
    count2(drivers);
    // 符合下界通配符原则,编译不报错
    List<Person> persons = new ArrayList<>();
}

Nether wildcard

The principle is the same as the upper bound wildcard. The lower bound wildcard restricts the unknown type to a specific type or the supertype of that type. The lower bound wildcard uses a wildcard ('?') to indicate, followed by the super keyword, followed by the lower bound: <? super A>.

public void count3(Collection<? super Driver> drivers) {
}
public void testCount() {
    //符合下界通配符原则,编译不报错
    List<Person> persons = new ArrayList<>();
    count3(persons);
}

General methods and type inference

General method

General method means that the type of method parameter is generic, both static and non-static methods can be used, and the construction method can also be used. We look at the specific use

/**
 * @author mac
 * @date 2020/10/31-12:24
 * 定义一个bean类
 */
public class Pair<K, V> {
    private K key; private V value;
    public Pair(K key, V value) {
        this.key = key; this.value = value;
    }
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}

public class Util {
    // <K, V>通用方法入参类型
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
                p1.getValue().equals(p2.getValue()); // 使用Object中equals判断是否相等
    }
}

    public static void main(String[] args) {
        Pair<Integer, String> p1 = new Pair<>(1, "apple");
        Pair<Integer, String> p2 = new Pair<>(2, "pear");
        // JDK8之后可以这么写boolean same = Util.compare(p1, p2);
        boolean same = Util.<Integer, String>compare(p1, p2); 
        System.out.println(same); // false
    }

Type inference

Type inference is the ability of the Java compiler to look at each method call and corresponding declaration to determine the type parameters that make the call applicable. The inference algorithm determines the type of parameter, and the type (if any) that determines whether the result has been assigned or returned. Finally, the inference algorithm tries to find the most specific type to use with all parameters.

/**
 * @author macfmc
 * @date 2020/10/31-12:39
 */
public class Box<U> {
    U u;
    public U get() { return u; }
    public void set(U u) { this.u = u; }
}

public class BoxDemo {
    public static <U> void addBox(U u, List<Box<U>> boxes) {
        Box<U> box = new Box<U>();
        box.set(u);
        boxes.add(box);
    }
    public static <U> void outputBoxes(List<Box<U>> boxes) {
        int counter = 0;
        for (Box<U> box : boxes) {
            U boxContents = box.get();
            System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]");
            counter++;
        }
    }
    public static void main(String[] args) {
        ArrayList<Box<Integer>> listOfIntegerBoxes = new ArrayList<>();
        // JDK8可以使用 BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes);
        BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
        BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
        BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
        BoxDemo.outputBoxes(listOfIntegerBoxes);
    }
}
// 结果
// Box #0 contains [10]
// Box #1 contains [20]
// Box #2 contains [30]

Then we understand the concept, principle and use of generics. How is generics resolved in JVM?

Generic erasure

We look at the following two pieces of code

public class Node {
    private Object obj;
    public Object get() { return obj; }
    public void set(Object obj) { this.obj = obj; }
    public static void main(String[] argv) {
        Student stu = new Student();
        Node node = new Node();
        node.set(stu);
        Student stu2 = (Student) node.get();
    }
}

public class Node<T> {
    private T obj;
    public T get() { return obj; }
    public void set(T obj) { this.obj = obj; }
    public static void main(String[] argv) {
        Student stu = new Student();
        Node<Student> node = new Node<>();
        node.set(stu);
        Student stu2 = node.get();
    }
}

We compile them separately and view the .class bytecode file

    public Node();
        Code:
           0: aload_0
           1: invokespecial #1       // Method java/lang/Object."<init>": ()V
           4: return
    public java.lang.Object get();
        Code:
           0: aload_0
           1: getfield    #2        // Field obj:Ljava/lang/Object;
           4: areturn
    public void set(java.lang.Object);
        Code:
           0: aload_0
           1: aload_1
           2: putfield    #2        // Field obj:Ljava/lang/Object;
           5: return

    public Node();
        Code:
           0: aload_0
           1: invokespecial #1       // Method java/lang/Object."<init>": ()V
           4: return
    public java.lang.Object get();
        Code:
           0: aload_0
           1: getfield    #2        // Field obj:Ljava/lang/Object;
           4: areturn
    public void set(java.lang.Object);
        Code:
           0: aload_0
           1: aload_1
           2: putfield    #2        // Field obj:Ljava/lang/Object;
           5: return

It can be seen that generics are used to pass type information to specific generic codes when they are used. After compilation, the generated .class file is exactly the same as the original code, as if the type information passed over has been erased.

Type erasure mainly includes: 1. Elimination of general types: In the type erasure process, the Java compiler will erase all type parameters. If the type parameters are bounded, each parameter will be replaced with the first one. Boundary; if the type parameter is unbounded, replace it with Object. 2. The erasure of general methods: The java compiler will also remove the type parameters in the general method parameters

Type removal problem

Bridging method

Type removal can cause unexpected problems in some cases. In order to solve this problem, the java compiler adopts a bridging method. First look at an official case

// 泛型擦除前
public class Node<T> {
    public T data;
    public Node(T data) { this.data = data; }
    public void setData(T data) { this.data = data; }
}
public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) { super.setData(data); }
}

// 泛型檫除后
public class Node {
    public Object data;
    public Node(Object data) { this.data = data; }
    public void setData(Object data) { this.data = data; }
}
public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }
    public void setData(Integer data) { super.setData(data); }
}

// 但是编译器会产生桥接方法
public class MyNode extends Node {
    public MyNode(Object data) { super(data); }
    // Bridge method generated by the compiler
    // 编译器产生的桥接方法
    public void setData(Object data) { setData((Integer) data); }
    public void setData(Integer data) { super.setData(data); }
}

Heap pollution

Heap pollution will not report an error at compile time, only a warning that may cause heap pollution at compile time. At runtime, if heap pollution occurs, a type conversion exception will be thrown. Heap pollution (heap pollution), refers to the possibility of heap pollution when a non-generic object is assigned to a variable with a generic type.

public static void main(String[] args) {
    List lists = new ArrayList<Integer>();
    lists.add(1);
    List<String> list = lists;
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    String str = list.get(0);
    System.out.println(str);
}

Type restrictions

The fact of Java generics conversion:
there are no generics in the virtual machine, only ordinary classes and methods.
All type parameters are replaced with their qualified types.
The bridging method is synthesized to maintain polymorphism.
To maintain type safety, insert coercive conversions when necessary.

jdk defines 7 types of generic usage restrictions:
1. You cannot use simple types to instantiate generic instances.
2. You cannot create type parameter instances directly.
3. You cannot declare static attributes as generic type parameters.
4. You cannot use parameterized types. Use cast or instanceof
5. Cannot create array generic type
6. Cannot create, catch, throw parameterized type objects
7. Can not have two methods of the same primitive type in the overloaded method

1. You cannot use simple types to instantiate generic instances

class Pair<K, V> {
    private K key;
    private V value;
    public Pair(K key, V value) { this.key = key; this.value = value; }
    public static void main(String[] args) {
        // 编译时会报错,因为 int、char 属于基础类型,不能用于实例化泛型对象
        Pair<int, char> p = new Pair(8, 'a');
        // 编译不会报错
        Pair<Integer, String> p2 = new Pair<>(8, "a");
    }
}

2. Cannot directly create type parameter instances

    public static <E> void append(List<E> list) {
        E elem = new E(); // compile-time error 编译报错
        list.add(elem);
    }
    //作为解决办法,可以通过反射来创建
    public static <E> void append(List<E> list, Class<E> cls) throws Exception {
        E elem = cls.newInstance(); // OK
        list.add(elem);
    }

3. Cannot declare static properties as generic type parameters

/**
 *  类的静态字段是该类所有非静态对象所共享的,如果可以,那么在有多种类型的情况下,os到底应该是哪种类型呢?
 *  下面这种情况,os到底应该是Smartphone还是Pager还是TablePC呢
 *  MobileDevice<Smartphone> phone = new MobileDevice<>();
 *  MobileDevice<Pager> pager = new MobileDevice<>();
 *  MobileDevice<TabletPC> pc = new MobileDevice<>();
 */
public class MobileDevice<T> {
    //非法
    private static T os;
}

4. Cannot use cast or instanceof for parameterized types

    public static <E> void rtti(List<E> list) {
        // 编译期会提示异常——因为 java 编译器在编译器会做类型檫除,于是在运行期就无法校验参数的类型
        if (list instanceof ArrayList<Integer>) { }
    }

    // 解决方法可以通过无界通配符来进行参数化
    public static void rtti(List<?> list) {
        // 编译不会报错
        if (list instanceof ArrayList<?>) { }
    }

5. Cannot create array generics

        // 编译器报错
        List<Integer>[] arrayOfLists = new List<Integer>[2];
        // 用一个通用列表尝试同样的事情,会出现一个问题
        Object[] strings = new String[2];
        strings[0] = "hi"; // OK
        strings[1] = 100; // An ArrayStoreException is thrown.
        Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed 缺少数组维
        stringLists[0] = new ArrayList<String>(); // OK
        // java.lang.ArrayStoreException: java.util.ArrayList but the runtime can't detect it.
        stringLists[1] = new ArrayList<Integer>();

6. Cannot create, catch, throw parameterized type objects

// 泛型类不能直接或间接的扩展 Throwable 类,以下情况会报编译错
// Extends Throwable indirectly
class MathException<T> extends Exception { } // compile-time error
// Extends Throwable directly
class QueueFullException<T> extends Throwable { } // compile-time error

// 捕捉泛型异常也是不允许的
    public static <T extends Exception, J> void execute(List<J> jobs) {
        try {
            for (J job : jobs) { }
        } catch (T e) { // compile-time error
        }
    }

// 但是可以在字句中使用类型参数
class Parser<T extends Exception> {
    public void parse(File file) throws T { }
}

7. There cannot be two methods of the same primitive type in the overloaded method

// 因为类型檫除后,两个方法将具有相同的签名,重载将共享相同的类文件表示形式,并且将生成编译时错误。
public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

to sum up:

Generic code evolution and the use of generics and Why is the foundation will be used, generic use three kinds of rules and use wildcards and use common methods and type inference is to be understood Advanced , and type erasure erase type of problem and the type of use restriction is considered a supplement familiar, can understand the generic design approach commonly used API in JDK source code is considered proficient.

 

 

Guess you like

Origin blog.csdn.net/FMC_WBL/article/details/109139475