What are generics

Generics are a new feature in Java SE 1.5 that can accommodate many, many different types.

The nature of generics is parameterized typing, which means that the data type being operated on is specified as a parameter. This parameter type can be used in the creation of classes, interfaces, and methods, called generic classes, generic interfaces, and generic methods, respectively.

Why generics

1. Use generics to write more flexible and generic code

2. Generics advance code security checks to compile time

Using generics allows the compiler to move the runtime ClassCastException to compile-time by checking for legitimate container insertions with the type parameters passed in

    // Use collections without generics
    public static void noGeneric(a) {
        ArrayList names = new ArrayList();
        names.add("Zhang");
        names.add("Bill");
        names.add(123);  // Compile properly
    }
 
    // Use collections with generics
    public static void useGeneric(a) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Zhang");
        names.add("Bill");
        names.add(123);  // Failed to compile
    }
Copy the code

3. Generics can eliminate type casting

Prior to JDK1.5, Java containers were implemented by casting types upcast to Object, so manual casts were required when they were taken out of the container. With generics, because the compiler knows the specific type, the cast is automatically performed at compile time, making the code more elegant.

    // Use collections without generics
    public static void noGeneric(a) {
        ArrayList names = new ArrayList();
        names.add("Zhang");
        names.add("Bill");
        names.add(123);
        String name = (String) names.get(2);  // A cast is required
    }
 
    // Use collections with generics
    public static void useGeneric(a) {
        ArrayList<String> names = new ArrayList<>();
        names.add("Zhang");
        names.add("Bill");
        String name = names.get(1);      // No casting required
    }
Copy the code

And the second method only throws an exception at runtime

How to use generics

1. A generic class

Define a generic class, add a pair of Angle brackets after the class name, and fill in the type parameters in Angle brackets. There can be more than one parameter, separated by commas

public class GenericClass<ab.a.c> {}Copy the code

Of course, there is a specification for the following parameter type, which cannot be written arbitrarily as I did above. Usually, we use uppercase single letter for type parameters:

T: any type type E: type of the element in the set Element K: key-value key V: key-value valueCopy the code

Let’s write an example to try:

public class GenericClass<T> {
    private T value;
    public GenericClass(T value) {
        this.value = value;
    }
    public T getValue(a) {
        return value;
    }
    public void setValue(T value) {
        this.value = value; }}Copy the code
GenericClass<String> name = new GenericClass<>("Zhang");
System.out.println(name.getValue());
 
GenericClass<Integer> number = new GenericClass<>(123);
System.out.println(name.getValue());
Copy the code

The printed result is zhang SAN 123 is no problem.

2. Generic interfaces

The definition of a generic interface is similar to that of a generic class

public interface GenericInterface<T> {
    void show(T value);
}
Copy the code

We are still implementing String and Integer:

public class StringShowImpl implements GenericInterface<String> {
    @Override
    public void show(String value) { System.out.println(value); }}Copy the code
public class NumberShowImpl implements GenericInterface<Integer> {
    @Override
    public void show(Integer value) { System.out.println(value); }}Copy the code

It is not allowed to write generic types as follows:

GenericInterface<String> genericInterface = new NumberShowImpl();  // Error compiling
Copy the code

Or do not specify the type at all, then new can be of any type:

GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();
Copy the code

3. Generic methods

Generic methods can be defined in either a generic class or a normal class.

If we have a method whose arguments may be of different types, we may have to override them instead of generics:

public class GenericFun {
    public void show(String value) {}public void show(Integer value) {}}Copy the code

But if we use generics, one function will do,

Define a generic method by adding Angle brackets before the return value and after the modifier and filling in the type parameters:

public class GenericFun {
    public static <T> void show(T value) {}}Copy the code

If you can define generic methods, try to define generic methods, that is, if generic methods solve the problem, try not to define generic classes.

Here is the call:

GenericFun.show(123);
//GenericFun.
      
       show(123); Can be omitted
      
Copy the code

But what if our business requirement in show() is that the arguments must all be numeric?

If you write “show”, the program can be compiled, but when processing business, there will be strong exceptions like the above, so we need to limit the parameter type of this generic method.

You open Integer,Double, etc. numeric wrapper class source code:

public final class Integer extends Number implements Comparable<Integer> {
Copy the code
public final class Double extends Number implements Comparable<Double> {
Copy the code

They all inherit from the Number class, so we can qualify them like this:

public class GenericFun {
    /** * T extends Number: specifies that the type T binds to must be a subclass of Number **/
    public static <T extends Number> void show(T value) {}}Copy the code

The qualified type can be an interface or a class, and it can be more than one, connected by an ampersand,

And if you qualify both classes and interfaces, the class must come first, what does that mean?

The Comparable interface is also implemented by Integer and Double, so let’s add another qualification:

public static <T extends Number & Comparator> void show(T value) {}Copy the code

We have multiple qualifiers, and the class is placed in front of the interface. If you don’t follow these rules, the compilation will fail.

To do this, you pass in parameters that are subclasses of “Number” and that implement the Comparator interface.

How are generics implemented

Having said some basic usage, let’s look at how Java is implemented as generics.

We all know that our classes have to be compiled into class files for the JVM to run, so what are the types of generic parameters in the compiled class file that we define for a generic class?

GenericClass (GenericClass) ¶ GenericClass (genericClass.class) ¶ GenericClass (genericClass.class) ¶ For the JVM, there is no such thing as generics, only concrete types, so how does Java implement it underneath?

Type erasure

Type erasure () {type erasure () {type erasure ();}}

When a generic class is compiled: 1: Remove the Angle brackets after the class name and the type parameters in the Angle brackets. The remaining name is the corresponding class name 2: For a type in a class that uses a type parameter variable, replace it with Object if there is no type qualification, replace it with the qualified type if there is a type qualification, and replace it with the first qualified type if there are multiple type qualifications.Copy the code

So what does our generic class above actually compile according to this rule?

public class GenericClass {
    private Object value;
    public GenericClass(T value) {
        this.value = value;
    }
    public Object getValue(a) {
        return value;
    }
    public void setValue(Object value) {
        this.value = value; }}Copy the code

Let’s take a look at the compiled bytecode file:

Compiled from "GenericClass.java"public class com.example.demo.test1.GenericClass<T> { public com.example.demo.test1.GenericClass(T); descriptor: (Ljava/lang/Object;) V Code: 0: aload_0 1: invokespecial#1 // Method java/lang/Object."
      
       ":()V
      
       4: aload_0
       5: aload_1
       6: putfield      #2 // Field value:Ljava/lang/Object;
       9: return
 
  public T getValue();
    descriptor: ()Ljava/lang/Object;
    Code:
       0: aload_0
       1: getfield      #2 // Field value:Ljava/lang/Object;
       4: areturn
 
  public void setValue(T); descriptor: (Ljava/lang/Object;) V Code: 0: aload_0 1: aload_1 2: putfield#2 // Field value:Ljava/lang/Object;
       5: return
}
Copy the code

Do I understand? Let’s define the type of a generic class and see what happens:

GenericClass<T extends Number>
Compiled from "GenericClass.java"public class com.example.demo.test1.GenericClass<T extends java.lang.Number> { public com.example.demo.test1.GenericClass(T); descriptor: (Ljava/lang/Number;) V Code: 0: aload_0 1: invokespecial#1 // Method java/lang/Object."
      
       ":()V
      
       4: aload_0
       5: aload_1
       6: putfield      #2 // Field value:Ljava/lang/Number;
       9: return
 
  public T getValue();
    descriptor: ()Ljava/lang/Number;
    Code:
       0: aload_0
       1: getfield      #2 // Field value:Ljava/lang/Number;
       4: areturn
 
  public void setValue(T); descriptor: (Ljava/lang/Number;) V Code: 0: aload_0 1: aload_1 2: putfield#2 // Field value:Ljava/lang/Number;
       5: return
}
Copy the code

The comment was made by the JVM, not by me, and clearly indicates the type to be returned. Object has been changed to Number.

We can also see this through a simpler little example:

	public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();
        System.out.println(c1 == c2);
    }
Copy the code

The result of this case is true, which means that ArrayList and ArrayList are of the same type.

	public class GenericDemo {
        public static void main(String[] args) {
            GenericClass<String> genericClass = new GenericClass<>("Zhang");
            //genericClass.setValue(123); // Compile errorString value = genericClass.getValue(); }}Copy the code

When we setValue, compilation will be red line, so when we getValue(), why didn’t we cast it?

ect object = new Object();
String s = (String) object;
Copy the code

If we write this separately, the program must require us to cast! What’s going on here?

Let’s take a look at the bytecode file of our GenericDemo class:

Compiled from "GenericDemo.java"
public class com.example.demo.test1.GenericDemo {
  public com.example.demo.test1.GenericDemo();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1 // Method java/lang/Object."
      
       ":()V
      
       4: return
 
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: new           #2 // class com/example/demo/test1/GenericClass
       3: dup
       4: ldc           #3 // String Zhang SAN
       6: invokespecial #4 // Method com/example/demo/test1/GenericClass."
      
       ":(Ljava/lang/Object;) V
      
       9: astore_1
      10: aload_1
      11: invokevirtual #5 // Method com/example/demo/test1/GenericClass.getValue:()Ljava/lang/Object;
      14: checkcast     #6 // class java/lang/String
      17: astore_2
      18: return
}
Copy the code

One of the

5. Precautions for using generics

1. Type parameters cannot be instantiated with primitive type data

GenericClass<Integer> g1 = new GenericClass<>(123);
GenericClass<int> g2 = new GenericClass<>(123);// Error compiling
Copy the code

If primitive types are allowed, then there is the problem of converting primitive data types to class types.

2. Runtime type checking does not apply to generics

GenericClass<Integer> g1 = new GenericClass<>(123);
if(g1 instanceof GenericClass<Integer>) {}   // Error compiling
Copy the code

As mentioned earlier, the JVM does not have the concept of GenericClass, meaning that there is no GenericClass, so only G1 instanceof GenericClass can be compared.

3. Arrays of generic types cannot be instantiated

GenericClass[] g1 = new GenericClass[5];
GenericClass<String>[] g2 = null;
GenericClass<String>[] g3 = new GenericClass<>[5];  // Error compiling
Copy the code

There is a principle of generic design involved here: if a piece of code does not raise “unchecked warnings” at compile time, the program will not raise a ClassCastException at run time. This means that if there is no warning at compile time, then the runtime will not throw a cast exception.

Object[] objects;
GenericClass<String>[] g = new GenericClass<>[5];  // There is a compilation error, we assume it is allowed
objects = g;
objects[0] = "123";
objects[1] = 123;
Copy the code

Object =g (String =g, String =g, String =g, String =g, String =g, String =g); A cast exception must be thrown, which would violate the principles of generic design, so this is not allowed.

Then we can use the following definition:

GenericClass<String>[] g = new GenericClass[5];
g[0] = new GenericClass<String>();
g[0] = new GenericClass<Integer>(); // Compile error
Copy the code

4. Type parameters cannot be instantiated

public static <T> void show(T value) { 
    value = new T();   // Compile error
}
Copy the code

We instantiated the type parameter in the generic method above, which was not passed at compile time.

Because we talked about type erasers, we’re just going to new an Object, so if we want to do that we’re going to have to new whatever the type defines, so if the type defines String, then it’s going to automatically new String(), if it defines Integer, it’s going to automatically new Integer(), But Java is currently unimplementable, and if you can’t implement it, you can’t use it.

5. Static methods cannot use type parameters defined in the class context

public class GenericClass<T> {
    public void test1(T value) {}
    public static void test2(T value){}  // Compile error
}
Copy the code

Static methods are not allowed to use type parameters defined by generic classes. If you want to use them, you can only define static methods as generic methods:

public static <T> void test2(T value){}
Copy the code

There is no correlation between the T in a static method and the T in a generic class.

6. Use of generics in exceptions

public class GenericException<T> extends Throwable {}// Compile error
Copy the code

Generic classes are not allowed to inherit from exception base classes.

public class GenericException<T> {
    public void test(a){
        try{}catch (T e) {   // Compile error}}}Copy the code
public class GenericException<T extends Throwable> {
    public void test(a){
        try{}catch (T e) {   // Compile error}}}Copy the code

None of this is allowed, so what do we do if we want to use exceptions with exceptions?

public class GenericException<T extends Throwable> {
    public void test(T e) throws Throwable {
        throwe; }}Copy the code

Yes, we are allowed to throw generic exceptions.

7. Type erasure conflicts

public class GenericClass<T> {
    public boolean equals(T obj) {  // Compile error
        return super.equals(obj); }}Copy the code

In this case, we override equals() with a type argument. The result is an error because the equals() argument is changed to Object obj, which is exactly the same as the previous equals() method.

Instead, we can limit the class to subclasses of Object:

public class GenericClass<T extends Number> {
    public boolean equals(T obj) {
        return super.equals(obj); }}Copy the code

8. Another generics principle

Another generics principle: to support type-erase conversions, it is necessary to enforce that a class or type parameter cannot be subclassed of two interface types, and that the two interfaces are different parameterizations of the same interface.

What does that mean? Let’s explain it through a case:

interface Show<T> {
    void show(T value);
}
Copy the code
public class BaseShowImpl implements Show<String> {
    @Override
    public void show(String value) { System.out.println(value); }}Copy the code
public class SubShowImpl extends BaseShowImpl implements Show<Long> {}  // Compile error
Copy the code

SubShowImpl (String, Long); SubShowImpl (BaseShowImpl); SubShowImpl (BaseShowImpl);

Type wildcard

Here is a brief example of a type wildcard requirement:

public class Animal {
    private String type;
    public Animal(String type) {
        this.type = type;
    }
    public void show(a) {
        System.out.println("type:"+ type); }}Copy the code
public class Dog extends Animal {
    public Dog(String type) {
        super(type); }}Copy the code
public class Cat extends Animal {
    public Cat(String type) {
        super(type); }}Copy the code
public class BigCat extends Cat {
    public BigCat(String type) {
        super(type); }}Copy the code

I’m going to define an Animal class, and I’m going to subclass Dog, and I’m going to subclass Cat, and I’m going to subclass Cat, BigCat.

We instantiate some data in the test class to prepare the test:

public class GenericDemo {
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        animals.add(new Animal("Animal"));
 
        List<Cat> cats = new ArrayList<>();
        cats.add(new Cat(Muppet Cat));
        cats.add(new Cat("English cat"));
 
        List<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog("Golden retriever"));
        dogs.add(new Dog(labrador));
 
        List<BigCat> bigCats = new ArrayList<>();
        bigCats.add(new BigCat("Big Muppet Cat"));
        bigCats.add(new BigCat(The British Short Cat));
 
        List<String> strList = new ArrayList<>();
        strList.add("xxx"); }}Copy the code

So we now have a requirement: define a method, pass in a List object that holds the Animal class or its subclasses, iterate through the collection, pull out the object, and call the show() method.

We don’t need generics to implement the following, which is very simple:

	public static void showAnimalNoGeneric(List list) {
        for (int index = 0; index < list.size(); index ++) { Animal animal = (Animal) list.get(index); animal.show(); }}Copy the code

But what’s wrong with that? If I pass strList to this method:

Yes, an error is immediately reported, which is one of the reasons we need generics: code safety checks.

Here we use generics:

	public static void showAnimalUseGeneric(List<Animal> list) {
        for(Animal animal : list) { animal.show(); }}Copy the code

That should be it, right? But when we use:

showAnimalUseGeneric(animals);
showAnimalUseGeneric(cats);  // Compile error
showAnimalUseGeneric(dogs);  // Compile error
showAnimalUseGeneric(bigCats);  // Compile error
Copy the code

Generics subtypes fail to compile. This is the problem with generics subtypes.

Cat[] catArr = new Cat[5];
Animal[] animalsArr = catArr;
animalsArr[0] = new Animal("XXX");
Copy the code

Cat[] = Animal[] = Animal[] = Animal[] = Animal[] = Animal[] = Animal[]

One of the inappropriacies of this array design, so it’s avoided in generic design, is the generic subtype problem.

Type wildcard

So if we can’t say Animal, what should we say? This is where the wildcard comes in, and there’s no problem writing it like this:

	/** Use generics * type wildcards * get objects: can only get Object * Store objects: disallow **/
    public static void showAnimalUseGeneric(List
        list) {
        for (int index = 0; index < list.size(); index ++) { Animal animal = (Animal) list.get(index); animal.show(); }}Copy the code

But this is still a problem, because there are no restrictions on type parameters, and when you pass the strList it will compile fine, and when you run it it will return an error.

Generic upper and lower bounds

So this brings us to a new point, the upper and lower bounds of generics.

	/** Use generics * type wildcard upper limit * get object: get Animal type, can limit type parameter * Store object: disallow **/
    public static void showAnimalUseGenericOne(List<? extends Animal> list) {
        for (int index = 0; index < list.size(); index ++) { Animal animal = list.get(index); animal.show(); }}Copy the code

List<? Extends Animal> list: extends Animal> list: extends Animal> list: Extends Animal> list: Extends Animal> list: Extends Animal> list: Extends Animal> list: Extends Animal> list The type that can be matched is either the specified class or interface, or a subclass or subinterface.

If you pass in strList, you’re going to get a compilation error.

Where there are limits, there are limits. Let’s look at generic limits:

	/** Use generics * type wildcard lower limit * get Object: get Object type, can restrict type parameters * store Object: can only store a limited lower limit specified type **/
    public static void showAnimalUseGenericTwo(List<? super  BigCat> list) {
        for (int index = 0; index < list.size(); index ++) { Animal animal = (Animal) list.get(index); animal.show(); }}Copy the code

List<? Super Animal> list < span style = “box-sizing: border-box; color: RGB (74, 74, 74); The type that can be matched is either the specified class or interface, or its parent class or parent interface.

At the lower end of the scale, if we were idle BigCat, then Dog would not fit the bill:

showAnimalUseGenericTwo(animals);
showAnimalUseGenericTwo(cats);
showAnimalUseGenericTwo(dogs);  // Compile error
showAnimalUseGenericTwo(bigCats);
Copy the code

So much for generics!