Exploration of generics

Keywords: Java

-The essence of generics is type parameterization.

-It is allowed to use type parameters when defining classes, interfaces and methods, and specify specific types when using.

-All places where this generic parameter is used are unified to ensure type consistency. If no specific type is specified, the default is object type. Generics are added to all classes in the collection system, and generics are mainly used in collections.

·Generics are defined on classes

Specify the corresponding type when creating. When using, you can use the corresponding type without forced conversion

public static void main(String[] args) {
    // Create an ObjectTool object and specify that the element type is String
    ObjectTool<String> stringTool = new ObjectTool<>();
    stringTool.setObj("muse");
    System.out.println(stringTool.getObj());
    // Create an ObjectTool object and specify that the element type is Integer
    ObjectTool<Integer> integerTool = new ObjectTool<>();
    // integerTool.setObj("aa"); //  Compilation error
    integerTool.setObj(10);
    System.out.println(integerTool.getObj());
}

/**
 * Build a tool class that can store objects of any type
 */
static class ObjectTool<T> {
    private T obj;
    public T getObj() {
        return obj;
    }
    public void setObj(T obj) {
        this.obj = obj;
    }
}

·Generics are defined on methods

When you call a method, you specify the corresponding type. What type of parameters you pass in will be converted to what type.

public static void main(String[] args) {
    //create object
    ObjectTool tool = new ObjectTool();
    // When calling a method, T is the type of the parameter passed in
    tool.show("hello");
    tool.show(12);
    tool.show(12.5f);
}
static class ObjectTool {
    //Defining generic methods
    public <T> void show(T t) {
        System.out.println(t);
    }
}

·Generics are defined on interfaces

When subclasses implement interfaces, they can specify generics or not, as shown below:

/**
 * Define generics on interfaces
 */
interface Inter<T> {
    void show(T t);
}

/**
 * Implementation 1: subclasses specify the type parameter variables of generic classes
 * In this case, if this subclass is used, an error will be reported when the non String type is passed in
 */
class InterImpl1 implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);
    }
}

/**
 * Implementation 2: the subclass does not specify the type parameter variable of the generic class, and the implementation class should also define the type of T
 */
class InterImpl2<T> implements Inter<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

·Differences between wildcards: object, t, t, and?

Similarities: object T? Can refer to any type, with different usage scenarios

difference:

1. When using an object, if you want to get the original type, you need to perform a forced conversion.

2. T: no forced rotation is required for use. It can be used in the set. object cannot be used in the set,

         For example, if list < string > is passed in list < Object >, an error will be reported during compilation. If list < T > and list <? > No problem

        T is also commonly used for general distribution

public <T> void test3(List<T> list) {
    list.add(list.get(0));
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
    }
    System.out.println();
}

3.?: It is defined that it cannot be used directly and will compile and report errors. T will not compile and report errors. It can be used directly, for example:

public void test(List<?> list) {
    // list.add(list.get(0)); //  Compilation error
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
    }
    System.out.println();
}

·Upper and lower bounds of generics

upper limit:

        Format: type name <? Extensions class > object name

        Meaning: only this type and its subclasses can be accepted

Lower limit:

        Format: type name <? Super class > object name

        Meaning: only this type and its parent class can be accepted

·PECS(Producer Extends Consumer Super)

<? Extensions T > storage is not allowed and can only be fetched out

<? Super T > can be stored, but fetching out can only be received using object

Extensions knows that the storage type must be a subclass of T, so when reading, you can use t or T's parent class to receive, but in order to ensure the safety of the type, it is forbidden to write any data into it;

super knows that the storage type must be the parent of T, so when writing, you can write T and its subclasses, but when reading, you must use Object to receive.

/**
 * [[read]
 * If you want to read data of type T from the set and cannot write, you can use the? extends wildcard; (Producer Extends)
 * List<? extends Animal> animals What can be stored in it?
 * Animals, dogs, cats, pigs, chickens... As long as they are animals, they may be stored in animals.
 */
public static void testPECSextends() {
    List<Dog> dogs = Lists.newArrayList(new Dog());
    List<? extends Animal> animals = dogs;
    /**
     * animals Is a List of subclasses of Animal. Since Dog is a subclass of Animal, it is legal to assign dogs to animals, but the compiler will prevent new Cat() from being added to animals.
     * Because the compiler only knows that animals is a List of a subclass of Animal, but does not know which subclass it is. For type safety, it has to prevent adding any subclasses to it. Then can you join
     * new Animal()And? Unfortunately, not either. In fact, can't reach one? Write any value in the data structure of extends.
     */
    // animals.add(new Cat()); //  Compilation failed
    // animals.add(new Animal()); //  Compilation failed
    // animals.add(new Dog()); //  Compilation failed
    /**
     * Because the compiler knows that it is always a subtype of Animal, but it does not know which subclass it is. Therefore, we can always read out the Animal object from it:
     */
    Animal animal = animals.get(0);
    Object obj = animals.get(0);
    // Dog dog = animals.get(0); //  Compilation failed
}
/**
 * [[write]
 * If you want to write data of type T from the collection and do not need to read, you can use? super wildcard; (Consumer Super)
 * <p>
 * If you want to save and retrieve, do not use any wildcards.
 */
public static void testPECSsuper() {
    List<Animal> animals = Lists.newArrayList();
    List<? super Dog> dogs = animals;
    /**
     * Here, animals is a List of Animal superclasses (superclasses). Similarly, for the sake of type safety, we can add Dog objects or any subclass (such as WhiteDog) objects,
     * However, because the compiler does not know which superclass of Dog is the content of List, it is not allowed to add any specific supertype.
     */
    dogs.add(new Dog());
    dogs.add(new WhiteDog());
    // dogs.add(new Animal()); //  Compilation failed
    // dogs.add(new Cat()); //  Compilation failed
    // dogs.add(new Object()); //  Compilation failed
    /**
     * When we read, the compiler can only return Object without knowing what type it is, because Object is the ultimate ancestor of any Java class.
     */
    Object obj = dogs.get(0);
    // Dog dog = dogs.get(0); //  Compilation failed
    // Animal animal = dogs.get(0); //  Compilation failed
}

·Type erasure and bridging method

Before the emergence of generics, the jdk compiler did not consider the occurrence of generics. Therefore, for compatibility, downward compatibility is required. The methods used are type erasure and automatic generation of bridge methods.

Generics are provided for the javac compiler to use. They are used to limit the input type of the collection and let the compiler insert illegal data into the collection at the source code level. However, after the compiler compiles the java program with generics, the generated class file will no longer contain generics information, so that the running efficiency of the program will not be affected. This process is called "erasure".

Since the type is erased, the compiler automatically generates bridging methods in order to maintain polymorphism.

Normal generic usage:

public class AAA<T data>{
    public T t;
    public void setT(T t){
        this.t = data;
    }
}

//Specifies that the generic type is String
public class aaa extends AAA<String s>{
    @override
    public void setT(String s){
        this.t = s;
    }
}

After downward compatibility type erasure and automatic generation of bridging methods:

//Type erasure is to erase all generics based on the above normal generics, and change the generic T in the method to Object, as shown below
public class AAA{
    public T t;
    public void setT(Object t){
        this.t = data;
    }
}


//Because of type erasure, setT(String s) in aaa will no longer override setT(Object t) in aaa
//Automatically generate the bridging method at this point, and turn the object into String after the bridging method and call the original setT(String s).
public class aaa extends AAA{
    public void setT(String s){
        this.t = s;
    }
    @override
    public void setT(Object t){
        setT((String)t);
    }
}

Posted by VLE79E on Wed, 29 Sep 2021 10:05:31 -0700