Phase to recommend

  • Java Basics
  • Java concurrent programming
  • JVM

Why were generics introduced

Advantages of generics

1. The nature of generics is to parameterize types, that is, to control the specific types of parameters restricted by the different types specified by generics without creating new types. This approach obviously improves code reuse.

2. Security has been improved by the introduction of generics, which provide compile-time type safety checks that allow developers to detect illegal types at compile time.

3. In the absence of generics, parameters can be “arbitrary” by referring to the type Object. The disadvantage of “arbitrary” is that explicit casts are required, which require the developer to be able to predict the actual parameter types. In the case of a cast error, the compiler may not give an error until runtime, which is itself a security risk.

The advantage of generics, then, is that type safety can be checked at compile time, and all casts are automatic and implicit.

Why improved security?

Example of insecurity


package keyAndDifficultPoints.Generic;


import java.util.ArrayList;
import java.util.List;

/ * * *@Author: Akiang
 * @Date: 2021/9/9 16:09 * <p> *
public class Test_Safe {

    public static void main(String[] args) {
        test();
    }

    public static void test() {
        List arrayList = new ArrayList();
        arrayList.add("aaaa");
        arrayList.add(100);

        for (int i = 0; i < arrayList.size(); i++) {
            String s = (String) arrayList.get(i); System.out.println(s); }}}Copy the code

Results:

aaaa
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
	at keyAndDifficultPoints.Generic.Test_Safe.test(Test_Safe.java:25)
	at keyAndDifficultPoints.Generic.Test_Safe.main(Test_Safe.java:16)

Copy the code

A typecasting error is obvious. ArrayList can hold any type. In this example, we added a String and an Integer, and then used them as strings, so the program crashed. Generics were created to solve problems like this, which can be solved at compile time.

Generics improve security


public static void test01(){
       List<String> arrayList = new ArrayList<>();
       arrayList.add("aaaa");
       // The following code was compiled with an error
       arrayList.add(100);

       for (int i = 0; i < arrayList.size(); i++) {
           String s = (String) arrayList.get(i); System.out.println(s); }}Copy the code

Generics are used to detect types ahead of time and fail at compile time.

A generic class

Generic types are used in the definition of classes and are called generic classes. Generics make it possible to open the same interface to operations on a set of classes. The most typical are the various container classes: List, Set, Map.


package keyAndDifficultPoints.Generic;

/ * * *@Author: Akiang
 * @Date: 2021/9/9 16:38 * <p> *
public class Test_GenericClass {
    public static void main(String[] args) {
        test();
    }

    public static void test(){
        /** * 1, the type parameter of the generic type must be class type (including custom class), not simple data type (such as int,long, etc.) * 2, the type of the argument passed must be the same as the type parameter of the generic type, i.e. Integer. */ * * * * * * * * * * * * * *
        Generic<Integer> genericInteger1 = new Generic<Integer>(123);
        Generic<Integer> genericInteger = new Generic<>(123);

        Generic<String> genericString = new Generic<String> ("my"); System.out.println(genericInteger.getVar()); System.out.println(genericString.getVar()); }}/** * 1, although T can be written as an arbitrary identifier, common parameters such as T, E, K, V are used to represent generics. * But for code readability in general: * K,V for key pair * E short for Element, often used for traversal * T short for Type, commonly used on generic classes * 2, and less common U,R, etc. */
class Generic<T> {
    // Key is a member variable of type T, whose type is externally specified
    private T var;

    public Generic(T var) { // The generic constructor parameter key is also of type T, whose type is externally specified
        this.var = var;
    }

    public T getVar() { // The generic method getKey returns a value of type T, the type of which is externally specified
        return var; }}class MyMap<K.V> {       // Two generic types are specified here
    private K key;     // The type of this variable is externally determined
    private V value;   // The type of this variable is externally determined

    public K getKey() {
        return this.key;
    }

    public V getValue() {
        return this.value;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public void setValue(V value) {
        this.value = value; }};Copy the code
  • Is it necessary to pass in a generic type argument to a defined generic class? This is not the case. When you use generics, if you pass in a generic argument, the generic argument will be restricted accordingly, and the generics will do what they are supposed to do. A method or member variable defined using a generic type in a generic class can be of any type, provided that no generic type argument is passed.

  • If the subclass that inherits from the parent class is a generic class, then the subclass and the parent class need to have the same generic type.

    class children<T> extends GenericParent<T> { }

  • If a subclass that inherits from a parent class (a generic class) is a non-generic class, then the subclass must explicitly specify the generic type of the parent class when declaring the parent class to inherit.

    class children extends GenenricParent<String> { }

Generic method

A generic class is the type we specify when we instantiate a class, and a generic method is the type we specify when we call a method. A generic method allows the method to change independently of the class.

  • Define generic method syntax formats

When you define a generic method, you must prefix the return value with a

to declare that it is a generic method and hold a generic T before you can use the generic T as the return value of the method.

The purpose of Class

is to specify the specific type of the generic, and the c variable of Class

can be used to create objects of the generic Class.

  • Invoke generic method syntax format

Generic methods require arguments of type Class

, and the return value of the class.forname () method is also Class

, so class.forname () can be used as arguments. The Class

returned in the forName() method depends on the type of the argument.


  • Generic method examples:
public class GenericClass<T> {
   
    // With a generic method, the method can accept arguments of any data type
    //
      
        Generic identifier, which is specified when the method is called
      
    public static <T> void genericMethod(T birthday){
        System.out.println(birthday);
    }
    
    // Use multiple generic types
    public static <T,V,E> void genericMethod2(T t,V v,E e){
        System.out.println(t+"-"+v+"-"+e);
    }
    
   
    // The definition of a generic variable parameter
    public static <T> void GenericVariadic(T... t){
        for (int i = 0; i < t.length; i++) {
            System.out.println(t[i]);
        }
    }

    
    public static void main(String[] args) {
      
        GenericClass.genericMethod(new Date());
        GenericClass.genericMethod("2021-09-09");
        
        // Call generic methods of multiple generic types
        GenericClass.genericMethod2("Akiang".212.110);
       
        // Generic variable arguments
        GenericClass.GenericVariadic(1.2.3.4.5."4"); }}Copy the code

A generic interface

interface Info<T>{        // Define generics on interfaces
    public T getVar() ; // Define a method whose return value is a generic type
} 
Copy the code
  • If the implementation class that implements a generic interface is not a generic class, the generic type of the generic interface must be explicitly specified when you declare the implementation of the generic interface.

    class interfaceImpl implements GenericInterface<String> { }

  • If the implementation class that implements a generic interface is a generic class, the generic type of the implementation class must contain the generic type of the interface generic class.

    Class interfaceImpl<T, other generic flags... > implements GenericInterface<T> { }

When a class that implements a generic interface passes no generic arguments:

/** ** * class InfoImpl
      
        implements Info
       
         Class InfoImpl implements Info
        
         , "Unknown class" */
        
       
      
class InfoImpl<T> implements Info<T> {   // Define a subclass of the generic interface
    private T var;

    public InfoImpl(T var) {
        this.setVar(var);
    }

    public void setVar(T var) {
        this.var = var;
    }

    public T getVar() {
        return this.var; }}Copy the code

When a class that implements a generic interface passes in a generic argument:

Info
      
        * When a class implements a generic interface, if a generic type has been passed in as an argument type, all uses of the generic type should be replaced by the argument type passed in * i.e. : InfoImpl01
       
        , public String getVar(); The T in is replaced by the String passed in. * /
       
      
class InfoImpl01 implements Info<String> {   // Define a subclass of the generic interface
    private String var;

    public InfoImpl01(String var) {
        this.setVar(var);
    }

    public void setVar(String var) {
        this.var = var;
    }

    public String getVar() {
        return this.var; }}Copy the code

Generic array

Official Sun Documentation

  • You can declare array references with generics, as inArrayList<String> [] listArray;, but you cannot create array objects with generics directly, as inArrayList<String> [] listArray = new ArrayList<String>[2];

package keyAndDifficultPoints.Generic;

import java.util.ArrayList;
import java.util.List;

/ * * *@Author: Akiang
 * @Date: 2021/09/08 12:10 * <p> *
public class Test_GenericArray {

    public static void main(String[] args) {
        test02();
    }

    public static void test() {
        // Error compiling
// List
      
       [] ls = new ArrayList
       
        [10];
       
      
    }


    public static void test01() {
        // This statement is correctList<? >[] ls =newArrayList<? > [10];
        ls[1] = new ArrayList<String> ();// Write to compile error
// ls[1].add(1);

    }

    /** ** In fact, don't worry too much, usually although the use of generics, but also not so weird. * /
    public static void test02(){ List<? >[] lsa =newList<? > [10]; // OK, array of unbounded wildcard type.
        Object o = lsa;
        Object[] oa = (Object[]) o;
        List<Integer> li = new ArrayList<Integer>();
        li.add(new Integer(3));
        oa[1] = li; // Correct.
        Integer i = (Integer) lsa[1].get(0); // OK
        System.out.println(i);
    }

    / / right
    public static void test03() {
        List<String>[] ls = new ArrayList[10];
        ls[0] = new ArrayList<String> (); ls[1] = new ArrayList<String> (); ls[0].add("x"); }}Copy the code

Generic wildcard

Common wildcard character

These are essentially representing wildcards, no different, just conventions of coding. For example, in the above code, T can be replaced with any letter between A and Z without affecting the normal operation of the program. However, if T is replaced with other letters, the readability may be weaker. In general, T, E, K, V, right? Here’s the deal:

  • ?Represents an indeterminate Java type
  • T (type)Represents a concrete Java type
  • K V (key value)Respectively represent Key values in Java key-value pairs
  • E (element)On behalf of the element

Infinite wildcards

Use of wildcards:? Class A is the parent of class B. G<A> and G<B> are unrelated. The common parent is G<? >Copy the code
    @Test
    public void test3() {
        List<Object> list1 = null;
        List<String> list2 = null; List<? > list =null;

        list = list1;
        list = list2;
        // The compiler passes
// print(list1);
// print(list2);


        //
        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        list = list3;
        // Add (write) : for List
       cannot add data to it internally.
        // In addition to adding null.
// list.add("DD");
// list.add('? ');

        list.add(null);

        // Get (read) : Allows reading of data of Object type.
        Object o = list.get(0);
        System.out.println(o);


    }
Copy the code

The upper bound wildcard <? extends E>

Upper bound: Declared with the extends keyword to indicate that the parameterized type may be the specified type or a subclass of that type.

Using extends in a type parameter means that the parameter in the generic must be E or a subclass of E. This has two benefits:

  • If the type passed is not E or a subclass of E, the compilation fails
  • The E method can be used in generics, otherwise you have to cast to E to use it
class Info<T extends Number>{    // Generics can only be numeric types here
    private T var ;        // Define generic variables
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // Print directly
        return this.var.toString() ;
    }
}
public class demo1{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // Declare the generic object of Integer}}Copy the code

The lower wildcard <? super E>

Lower bound: declared with super, indicating that the parameterized type may be the specified type or a parent of the type, up to Object

Using super in type parameters indicates that the parameters in the generic must be E or a parent of E.

class Info<T>{
    private T var ;        // Define generic variables
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // Print directly
        return this.var.toString() ;
    }
}
public class GenericsDemo21{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>() ;        // Declare a generic object for String
        Info<Object> i2 = new Info<Object>() ;        // Declare the generic Object of Object
        i1.setVar("hello"); i2.setVar(new Object()); fun(i1) ; fun(i2) ; } publicstatic void fun(Info<? super String> temp){    // Only generics of type String or Object can be received. The parent of the String class has only the Object class
        System.out.print(temp + ","); }}Copy the code

? And T

? And T are indeterminate types, the difference is that we can operate on T, but right? I can’t.

  • T Is a deterministic type, usually used in the definition of generic classes and generic methods.
  • ?Is an indeterminate type that is usually used for calling code and parameters of generic methods and cannot be used to define classes and generic methods.

Difference 1: T is used to ensure consistency of generic parameters

package keyAndDifficultPoints.Wildcard_Character;

import java.util.ArrayList;
import java.util.List;

/ * * *@Author: Akiang
 * @Date: 2021/09/09 11:28 * <p
public class Test_difference {

    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<Float> floatList = new ArrayList<>();

        // Compile error
// test(integerList, floatList);
        // The compiler passes
        test1(integerList, floatList);


        // The compiler passes
        test(integerList, integerList);
        test1(integerList, integerList);

    }



    // Use T to ensure consistency of generic parameters
    public static <T extends Number> void test(List<T> dest, List<T> src){}// Wildcards are indeterminate, so this method cannot guarantee that both lists have the same element type
    public static void test1(List<? extends Number> dest, List<? extends Number> src){}}Copy the code

Difference 2: T can be multiqualified with &

public class Test_difference {

    public static void main(String[] args) {


        / * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - test multiple qualifiers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
        ArrayList list = new ArrayList<>();
        ArrayDeque deque = new ArrayDeque<>();
        LinkedList<Object> linkedList = new LinkedList<>();

        // When multiqualified, take the minimum range or common subclass at compile timetest2(list); test3(list); A compiler error// Compile error
        test2(deque);
        test3(deque);

        // The compiler passes
        test2(linkedList);
        test3(linkedList);


    }


    // Can be multiple qualified
    public static <T extends List & Collection> void test2(T t){}// Can be multiple qualified
    public static <T extends Queue & List> void test3(T t){}// Error compiling, unable to multiqualify
    public static <? extends List & Collection> void test4(List<T> dest, List<T> src){}}Copy the code

Difference 3:? Wildcards can be qualified using superclasses but T cannot

The type parameter T has only one type qualification:

T extends A
Copy the code

But wildcards? Two kinds of qualification can be made:

? extends A
? super A
Copy the code

Generic erasure

Java generics are pseudo generics. Why are Java generics pseudo generics? Because all generic information is erased during compilation, this is often called generic erasure.

  • Generics in Java are basically implemented at the compiler level, and the generated Java bytecode does not contain the type information in generics. Type parameters added when using generics are removed by the compiler at compile time, a process known as type erasure.

Types such as Listand List

, defined in code, are programmed to List after compilation, and all the JVM sees is the List. Additional type information from generics is not visible to the JVM. The Java compiler looks for possible errors at compile time, but there is still no way to avoid type conversion exceptions at run time. Type erasure is also an important difference between the way generics are implemented in Java and the way C++ template mechanisms are implemented.

Erasing type parameters in a class definition – unrestricted type erasing

When there are no restrictions on type parameters in a class definition, they are replaced directly with Object in type erasure, such as

and
are replaced with Object.

Erasing type parameters in a class definition – restricted type erasing

When there are limits (upper and lower bounds) on type parameters in a class definition, they are replaced with upper or lower bounds on type parameters in type erasure, such as

and
is replaced with Number,
is replaced with Object.

Erases type parameters in method definitions

The principle of erasing type parameters in a method definition is the same as erasing type parameters in a class definition.

The bridge method

We first define an Info generic interface that contains method() methods, then InfoImpl implements the Info interface and specifies that the generic type of the interface is Integer and overwrites the method() method in the interface, using reflection to see what methods are in the compiled InfoImpl implementation class and their return value types.

public interface Info<T> {
    T method(T t);
}

public class InfoImpl implements Info<Integer> {
    @Override
    public Integer method(Integer i) {
        return i;
    }

    public static void main(String[] args) {
        // Get the bytecode of InfoImpl, the implementation of the generic interface Info
        Class<InfoImpl> infoClass = InfoImpl.class;
        // Get all methods
        Method[] mes = infoClass.getDeclaredMethods();
        for (Method method : mes) {
            // Outputs the method name and return value type in the implementation class
            if(method.getName()! ="main")
            System.out.println(method.getName() + ":"+ method.getReturnType()); }}} program output:method:class java.lang.Integer
method:class java.lang.Object
Copy the code

After running the main method above, we can see that there are two method () methods in the InfoImpl implementation class with the same name but different types of return value types and parameter lists.

The Java virtual machine added method(), which is a bridge method, with the return type and parameter type Object. Because our generic interface Info of type Integer will be erased to type Object when compiled, our implementation class InfoImpl will ensure that our specification and constraints on the implementation of the interface So the Java virtual machine helps us generate a method() method that returns a value type and an argument type Object in the implementation class, thus preserving the implementation relationship between the interface and the class.

How to understand that primitive types cannot be generic types?

For example, we don’t have ArrayList

, only ArrayList

, why?

Because when the type is erased, the original type of ArrayList changes to Object, but Object cannot store an int value, only an Integer value can be referenced.

Also note that we were able to use list.add(1) because of the Java base type of automatic boxing and unboxing.

How do you prove type erasure?

  • Primitive type equality
public class Test {

    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String> (); list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        System.out.println(list1.getClass() == list2.getClass()); // true}}Copy the code

In this example, we define two ArrayList arrays, but one is a generic ArrayList

type that can only store strings. One is the ArrayList

generic type, which can only store integers. Finally, we get information about list1 objects and List2 objects using the getClass() method, and find that the result is true. Note The generic String and Integer types have been erased, leaving only the primitive type.

  • Add elements of other types through reflection
public class Test {

    public static void main(String[] args) throws Exception {

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

        list.add(1);  // This call to add can only store integers, since instances of generic types are Integer

        list.getClass().getMethod("add".Object.class).invoke(list, "asd");

        for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }}}Copy the code

We define an ArrayList generic type instantiated as an Integer object. If we call add() directly, we can only store Integer data, but when we call add() with reflection, we can store strings. This shows that the Integer generic instances are erased after compilation. Only the original type is preserved.

How to understand that generic types cannot be instantiated?

Generic types cannot be instantiated, essentially due to type erasure:

We can see that the following code will report an error in the compiler:

T test = new T(); // ERRORBecause there is no way to determine the generic parameterized type at Java compile time, there is no way to find the corresponding class bytecode file, so it is not possible`T`Be erased`Object`If you can`new T()`So it becomes 1, 2, 3`new Object()`, lost the original intention.Copy the code

If we do need to instantiate a generic, how do we do it? This can be done by reflection:

static <T> T newTclass (Class < T > clazz) throws InstantiationException, IllegalAccessException {
    T obj = clazz.newInstance();
    return obj;
}
Copy the code

The above is the summary of the knowledge of generics in Java, master the use of generics, can improve the reuse rate of our code by eliminating the forced conversion can improve the security of our program.