Generics are an important concept in Java, and just because we don’t use them often doesn’t mean we don’t need to learn them. In fact, a lot of awesome framework modules, which are often used generics, casually open a few source code can see.

 

1. What are generics?

We don’t use generics very often, but they are everywhere. For example, you can type a List and see classes that use generics. In short, what we see from time to time in those Angle brackets, where a single uppercase letter is substituted for a specific type, is generics.

Generics, or “parameterized types,” were introduced in JDK5 to parameterize a specific type (a type parameter) so that its specific type (a type argument) is known when it is used and called. This parameter type can be used in classes, interfaces, and methods, called generic classes, generic interfaces, and generic methods, respectively.

 

 

2. Why generics?

Why do we need generics? Why do we need a set

Let’s start with this code:

        List list = new ArrayList();
        // A List contains two different types of data: string and integer
        list.add("qqyumidi");
        list.add(100);
        for (int i = 0; i < list.size(); i++) {
            String name = (String) list.get(i); 
            System.out.println("name:" + name);
        }
Copy the code

The result will throw a ClassCastException: Java.lang.Integer cannot be cast to java.lang.String exception, because the type conversion failed, but no problem is detected at compile time. Unable to red before compilation).

Without generics, when we add an Object to a collection, the collection does not remember its type, so it is stored as Object. So we have to cast them one by one when we pull them out. Once cast, if the entire collection is of the same type, it is fine, but if a different type is added, a ClassCastException immediately occurs.

Generics were born to expose this problem at compile time without ClassCastException occurring at run time.

 

The collection after using generics

Now that generics solve this problem, let’s see how generics solve this problem, or is this code just specifying the type when we initialize the List

        List<String> list = new ArrayList<>();
        // Add both string and integer data
        list.add("qqyumidi");
        list.add(100);
        for (int i = 0; i < list.size(); i++) {
            String name = list.get(i);
            System.out.println("name:" + name);
        }
Copy the code

Add (100) is a red error at compile time. By specifying the type, you limit the list to only String elements. Since you can’t add any other types, you don’t need to cast them when you pull them out.

 

 

3. Use of generics

Generics can be used in three ways: generic classes, generic interfaces, and generic methods

(1) a generic class

It’s faster to create a generic class:

package com.lzh;
public class Box<T> {
    private T data;
    public Box(T data) {
        this.data = data;
    }
    public T getData(a) {
        returndata; }}Copy the code

The only difference is that the name of the class is followed by a **<T>**, and the properties and methods in the class are replaced by T, which is the generic type. There are a lot of different letters for this generic, E, V, K, T, but there’s not much difference, and I’ll talk about that later.

We then initialize different similar parameters to the generic class to see if their class names are different.

package com.lzh;
public class Test {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>("giao");
        Box<Integer> integerBox = new Box<>(250);
        System.out.println("stringBox class:" + stringBox.getClass());
        System.out.println("integerBox class:"+ integerBox.getClass()); System.out.println(stringBox.getClass() == integerBox.getClass()); }}Copy the code

It turns out that there is no difference between the two, no matter what the actual parameters of its generic type are, it is a Box class. And we found that it has only one constructor, but it can pass strings or integers to successfully construct objects.

So we can see that in fact this generic type is just like our normal new object, except that we pass a “parameter”

in Angle brackets and replace all the generic positions (such as the letter T) in the generic class with the passed parameters.

So we don’t have to think of generic classes as being too complicated, just passing an extra argument in Angle brackets along with a new object.

Note: Generic types can only be reference types (that is, object types), not simple types such as int, long, etc.

 

② Generic interfaces

Generic interfaces are defined and used in much the same way as generic classes

Create a very simple interface that defines only one simple method.

package com.lzh;
public interface Generator<T> {
    public T getInfo(a);
}
Copy the code

But when a class implements it, there are two different situations:

Case 1: If you implement a generic interface without passing a generic argument, is the class defined the same as a generic class or is equivalent to a generic class

package com.lzh;
public class WaterGenerator<T> implements Generator<T> {
    @Override
    public T getInfo(a) {
        return null; }}Copy the code

Case 2: when a generic interface is passed a generic argument, it is just a normal class implementing an interface. In its eyes, all T generics of the method in the interface are replaced by the String argument, so the method implemented in the class is also replaced by the String argument. You can say that this class is a normal class, and it has nothing to do with generics.

package com.lzh;
public class WaterGenerator implements Generator<String> {
    @Override
    public String getInfo(a) {
        return null; }}Copy the code

 

③ Generic methods

There’s a misconception about this generic method, some people think that the T in a generic class is a generic method, but it’s not, including the method in the generic class, that’s not a generic method, it’s just a method in a generic class.

It’s hard to tell, but generic methods also have one identifying difference: there is a <T> between public and the return value.

If there is no <T>, then the T of any method in a generic class will be marked in red, because there is no class named T. You can think of <T> as a declaration of generics, and you can only use T in it if you annotate <T>.

A generic method does not depend on a generic class, so the declared

is applied to the method, for example:

package com.lzh;
public class Person {
    public <T> T getSelf(T param) {
        returnparam; }}Copy the code

This makes it easy to distinguish between generic methods and methods in generic classes.

In addition, static methods do not have access to generics on a class, so static methods that use generics, even in a generic class, need to use generic methods.

 

④ The difference between generic wildcards

When we define generics, we often see different wildcards such as T, E, K, V, etc. What is the difference between them?

In fact, they are all wildcards and make no difference to Java, even if we can choose one from A to Z, without affecting the use of programs and generics.

It’s just a convention, like the hump variable, and it doesn’t matter if we don’t follow it, but good programmers follow it.

T: Type: indicates the specific Java Type

K V: Key Value: indicates the Key and Value in a Java Key/Value pair

E: Element, for the Element in the set

N: Number, representing numeric types (such as Integer and Long)

? : indicates an indeterminate Java type

And then there’s the big hole, which is T and T, right? Because A-Z and T are the same, but T and T? But it’s not a thing.

T is the identified Java type,? Is an indeterminate Java type.

If there is one difference to be made, the biggest difference is that the question mark is mostly used as a parameter on generic methods and cannot be used to define generic classes and methods.

A brief explanation of my own understanding of them:

T and? The biggest difference is consistency. T is a known type. In a generic class, as long as we declare T, then when we use the class, all the positions of T are replaced by the type parameter we pass in. The same is true for generic methods, so we can use the type of T inside the method, or if it is a collection type, we can take each object separately and assign it to T:

    public <T> T getSelf(T param) {
        T t = param;
        return t;
    }
Copy the code

But? It doesn’t, it represents an unknown type, so we can’t define it in a method, okay? Type that can only operate on objects that it takes as parameters. Since it is an unknown type, it cannot be used to declare generics on generic classes.

 

It’s a little vague, but we just need to know that the question mark is most commonly used in the following scenario

⑤ Wildcard boundary

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

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

When we need a method to receive arguments to a List, and the elements in the List can be subclasses or superclasses of a kind, we can finally use it. Okay? .

To illustrate the scenario, let’s say we have a generic class with a method like this:

    public void doWord(List<Animal> animal)
Copy the code

It takes a List<Animal> argument, and we don’t care what it does, but now we need to pass in List<Dog> and call it, Dog is a subclass of Animal.

You’ll find that you’ve failed, because the List is still the same List, but because the generics are different, you can’t call them, even though they’re parent-child classes.

Here you can use question marks and boundary keywords:

    public void doWord(List<? extends Animal> animal)
Copy the code

In this way, we can pass in List<Dog> and execute the method. The super keyword is used similarly, except that the scope is the parent of the specified class.

 

6 T and? Other differences between

T and? I was a little confused by the end, so I tried to sort out the differences as best I could.

(1) T does not have the super keyword

(2) T extends XX and? The extends XX class has similar effects, but T needs to be defined in advance, both for the generic class and for the generic method, while? It can be used anywhere.

(3) All T’s in a method are uniform. No matter how many T’s there are, they are the same class at runtime. They are not unified, they can be different from each other.

(4) T is used in generic classes and generic methods,? Used for declarations or parameters in methods. (Declarations are declarations of variables)

 

 

*4. Type erasure

About type erase, specific can understand this article, I write is also equivalent to reading feelings —

Java generics type erasure and type erasure problems: www.cnblogs.com/wuqinglong/…

① Java generics implementation method: type erasure

Java generics are pseudo-generics because all generic information is erased during compilation.

A List<Object> or List<String> type defined in code will become a List when compiled.

How do you prove this?

In the previous example, we used new arrays of two different generics, and then getClass compared them and found that they were the same class, so that was one way to prove it. Prove that the generic types have been erased, leaving only the primitive types.

The alternative is to define an ArrayList<Integer> array, which normally cannot add string elements, but when we add them in reflection mode, we will find that it can store strings, indicating that the Integer generic instances have been erased after compilation.

 

② What is the original type

The erased class becomes a primitive type, and this primitive type is usually Object, which means that at compile time all T’s in the generic class are replaced with Object, so we can add different types of data by reflection.

If it is a bounded generic, such as Pair<T extends Comparable>, its primitive type is the boundary itself Comparable.

 

③ Problems caused by type erasure and solutions

For some reason, Java can’t implement true generics, and only pseudo-generics can be implemented with type erasure. This doesn’t cause type bloat, but it does introduce some new problems, so SUN has some restrictions on these problems to avoid errors.

For example, the Java compiler checks the type of the generics in the code before performing generic erasure, preventing us from adding String elements to ArrayList<Integer>.

This check can be better understood by using this example:

public class Test {  
    public static void main(String[] args) {  
        // Simply declare a generic ArrayList
        ArrayList<String> list1 = new ArrayList();  
        list1.add("1"); // The compiler passes
        list1.add(1); // Error compiling
        String str1 = list1.get(0); // The return type is String
        // ArrayList with generics is declared without generics
        ArrayList list2 = new ArrayList<String>();  
        list2.add("1"); // The compiler passes
        list2.add(1); // The compiler passes
        Object object = list2.get(0); // The return type is Object, which has nothing to do with String
        
        new ArrayList<String>().add("11"); // The compiler passes
        new ArrayList<String>().add(22); // Error compiling
        String str2 = new ArrayList<String>().get(0); // The return type is String}}Copy the code

From the above example, we can see that type checking is for variable types. If a variable is declared without a generic type, then the default is Object, regardless of whether the type it is actually new from is generic or not.

Java replaces all T’s with Object’s at compile time, and stores generic types. When reflected or evaluated, the stored generic types are cast, so that we can use them directly.

 

 

 

References:

Java generics:

www.cnblogs.com/coprince/p/…

Talk about it – Wildcards T, E, K, V, JAVA generics? :

Juejin. Cn/post / 684490…

Type erasure:

www.jianshu.com/p/2bfbe041e…