What are generics?

  • Java generics is a parameterized type feature introduced in JDK5

  • Generics is a new feature introduced in JDK5. Generics provides a compile-time type-safety detection mechanism that allows programmers to detect illegal types at compile time. The nature of generics is parameter types, that is, the data type being operated on is specified as a parameter generic that does not exist in the JVM virtual machine

What are parameterized types?

Pass the type as an argument

Data types can only be reference types (a side effect of generics)

Here’s an example:

  • The “T” in the List is called the type parameter

  • The “Person” in the List is called the actual type parameter

  • “List” becomes a generic type altogether

  • The whole “List” is called the ParameterizedType

Why generics and the benefits of using generics?

  1. More robust code (no classcastExceptions at runtime as long as there are no warnings at compile time)

    List List = new ArrayList(); list.add(“hello”); Integer s = (String) list.get(0); // Causes a ClassCastException to be thrown.

    List List = new ArrayList(); list.add(“hello”); Integer s = (String) list.get(0); // The compiler will fail

  2. Code is more concise, no strong rotation

    List = new ArrayList(); list.add(“hello”); String s = (String) list.get(0);

    List = new ArrayList(); list.add(“hello”); String s = list.get(0); // no cast

  3. Code is more flexible and reusable

    Void void sort(@nullable Comparator<? super E> c) { throw new RuntimeException(“Stub!” ); }

How does Java handle generics

  1. The class information obtained through the runtime is exactly the same. The generic type is erased, leaving only the original type, as shown below, leaving only the ArrayList type.

    ArrayList strings = new ArrayList<>(); ArrayList integers = new ArrayList<>(); System.out.println(strings.getClass() == integers.getClass()); //result true

Generic erasure

  • Functionality: Ensures that generics do not appear at run time

  • Where type elimination is applied: The compiler replaces all type parameters in a generic type with their upper (lower) limits, or, if no restrictions are placed on type parameters, with type Object. Therefore, the compiled bytecode contains only regular classes, interfaces, and methods. Type conversions are inserted when necessary to preserve type safety. Bridge methods are generated to preserve polymorphism as generics are extended

  • When compiling a class that extends a parameterized class, or an interface that implements a parameterized interface, the compiler may therefore create a composite method called a Bridge method. It is part of the type erasure process

A simple example of how Java handles generics

  1. Define a generic interface

    public interface Box { void set(T t); T get(); }

  2. using

    javac

    Command to obtain the bytecode file

    public interface Box { void set(T var1); T get(); }

  3. using

    javap -c

    Command to view the generated bytecode, our T becomes type Object.

    public abstract interface test3/Box { public abstract set(Ljava/lang/Object;) V public abstract get()Ljava/lang/Object; }

  4. We define a class to implement this interface

    public class ConditionalBox implements Box {

    private List<T> items = new ArrayList<T>(10); public ConditionalBox() { } @Override public void set(T t) { items.add(t); } @Override public T get() { int index = items.size() - 1; if (index >= 0) { return items.get(index); } else { return null; }}Copy the code

    }

5. Run the javap -c command to view the generated bytecode. You can see that our set and get are still constructors, and T becomes Object.

public class test3/ConditionalBox implements test3/Box { // compiled from: ConditionalBox.java // access flags 0x2 // signature Ljava/util/List<TT; >; // declaration: items extends java.util.List<T> private Ljava/util/List; items // access flags 0x1 public <init>()V L0 LINENUMBER 10 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V L1 LINENUMBER 8 L1 ALOAD 0 NEW java/util/ArrayList DUP BIPUSH 10 INVOKESPECIAL java/util/ArrayList.<init> (I)V PUTFIELD test3/ConditionalBox.items : Ljava/util/List; L2 LINENUMBER 11 L2 RETURN L3 LOCALVARIABLE this Ltest3/ConditionalBox; L0 L3 0 // signature Ltest3/ConditionalBox<TT; >; // declaration: this extends test3.ConditionalBox<T> MAXSTACK = 4 MAXLOCALS = 1 // access flags 0x1 // signature (TT;) V // declaration: void set(T) public set(Ljava/lang/Object;) V L0 LINENUMBER 15 L0 ALOAD 0 GETFIELD test3/ConditionalBox.items : Ljava/util/List; ALOAD 1 INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;) Z (itf) POP L1 LINENUMBER 16 L1 RETURN L2 LOCALVARIABLE this Ltest3/ConditionalBox; L0 L2 0 // signature Ltest3/ConditionalBox<TT; >; // declaration: this extends test3.ConditionalBox<T> LOCALVARIABLE t Ljava/lang/Object; L0 L2 1 // signature TT; // declaration: t extends T MAXSTACK = 2 MAXLOCALS = 2 // access flags 0x1 // signature ()TT; // declaration: T get() public get()Ljava/lang/Object; L0 LINENUMBER 20 L0 ALOAD 0 GETFIELD test3/ConditionalBox.items : Ljava/util/List; INVOKEINTERFACE java/util/List.size ()I (itf) ICONST_1 ISUB ISTORE 1 L1 LINENUMBER 21 L1 ILOAD 1 IFLT L2 L3 LINENUMBER 22 L3 ALOAD 0 GETFIELD test3/ConditionalBox.items : Ljava/util/List; ILOAD 1 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf) ARETURN L2 LINENUMBER 24 L2 FRAME APPEND [I] ACONST_NULL ARETURN L4 LOCALVARIABLE this Ltest3/ConditionalBox; L0 L4 0 // signature Ltest3/ConditionalBox<TT; >; // declaration: this extends test3.ConditionalBox<T> LOCALVARIABLE index I L1 L4 1 MAXSTACK = 2 MAXLOCALS = 2 }Copy the code
  1. IntelligentBox<T extends Comparable> implements the Box interface. The following code

    public class IntelligentBox<T extends Comparable> implements Box {

    private List<T> items = new ArrayList<T>(10); @Override public void set(T t) { items.add(t); Collections.sort(items); } @Override public T get() { int index = items.size() - 1; if (index >= 0) { return items.get(index); } else { return null; }}Copy the code

    }

  2. Use the Javap -c command to view the bytecode generated by IntelligentBox

    public class test3/IntelligentBox implements test3/Box {

    // compiled from: IntelligentBox.java

    // access flags 0x2 // signature Ljava/util/List<TT; >; // declaration: items extends java.util.List private Ljava/util/List; items

    // access flags 0x1 public ()V L0 LINENUMBER 7 L0 ALOAD 0 INVOKESPECIAL java/lang/Object. ()V L1 LINENUMBER 9 L1 ALOAD 0 NEW java/util/ArrayList DUP BIPUSH 10 INVOKESPECIAL java/util/ArrayList. (I)V PUTFIELD test3/IntelligentBox.items : Ljava/util/List; RETURN L2 LOCALVARIABLE this Ltest3/IntelligentBox; L0 L2 0 // signature Ltest3/IntelligentBox<TT; >; // declaration: this extends test3.IntelligentBox MAXSTACK = 4 MAXLOCALS = 1

    // access flags 0x1 // signature (TT;) V // declaration: void set(T) public set(Ljava/lang/Comparable;) V L0 LINENUMBER 13 L0 ALOAD 0 GETFIELD test3/IntelligentBox.items : Ljava/util/List; ALOAD 1 INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;) Z (itf) POP L1 LINENUMBER 14 L1 ALOAD 0 GETFIELD test3/IntelligentBox.items : Ljava/util/List; INVOKESTATIC java/util/Collections.sort (Ljava/util/List;) V L2 LINENUMBER 15 L2 RETURN L3 LOCALVARIABLE this Ltest3/IntelligentBox; L0 L3 0 // signature Ltest3/IntelligentBox<TT; >; // declaration: this extends test3.IntelligentBox LOCALVARIABLE t Ljava/lang/Comparable; L0 L3 1 // signature TT; // declaration: t extends T MAXSTACK = 2 MAXLOCALS = 2

    // access flags 0x1 // signature ()TT; // declaration: T get() public get()Ljava/lang/Comparable; L0 LINENUMBER 19 L0 ALOAD 0 GETFIELD test3/IntelligentBox.items : Ljava/util/List; INVOKEINTERFACE java/util/List.size ()I (itf) ICONST_1 ISUB ISTORE 1 L1 LINENUMBER 20 L1 ILOAD 1 IFLT L2 L3 LINENUMBER 21 L3 ALOAD 0 GETFIELD test3/IntelligentBox.items : Ljava/util/List; ILOAD 1 INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf) CHECKCAST java/lang/Comparable ARETURN L2 LINENUMBER 23 L2 FRAME APPEND [I] ACONST_NULL ARETURN L4 LOCALVARIABLE this Ltest3/IntelligentBox; L0 L4 0 // signature Ltest3/IntelligentBox<TT; >; // declaration: this extends test3.IntelligentBox LOCALVARIABLE index I L1 L4 1 MAXSTACK = 2 MAXLOCALS = 2

    // access flags 0x1041 public synthetic bridge get()Ljava/lang/Object; L0 LINENUMBER 7 L0 ALOAD 0 INVOKEVIRTUAL test3/IntelligentBox.get ()Ljava/lang/Comparable; ARETURN L1 LOCALVARIABLE this Ltest3/IntelligentBox; L0 L1 0 // signature Ltest3/IntelligentBox<TT; >; // declaration: this extends test3.IntelligentBox MAXSTACK = 1 MAXLOCALS = 1

    // access flags 0x1041 public synthetic bridge set(Ljava/lang/Object;) V L0 LINENUMBER 7 L0 ALOAD 0 ALOAD 1 CHECKCAST java/lang/Comparable INVOKEVIRTUAL test3/IntelligentBox.set (Ljava/lang/Comparable;) V RETURN L1 LOCALVARIABLE this Ltest3/IntelligentBox; L0 L1 0 // signature Ltest3/IntelligentBox<TT; >; // declaration: this extends test3.IntelligentBox MAXSTACK = 2 MAXLOCALS = 2 }

  • As you can see, there are two places to cast, the GET and set methods.

    INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object; (itf) CHECKCAST java/lang/Comparable

    CHECKCAST java/lang/Comparable INVOKEVIRTUAL test3/IntelligentBox.set (Ljava/lang/Comparable;) V

  • You can see that there are two bridge methods

    public synthetic bridge set(Ljava/lang/Object;) V public synthetic bridge get()Ljava/lang/Object;

  • The process of representing IntelligentBox in pseudocode

    public class test3/IntelligentBox implements test3/Box { public void set(Comparable t) { /* compiled code */ }

    public Comparable get() { /* compiled code */ }
    
    @Overide
    public synthetic bridge get(){
    }
    
    @Overide
    public synthetic bridge set(Object t){
        set((Comparable)t)
    }
    Copy the code

    }

Remnants of generic erasure

Take a look at Box’s bytecode file, box.class, and view the generated bytecode

public interface Box<T> {
    void set(T var1);
    T get();
}
Copy the code
  • Question: Does the type become Object after erasure? Why is the bytecode file still of type T? In fact, what you see here is actually a signature, but still retain the defined format, which is good for analyzing bytecode. It’s not really erased, it’s stored in the class’s constant pool.

    / * *

    • ParameterizedType
    • Specific generic types, such as Map
      ,>
    • There are the following methods:
    • Type getRawType(): returns the object carrying the generic information, such as Map

      the object carrying the generic information is Map
      ,>
    • Type[] getActualTypeArguments(): Returns a list of actual generic types, such as Map
      ,>
    • Type getOwnerType(): returns whose member it is.

    */ public class TestType { Map<String, String> map; Public static void main(String[] args) throws Exception {Field f = TestType.class.getDeclaredField(“map”); System.out.println(f.getGenericType()); // java.util.Map<java.lang.String, java.lang.String> System.out.println(f.getGenericType() instanceof ParameterizedType); // true ParameterizedType pType = (ParameterizedType) f.getGenericType(); System.out.println(pType.getRawType()); // interface java.util.Map for (Type type : pType.getActualTypeArguments()) { System.out.println(type); Class java.lang.string} system.out.println (ptype.getownerType ()); // null } }

  • In response to the problem of how to get parameterized types from generic classes, the Java Virtual Machine specification introduces new attributes such as Signature and LocalVariableTypeTable to record generic information. Therefore, the so-called generic type erasure is only the bytecode erasure in the code attribute of the method. This information is stored in the class bytecode constant pool. Code calls that use generics generate a signature field. Signature specifies the address of the constant in the constant pool. So we also know that we now know that generic erasure is not all erasure

conclusion

  • QUESTION: How does Java generics work? What is the generic erase mechanism?

  • ANSWER:Java generics is a new feature introduced in JDK5. For backward compatibility, virtual machines actually do not support generics, so Java implements a pseudo-generics mechanism, which means that Java erases all generics information at compile time, so that Java does not need to generate new types to bytecode. All generic types are ultimately primitive types, and there is no generic information at all in the Java runtime.

  • QUESTION: How exactly does the Java compiler erase generics

  • ANSWER:

    1. Check the generic type to get the target type

    2. If the type variable of a generic type is not qualified (), Object is used as the primitive type. If there are more than one qualification (T extends XClass1&XClass2), the first bound XClass1 is used as the primitive class

    3. Type conversions are inserted when necessary to preserve type safety

    4. Bridge methods are generated to maintain polymorphism while scaling

Effects of using generics and generic erasure (side effects)

Generic type variables cannot use primitive data types

For example, there is no ArrayList, only ArrayList. When the type is erased, the type variable (T) in the original class of ArrayList is replaced with Object, but Object cannot hold an int value

Int ArrayList<int> ints = new ArrayList<int>(); ArrayList<Integer> integerArrayList = new ArrayList<Integer>();Copy the code

The instanceof operator cannot be used

Because after erasing, the ArrayList is left with the original type, and the generic String information is gone, instanceof can’t be used

ArrayList<String> stringArrayList = new ArrayList<String>(); / / use ArrayList <? > if (stringArrayList instanceof ArrayList<? If (ArrayList instanceof ArrayList<String>){} if (ArrayList instanceof ArrayList<String>){}Copy the code

Problems with generics in static methods and classes

Since the instantiation of a generic parameter in a generic class is specified when an object of a generic type (such as an ArrayList) is defined, and a static member is not called using an object. None of the objects are created. How do I determine what the generic parameter is

Public static T a; public static T a; Public static T test1(T T) {} public static <T> T test2(T T) {return T; }Copy the code

Method conflicts in generic types

Because the two equals methods are the same after erasing

@override public Boolean equals(T obj) {return super.equals(obj); } @Override public boolean equals(Object obj) { return super.equals(obj); }Copy the code

Unable to create generic instance

Because the type is uncertain

Class Test02 {// Cannot create an instance of a type parameter. Public static <E> void append(List<E> List) {// 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); }}Copy the code

There are no generic arrays

Because arrays are covariant, erasing doesn’t satisfy the covariant principle of arrays

// Plate<Apple>[] applePlates = new Plate<Apple>[10]; // not allowed // T[] arr = new T[10]; // Not allowed Apple[] apples = new Apple[10]; Fruit[] fruits = new Fruit[10]; System.out.println(apples.getClass()); //class [Lcom.zero.genericsdemo02.demo02.Apple; System.out.println(fruits.getClass()); //class [Lcom. Zero. Genericsdemo02. Demo02. Fruit; fruits = apples; / / what kind of fruits was put inside? Fruit or Apple / / Apple fruits [] [0] = new Fruit[] is the parent of Apple, Fruit[] is the parent of Apple[], which is the covariant of the array. [] plates = new Plate<?>[10];// This is okCopy the code

Generics, inheritance, and subtypes

Given two concrete types A and B(Fruit and Apple, for example), MyClass has nothing to do with MyClass, whether A and B are related or not. Their common parent Object is Object

The generic PECS principle

  • If you only need to get type T from the collection, use <? Extends T> wildcard

  • If you only need to put type T into the collection, use <? Super T> wildcard

  • If you want to both get and place elements, do not use any wildcards. Such as the List

  • PECS is Producer extends Consumer super for easy memorization.

  • Why PECS principles? Improved API flexibility

  • You can neither deposit nor take it out

When using partially qualified parameters in generic programming, <? Super T > and <? The use of extends T> is confusing, and PECS principles can help us remember them well: providers use extends and consumers use super. In plain English, Provider refers to a container that provides objects of type T or subtypes of T from its own container for others to use. Consumer means that the container puts objects of type T or subtypes of T in its own container that it gets from elsewhere.

Kotlin generics

  • Use the keyword out to support covariant, equivalent to the upper bound wildcard in Java? Extends.

  • Use the keyword in to support inversion, equivalent to the lower bound wildcard in Java? Super.

    var textViews: List var textViews: List

Out and in at the declaration

Kotlin offers an alternative: you can declare a class by adding the out keyword to the generic symbol to indicate that the generic parameter T will only be used for output.

class Producer<out T> { fun produce(): T { ... }} val producer: producer <TextView> = producer <Button>() 👈 Producer<out TextView> = Producer<Button>() // 👈 out can but not necessaryCopy the code

The where keyword

When you declare a class or interface in Java, you can use extends to set boundaries, limiting generic type parameters to a subset of a type. This boundary can be multiple, concatenated with ampersand:

Class A<T extends B & C>{} class A<T extends B & C>{}Copy the code

In the Kotlin

Class A<T> where T: B, T: CCopy the code

Reified keyword

Inline fun <reified T> printIfTypeMatch(item: Any) {if (item is T) {// 👈Copy the code

Where Kotlin generics differ from Java generics

  1. Arrays in Java support covariance, whereas arrays in Kotlin do not.

This is because arrays in Kotlin are represented by the Array class, which uses generics just like collections, and therefore does not support covariance.

  1. The List interface in Java does not support covariance, whereas the List interface in Kotlin does.

Lists in Java do not support covariance, for reasons already described above, and need to be addressed using generic wildcards.

In Kotlin, the MutableList interface is actually the Java equivalent of a List. The List interface in Kotlin implements read-only operations and no writes, so there are no type-safety issues and covariance is naturally supported.

The interview often ask

  1. Can generics be used in Array?

A: can’t

  1. Generic type reference passing problem

Q: Can you pass a List to a method that takes a List argument?

ArrayList arrayList1=new ArrayList(); ArrayList arrayList1=new ArrayList();

A: No. It doesn’t matter

  1. In Java List <? What’s the difference between > and List? A:
    • List: No type restrictions or assignment restrictions at all.

    • List: Appears to be used in the same way as List, but a compilation error occurs when accepting other generic assignments.

    • List: a generic set that accepts assignments of any type before assignment, but cannot add elements to it after assignment, but can remove and clear, and is not an immutable set. A List typically receives an external collection as an argument, or returns a collection of specific element types, also known as a wildcard collection.

    1. What are qualified and unqualified wildcards in generics?

    A:

    • The qualified wildcard <? extends T> <? super T>

    • The unqualified wildcard <? >

    5. Generic type variables cannot be primitive data types

    //error
    ArrayList<double> arr1 = new ArrayList<>();
    ArrayList<Double> arr2 = new ArrayList<>();
    Copy the code
    1. Run time type query

      ArrayList arrayList=new ArrayList(); If (arrayList instanceof arrayList) // Erase if(arrayList instanceof arrayList <? >)

    2. Java generics themselves do not support covariant and contravariant types

    • Can you use generic wildcards? Extends makes generics covariant, but “reads, not modifies”, where modification only means adding elements to a generic collection, such as remove(int index) and clear.

    • Can you use generic wildcards? Super makes generic types contravariant, but “can only be modified but cannot be read”. By “cannot read”, we mean that we cannot read from the generic type.

    1. Arrays in Java are covariant