General code requires that the type be deterministic, which greatly restricts the code that can be reused by different types.

Declaring a type as a superclass or interface allows code reuse to a certain extent, but it only extends the limit to the superclass and its subclasses or classes that implement the interface. In some cases, this scope is still not sufficient for us, especially since Java is inherited from a single root. We want “non-type-specific” coding, not a concrete class or interface.

The introduction of generics in Java 5 allowed us to write “type-less” code. Generics implement parameterization of types, declaring type parameters when you define a class, interface, or method, and deciding their specific type when you use them.

Generics are compile-time properties where the compiler performs type checking on the generics and adds some extra transition code at the ‘bounds (in and back)’ of the class to ensure type-safety at runtime. We use it as if we were replacing our declared type parameter with a specific type. However, the implementation loses the reference type information after compilation, and the specific type we specified is erased at run time.

Java’s use of type erasing, unlike c++ ‘s type replacement, is also a matter of choice, as there were no generics before Java 5, and type erasing was a matter of choice in order to be compatible with pre-java 5 code.

Noun explanation:

  • Type parameters: Type parameters declared in Angle brackets when declaring a generic class, interface, or method, such as E for List

  • Generic classes: Classes, interfaces, and methods that declare type parameters are called generic classes, generic interfaces, and generic methods, respectively.

  • Parameterized types: When a generic class is used and a specific type is specified, it is called a parameterized type, such as List

  • Primitive type: The Class of a generic Class of a parameterized type, such as List whose primitive type is List, and List[] whose primitive type is also List

1. Generic definition and use

1.1 a generic class

We use Angle brackets after the class name to declare the type parameter. The declared type parameter can be used in the type declaration just like a normal type. When it is used, we decide what type it is and then the compiler will handle some of the details of the type conversion.

public class Holder<T> {
    T val;

    public Holder(T val) {
        this.val = val;
    }

    public T getVal(a) {
        return val;
    }
    
    public void setVal(T val) {
        this.val = val;
    }
    
    public static void main(String[] args) {
        Holder<String> strHolder = new Holder<String>("abc"); String s = h.getVal(); }}Copy the code

The Holder type parameter specified when used is String. You can assign the return value of getVal() directly to a String variable without the transformation shown. String or its subclass must also be passed when using setVal. If the input is not String or its subclass, the compilation will report an error.

Before Java7, you need to specify the type when new parameterizes the type, but after Java7, you don’t need to specify the type for new operations. The compiler will automatically deduce:

 Holder<String> h = new Holder<>("abc");
Copy the code

Use commas to separate multiple type parameters:

public class Holder<A.B.C> {

    public A v1;
    public B v2;
    public C v3;

    public Holder(A v1, B v2, C v3) {
        this.v1 = v1;
        this.v2 = v2;
        this.v3 = v3;
    }

    public static void main(String[] args) {
        Holder<String, Integer, Float> h = new Holder<>("abc".1.2.5); }}Copy the code

An inner class can use the type parameters of an outer class:

class A<T> {
    class B { T a; }}Copy the code

Anonymous inner classes can also be parameterized types

interface A<T> {
    T next(a);
}
new A<String>() {
    @Override
    public String next(a) {
        return null; }};Copy the code

Static properties, static methods, and static inner classes cannot use the generic parameters of the class. If you want to make a static method generic, you can use generic methods.

public class Calculate<T> {
    // If T cannot be used in static methods, an error will be reported at compile time
    public static T add(T a, T b) { T c = a + b; }}Copy the code

1.2 Generic interfaces

Interfaces can also be declared as generic in the same way as generic classes.

public interface Generator<T> {
    T next(a);
}
Copy the code

When implementing a generic class, you need to specify a specific type for the type parameter:

public interface Bottle<T> {
    void pourInto(T t);
    T pourOut(a);
}

// To implement Bottle, specify the type parameter Juice
public class GlassBottle implements Bottle<Juice> {
    public void pourInto(Juice juice) {}public Juice pourOut(a) {
        return null; }}Copy the code

1.3 Generic methods

You can declare a generic type for a method alone, and the class does not have to be generic. To define a generic method, you simply place the generic parameter list before the return value. Declared type parameters are used in methods where the type is defined just like a normal class.

public class Test {
    public static <T> void t(T x) {
        System.out.println(x.getClass().getName());
    }
    
    public static <K,V> Map<K, V> newMap(a) {
        return new HashMap<K, V>();
    }

    public static void main(String[] args) {
        t(11);  // java.lang.Integer
        t("abc"); // java.lang.String

        Map<String, Date> m = newMap();
        m.put("now".newDate()); }}Copy the code

When you use a generic method, you do not have to specify a specific type. The compiler will infer a specific type from the type of the input or return assignment to the method type parameter. However, if you pass the result of the call directly as an argument to another method, the compiler will not infer the type. If it is a basic type, it is automatically boxed to the packing type.

public static <T> String className(T v) {
    return v.getClass().getSimpleName();
}

public static void main(String[] args) {
    // Output Integer, which is automatically inferred to be an Integer
    System.out.println(Test.className(11));
}
Copy the code

Specify the type that can also be displayed when a generic method is called by inserting Angle brackets between the dot operator and the method name, and then placing the type inside.

Test.<String, Date>newMap();
Copy the code

Variable-length argument lists can also use generic arguments:

public static <T> List<T> toList(T... args) {
    List<T> l = new ArrayList<T>(args.length);
    for (T e : args) {
        l.add(e);
    }
    return l;
}
Copy the code

When a variadic method is called, an array is created to hold the variadic arguments. If the arguments are of generic type, then a generic array is created, but isn’t Java allowed to create arrays using generics directly? Java has made some compromises here to allow you to create a generic array for mutable parameters.

However, the input parameters of a mutable argument list can be of different types, so sometimes the compiler cannot determine the specific type of a generic mutable argument, and can only choose the most generic type.

public class Test {

    public static void main(String[] args) {
        System.out.println(toArray(Integer.valueOf(11),   Double.valueOf(13)).getClass());
    }

    public static <T> T[] toArray(T... args) {
        returnargs; }}Copy the code

Output:

class [Ljava.lang.Number;
Copy the code

2. Inherit generic classes/implement generic interfaces

2.1 Specify the type during inheritance

When you inherit a generic class or implement a generic interface, you need to specify a specific type. When you specify a specific type, the subclass’s superclass or interface is parameterized. When you obtain the type of the parent Class from getGenericSuperclass, the type returned is ParameterizedType.

public class Holder<T> {

    private T val;

    public Holder(T val) {
        this.val = val;
    }

    public T getVal(a) {
        return val;
    }

    public void setVal(T val) {
        this.val = val; }}class Apple {
    public void  show(a) { System.out.println(getClass().getSimpleName()); }}public class AppleHolder extends Holder<Apple> {

    public AppleHolder(Apple apple) {
        super(apple);
    }

    public static void main(String[] args) {
        AppleHolder appleHolder = new AppleHolder(new Apple());
        Apple apple = appleHolder.getVal();
        apple.show();

        System.out.println(appleHolder.getClass().getGenericSuperclass() instanceofParameterizedType); }}Copy the code

Output:

Apple

true
Copy the code

2.2 No type is specified during inheritance

If you inherit a Class or implement an interface without specifying a type, the superclass or interface is a normal Class or interface to the subclass, and its type parameter is erased to Object. The type returned by getGenericSuperclass of Class is Class.

public class CommonHolder extends Holder {

    public CommonHolder(Object val) {
        super(val);
    }

    public static void main(String[] args) {
        System.out.println(CommonHolder.class.getGenericSuperclass() instanceofClass); }}Copy the code

Output:

true
Copy the code

2.3 specifies as a type parameter in a subclass

You can also give a type parameter declared in a subclass to a superclass, and the superclass gets the same type when you specify a type for the subclass. For subclasses, the superclass is still parameterized, and the return type of getGenericSuperclass is still ParameterizedType.

public class CommonHolder<T> extends Holder<T> {

    public CommonHolder(T val) {
        super(val);
    }

    public static void main(String[] args) {
        System.out.println(CommonHolder.class.getGenericSuperclass() instanceofParameterizedType); }}Copy the code

Output:

true
Copy the code

3. Boundaries of generics

Because of type erasure, we cannot directly use specific properties or methods for type parameters. The following calls will fail to compile:

import java.sql.DriverManager;
import java.util.*;

public class Test<T> {
    public T val;

    public void show(a) {
        // Failed at compile time
        val.show();
    }

    public static class Apple {
        public void show(a) {}}public static void main(String[] args) throws ClassNotFoundException {
        Test<Apple> t = newTest<>(); t.show(); }}Copy the code

In the example above, even though we know that the type of val is followed by the time Show, the compiler disallows such use because there is no guarantee of doing so after type erasure.

However, you can use extends to display an upper bound on a type parameter. Otherwise, the upper bound would be Object. After declaring an upper bound for a class, the type specified when the generic class is used can only be the upper bound or its subclass.

public class Show {
    public void show(a) {}}public class Test<T extends Show> {
    public T val;

    public void show(a) {
        // Can be called
        val.show();
    }
    
    public static void main(String[] args)  {
        Test<Show> t = newTest<>(); t.show(); }}Copy the code

The default upper bound is Object when no upper bound is declared, so we can call methods of Object without declaring an upper bound.

public class Test<T> {
    public T val;
    public void show(a) { val.getClass(); val.toString(); val.hashCode(); }}Copy the code

4. Wildcard characters

Drink -> Juice -> AppleJuice
public class Drink {}
public class Juice extends Drink {}
public class AppleJuice extends Juice {}
Copy the code
public class Bottle<T> {
    private T drink;

    public Bottle(T drink) {
        drink = drink;
    }

    public T getDrink(a) {
        return drink;
    }

    public void setDrink(T drink) { drink = drink; }}Copy the code

For ordinary classes, objects of the same class can be assigned to each other, and subclass objects can be assigned to the parent class objects.

Juice juice = new Juice();
juice = new AppleJuice();
Copy the code

However, as long as the specified type parameters are different, even if they are the same generic class, they are different parameterized types and cannot be assigned to each other directly:

// Error
Bottle<Juice> b1 = new Bottle<AppleJuice>(new AppleJuice());
Copy the code

Although they are both bottles after type erasers, at compile time the compiler inserts different type handling code at the edge of the generic class. Obviously, you can’t use the same code that handles AppleJuice to handle other types, so they are different types from the compiler’s point of view and will report an error at compile time.

To solve the problem of assigning values between generic instances whose type parameters are inherited, Java provides wildcards.

4.1 Upper wildcard

You can use the extends key when defining a generic variable to specify an upper bound of the type, so that the declared variable can be assigned to a generic parameterized type of the class whose type parameter is the upper bound class and its subclass, provided that the generic class is the same or parent class.

Bottle<? extends Juice> b = new Bottle<AppleJuice>(new AppleJuice());
Copy the code

B declaring Juice as the upper bound can be assigned to Bottle. However, the use of generic instances is limited by the use of upper bound wildcards.

Despite the use of the extends wildcard, the compiler still doesn’t know whether the specific type of B is AppleJuice or a subclass of OrangeJuice, so the compiler can’t guarantee the security of incoming parameters to methods whose parameter types have type parameters, such as:

Bottle<? extends Juice> bottle = new Bottle<AppleJuice>(new AppleJuice());
// error
bottle.setDrink(new OrangeJuice());
Copy the code

SetDrink is defined as:

void setDrink(T drink)
Copy the code

Then obviously the actual type of the bottle variable is bottle, so setDrink will compile to:

setDrink((AppleJuice) val)
Copy the code

Obviously, it’s not safe to force conversions between sibling types, so instances declared with an upper bound wildcard are not allowed to call methods that take type parameters. But if it’s null, it’s ok, because null doesn’t have a specific type. But it’s safe to return, it’s safe to assign subclasses to the superclass, so methods that return type arguments are not affected.

Bottle<? extends Juice> b = new Bottle<AppleJuice>(new AppleJuice());
Juice juice = b.getDrink();
Copy the code

4.2 Lower wildcard

A generic variable that uses the super keyword to specify a lower bound. A variable that specifies a lower bound can only be assigned to the type of the class whose argument is the specified lower bound or the parent of the lower bound.

Bottle<? super Juice> b = new Bottle<Drink>(new AppleJuice());
Copy the code

At compile time the entry is converted to the actual type Drink:

setDrink((Drink) val)
Copy the code

It is safe to use a parent type to operate on a subtype, so it is safe for instances declared by the lower bound wildcard to use methods with input type parameters. But because a parent class cannot be assigned to a child class, an instance of the lower bound wildcard declaration cannot assign the return value of a method of a parameter type to another variable.

Bottle<? super Juice> b = new Bottle<Drink>(new AppleJuice());
// Error
Drink drink = b.getDrink();
Copy the code

4.3 Unbounded wildcards

Parameter type is specified as? Number, which means any type can be used.

Bottle<? > b =new Bottle<>(new AppleJuice());
Drink drink = (Drink) b.getDrink();

// ERROR
b.setDrink(new AppleJuice());
Copy the code

Using unbounded wildcard looks much the same and the original raw type, but the meaning of the unbounded wildcard is the use of any type we know here, and unbounded wildcard type checks, because don’t know the exact type of unbounded wildcard cannot guarantee the security, the unbounded wildcard variables cannot be called into the parameter type for the type parameter method.

5. Type erasure

When using generics, the type specified only takes effect at compile time, after which all type parameters are erased to its first boundary, or Object if no boundary is specified.

Type operations such as instanceof, new, t.lass, etc., cannot be explicitly used at run time because type erasures no longer exist at run time.

public class Test<T> { Class<? > type;public Test(Class
        type) {
        this.type = type;
    }

    public T[] newArray(int size) {
        return (T[]) Array.newInstance(type, size);
    }

    public static void main(String[] args) {
       Test<String> t = new Test<>(String.class);
       String[] strArr = t.newArray(10); }}Copy the code

Although we can specify different type parameters, they all point to the same type after erasures. For example, List and List have the same Class: list.class.

Because in addition to specific types of information, at compile time for the class to ensure runtime type behavior correctly, the compiler of generic ‘border’ at compile time, namely the generic into the reference in the class and back on the way to do the type checking and code insertion force transformation, when the method is called to type conversion into arguments, return to return values.

6, advice,

6.1 Specify type Information

Because of type erasing, we cannot get specific parameter type information at run time. If we need specific type information, we can display the Class object passing type.

public class Test<T> {
    private Class<T> kind;
    public T val;
    public Test(Class<T> kind) {
        this.kind = kind;
    }
    public boolean isType(Object o) {
        returnkind.isInstance(o); }}Copy the code

6.2 Do not use generic classes if you can use generic methods

If it is possible to replace a generic class with a generic method, then try to replace the generic class with a generic method.

6.3 Use parameterized generics whenever possible

If a class or interface is generic, use the parameterized type as much as possible so that the compiler does some type checking at compile time to avoid errors at run time.

Wildcards are also recommended if there is no specific type, such as List
. Using wildcards can be checked at compile time and prevents us from calling methods that have type arguments.

Directly using the original type of generic risk from time to tome, primitive types will not at compile time type checking, and type parameters be erased as the Object, the Object can accept any type of instance, if the instance is given to the class of the different types of, so to manipulate these instances in the class there is a safety hazard and exposed these hazards may be in luck. The only reason Java supports using generic primitives is for compatibility with pre-Java 5 code.

6.4 Do not assign parameterized types to primitive types

For compatibility, Java does not disallow converting variables of parameterized types to primitive types; doing so will only generate an alarm at compile time. However, when a parameterized type is assigned to a primitive type, the compiler does no type checking on operations on instances of the primitive type, which can cause runtime errors.

class Calculator<T> {
    public int intAdd(T v1, T v2) {
        return((Number) v1).intValue() + ((Number) v1).intValue(); }}Copy the code
public class Test {
    public static void main(String[] args) {
        Calculator<Integer> intCal= new Calculator<>();
        Calculator cal = intCal;
        cal.intAdd("a"."b"); }}Copy the code
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

at com.test.java.Calculator.intAdd(Test.java:10)  
at com.test.java.Test.main(Test.java:20)
Copy the code

Assigning intCal of the parameterized type Calculator to CAL does not type check operations on CAL. This error is only thrown at run time.

public static void main(String[] args) {
    List<String> strList = new ArrayList<>();
    List list = strList;
    list.add(Integer.valueOf(11));
    String s = strList.get(0);
}
Copy the code
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

at com.test.java.Test.main(Test.java:27)  
Copy the code

The above code produces only a warning when compiled, but a fatal error when run. Because strList is assigned to a List of type List, the lock is compiled without type checking for operations on the List variable. Because of type eraser, String is erases to Object at run time, so list.add(integer.valueof (11)) works fine. But because strList is of type List, String conversion code was inserted into it at compile time, and converting an Integer to a String is illegal.

6.5 Try not to use generic mutable parameter lists

Sometimes the type of a generic variable parameter cannot be determined, and the array type of the variable parameter can only be located to a generic type. With an array of mutable argument lists, we don’t just pass values, we may operate on them, which poses a type safety risk. You should try to avoid using generic mutable arguments or replacing mutable arguments with List parameterized types.

Effective Java has a classic example of passing in three objects and randomly selecting two of the most predictable arrays to return:

public class Test {

    public static void main(String[] args) {
        String[] strArr = pickTwo("a"."b"."c");
    }

    public static <T> T[] toArray(T... args) {
        return args;
    }

    public static <T> T[] pickTwo(T a, T b, T c) {
        switch (ThreadLocalRandom.current().nextInt(3)) {
            case 0: return toArray(a, b);
            case 1: return toArray(a, c);
            case 2: return toArray(b, c);
        }
        throw newAssertionError(); }}Copy the code
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

at cn.ly.test.java.Test.main(Test.java:21)
Copy the code

This class does not report an error when compiled, but it throws a ClassCastException when run. PickTwo’s arguments are type arguments. The compiler cannot determine the type argument when passing this type argument to the toArray method. It can only create an Object[] array to hold the variable arguments. The return from pickTwo compiles to the point that we have inserted a String[] cast, but the actual type is Object[] and cannot be converted to String[].

6.6 Mandatory conversions to generic types should be converted to wildcard types

When casting, if the destination type is a generic type, it should be converted to the wildcard parameterized type of that type, not to the original type. The converted variable is then examined by the compiler.

if (o instanceofList) { List<? > l = (List<? >) o; }Copy the code