preface

Hello, everyone, I am Tang Yuan, today to bring you is “Java in the generics – details”, I hope to help you, thank you

Careful audience friends may have found that now the title is no longer an introduction, but a variety of detailed, detailed;

Because the previous ones were relatively simple, it would be appropriate to call them introductory;

Now the back of the slowly began to complicate, so called introduction is a bit of title party, so called detailed or detailed or advanced and so on

The article is purely original, personal summary is inevitable mistakes, if so, please reply in the comment section or background private letter, thanks

Introduction to the

The purpose of generics is to parameterize types, known as type parameters

Public void fun(String s); The parameter is of type String and is fixed

Public static

void fun(T T); public static

void fun(T T); The type of the parameter is the type of T, and it’s not fixed

From the String and T above, generics have a strong smell of polymorphism, but there is a difference between generics and polymorphism

Essentially, polymorphism is a feature, a concept, in Java, and generics are a real type;

directory

The following is a detailed description of generics in Java:

  • What are type parameters

  • Why generics

  • The evolution of generics

  • Type erasure

  • Application scenarios for generics

  • Wildcard qualification

  • Dynamic type safety

  • , etc.

Most of the examples in the text are introduced using generics in collections as an example, because they are used so much that everyone is familiar with them

The body of the

What are type parameters

A type parameter is the type of the parameter, which accepts the class as the actual value

In plain English, you can treat type parameters as parameters and the classes actually passed as arguments

For example, the type parameter E in ArrayList

is a parameter, and the class String in ArrayList

is an argument

If you’ve learned the factory design pattern, you can think of the ArrayList

here as a factory class, and then you just pass in the type parameters for what type of ArrayList you want, right

  • For example, if Integer is passedArrayList<Integer>class
  • For example, passing String isArrayList<String>class

Why generics

The main purpose is to improve code readability and security

Specific to start with the evolution of generics

The evolution of generics

In a broad sense, generics have been around for a long time, just implicitly;

List List = new ArrayList(); // Equivalent to ListList = new ArrayList<>();

But generics were fragile at the time, with poor readability and security (collections were not yet a big advantage over arrays).

First, there is no type checking when populating the data, which makes it possible to put Cat into the Dog collection

Second, when fetching, you need to cast. If you are lucky enough to put an object in the wrong collection (possibly on purpose), the runtime will raise an error cast exception (but the compilation will pass).

In JDK1.5, however, true generics (type parameters, represented by Angle brackets <>) appear;

For example, in the List

set class, E is the type parameter of the generic type. Because the set contains all the existing elements, it is replaced by E (T, S, k-key, v-value).

At this point, the program is more robust, readable and secure, and you can tell at a glance what you’re putting into it.

Now take List

List = new ArrayList<>(); To illustrate

First, when you populate the data, the compiler does its own type checking to prevent Cat from being put into Dog

Second, when we fetch the data, we don’t need to cast it manually; the compiler does the casting itself

If you’re careful, you might notice that since I have generics, I put Dog in, shouldn’t I take Dog out? Why does the compiler need to cast?

This brings us to the concept of type erasure

Type erasure

What is type erasure?

Type erasure means that when you assign a value to a type parameter

, the compiler erases the type of the argument to Object (assuming no qualifiers, which are covered below).

So here’s one thing to understand: a virtual machine has no concept of a generic type object; it sees all objects as ordinary objects

Take the following code for example

Erase the former

public class EraseDemo<T> {
    private T t;
    public static void main(String[] args) {}public T getT(a){
        return t;
    }
    public void setT(T t){
        this.t = t; }}Copy the code

After erasing

public class EraseDemo {
    private Object t;
    public static void main(String[] args) {}public Object getT(a){
        return t;
    }
    public void setT(Object t){
        this.t = t; }}Copy the code

And you can see that all the T’s become Object

EraseDemo

EraseDemo

EraseDemo

EraseDemo

EraseDemo



Correspondingly, if you have two arraylists, ArrayList

and ArrayList

, the compiler will erase both as ArrayLists

You can test it in code

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());// This will print true
Copy the code

What are the qualifiers mentioned above?

Qualifiers are used to define boundaries. If generics have a setting boundary, such as

, then erasing goes to the first boundary Animal class, not Object

The code above is used as an example to show the comparison before and after erasure

Erase the front:

public class EraseDemo<T extends Animal> {
    private T t;
    public static void main(String[] args) {}public T getT(a){
        return t;
    }
    public void setT(T t){
        this.t = t; }}Copy the code

After erasing:

public class EraseDemo {
    private Animal t;
    public static void main(String[] args) {}public Animal getT(a){
        return t;
    }
    public void setT(Animal t){
        this.t = t; }}Copy the code

Does the extends symbol here mean inheritance?

No, the extends here simply means that the former is a subclass of the latter and can be inherited or implemented

Extends is used only because the keyword is already built into Java and fits the situation

Creating a new keyword, such as sub, can cause problems with older code (such as code that uses sub as a variable)

Why erase it?

It’s not really a matter of wanting to erase, it’s a matter of having to erase

Because old code has no concept of generics, the purpose of erasures here is to make old code compatible, so that old code and new code can call each other

Application scenarios for generics

  • In the big picture:
    • Used in classes: called generic classes, the class name followed by < type parameter >, for exampleArrayList<E>
    • In methods: called generic methods, the return value of a method is preceded by a < type parameter >, for example:public <T> void fun(T obj)

Do you have abstract classes and methods in mind?

There are differences. Abstract classes and methods are related, but there is no connection between generic classes and generic methods

  • Focus on the class direction: Generics are used in collection classes, such asArrayList<E>

The generic approach is recommended for custom generics for two reasons:

  1. Use it independently of generic classes to make your code cleaner (no need to generalize the entire class for a small function)

  2. In generic classes, static methods cannot use type parameters; But static generic methods can

Wildcard qualification


,
,

  • <T>: This is the most commonly used, just plain type argument, just pass in the actual class to replace T when called, this oneThe actual class can be either T or a subclass of T

List

List = new ArrayList<>(); , where String is the actual class passed in instead of the type argument T

  • <? extends T>: This belongs to the wildcard qualifierSubtype qualification, i.e.,The actual class passed in must be T or a T subclass

At first glance, this looks a bit like a

type parameter, either putting T in or a subclass of T;

But there are many differences, which will be listed later

  • : This falls under the supertype qualification of the wildcard qualification, which means that the actual class passed in must be T or a superclass of T

  • : This is an unqualified wildcard, meaning it doesn’t know what type to put in it, so it doesn’t let you add to it. Extends T >)

Below is a table listing

,
,
a few more detailed differences

<T> <? extends T> <? super T>
Type erasure When an argument is passed in, it is erased as Object, but the compiler automatically converts it to T on GET Rub into T To the Object
Reference object A reference to an object of a subtype or parent type cannot be used, for example:List<Animal> list = new ArrayList<Cat>();/ / an error Can point a reference to an object of a subtype, such as:List<? extends Animal> list = new ArrayList<Cat>(); Can refer to an object of a parent type, such as:List<? super Cat> list = new ArrayList<Animal>();
Add data You can add data, T or a subclass of T Can’t Yes, T or a subclass of T

So let’s do this in code

Type erasure:

// 
      
        type. When an argument is passed, it is erased as Object, but when it gets, it remains the type of the argument
      
List<Animal> list1 = new ArrayList<>();/ / legal
list1.add(new Dog());/ / legal
Animal animal = list1.get(0); // There is no need for coercion, although the previous argument was erased as Object, but the compiler has already cast it internally at get

/ / 
       extends T> extends T> extends T> extends T> extends T>
List<? extends Animal> list2 = list1;/ / legal
Animal animal2 = list2.get(0); // We don't need strong rotation here, because we only erase to T.

/ / 
       wildcard qualification of the supertype, erased to Object
List<? super Animal> list3 = list1; / / legal
Animal animal3 = (Animal)list3.get(0); // Manual force is required because it is erased to Object
Copy the code

To refer to an object that points to a child or parent type:

// 
      
        type, cannot point to a child type or parent type
      
List<Animal> list = new ArrayList<Dog>();ArrayList< Animal> ArrayList
      

/ / 
       the wildcard qualification of a subtype, pointing to a subtype
List<? extends Animal> list2 = new ArrayList<Dog>();/ / legal

/ / 
       Wildcard qualification of the supertype, pointing to the parent type
List<? super Dog> list3 = new ArrayList<Animal>(); / / legal
Copy the code

Add data

// 
      
        type, you can add T or a subtype of T
      
List<Animal> list1 = new ArrayList<>();
list.add(new Dog());/ / legal

/ / 
       the wildcard of the extends > subtype that limits the addition of elements
List<? extends Animal> list2 = new ArrayList<Dog>();/ / right
list2.add(new Dog()); // Error: can't add element to inside

/ / 
       wildcard qualifier for supertypes. T or subtypes of T can be added
List<? super Dog> list3 = new ArrayList<Animal>();
list3.add(new Dog()); // It is valid to add elements of type T
list3.add(new Animal());// Error, cannot add element of parent type
Copy the code

The following is a clarification of the above test results

Let’s start with the

error


references can’t point to subtypes like List

List = new ArrayList

();


List

and List

have no relationship (similar to Java and Javascript).

Their relationship is shown below

The main reason for this design is type safety

So let’s say we can point List

to a subclass List

List<Animal> list = new ArrayList<Dog>();// Assume there is no error here
list.add(new Cat()); // The cat is in the dog
Copy the code

The second line shows that it is clearly wrong to put a cat in a dog, which goes back to a time before generics really existed (without generics, collections were unsafe to access data).

That is what the
point to a subtype? Such as the List
list = new ArrayList

();

To put it more simply, the reason is that the wildcard qualification appears to solve the problem of not pointing to subclasses

Of course, that’s as good as it gets. So let’s get down to the nitty-gritty

Since this wildcard constraint does not allow insertion of any data, when you point to a subtype, the list can only hold data from that collection, not add to it;

Naturally, this is type-safe, accessible only, not additive

Why
does not allow data to be inserted?

In fact, the reason for this is complementary to the previous modification of the reference object, which is combined to ensure that generics are type safe

Consider the following code

List<Animal> list = new ArrayList<>();
list.add(new Cat());
list.add(new Dog());
Dog d = (Dog) list.get(0); // An error is reported
Copy the code

As you can see, the inserted subclasses are messy, leading to error-prone extraction transitions (this is one of the drawbacks of generic

, but we might not have to be careful when we write)

But with
, it’s a different story

First, you can modify the referenced object so that the list points to a different Animal subclass

Next, you add data, not directly, but by pointing to the Animal subclass object

This ensures type safety

The code is as follows:

// Define a Dog collection
List<Dog> listDog = new ArrayList<>();
listDog.add(new Dog());

/ / 
       wildcard qualified generics refer to the above Dog collection
List<? extends Animal> list2 = listDog;
// If you want to add data to the list, you just need to call listDog, which is type-safe
listDog.add(new Dog());
// If you add it yourself, an error will be reported
list2.add(new Dog());/ / an error
Copy the code

<? extends T>It is usually used as a parameter, so we just need to pass in the generic object corresponding to the subtype, so that we can implement polymorphism in the generic type

why can be inserted?

Two reasons

  1. It can only insert T or a subclass of T
  2. Its lower bound is T

That means you can insert whatever you want, and I’ve restricted you to inserting type T or a subclass of T

So when I query, I can safely convert to T or T’s parent

The code is as follows:

List<? super Dog> listDog = new ArrayList<>();
listDog.add(new Dog());
listDog.add(new Cat()); / / an error
listDog.add(new Animal()); / / an error
Dog dog = (Dog) listDog.get(0); // The interior is erased as Object, so manual strong rotation is required
Copy the code

Why does the compiler automatically force the conversion when

gets to

? Super T>, you have to manually convert?

This is probably because the compiler isn’t sure what type the parent of the T you’re returning is, so you just leave it up to you, right

But if you point the listDog to a generic object of the parent class, and then insert another type into the generic object of the parent class, that’s a mess.

Such as:

List<Animal> list = new ArrayList<>();
list.add(new Cat()); / / the Cat
/ / points to Animal
List<? super Dog> listDog = list;
listDog.add(new Dog());
list.add(new Cat()); / / an error
list.add(new Animal()); / / an error

Dog dog = (Dog) listDog.get(0); // Error: conversion error Cat- "Dog

Copy the code

So the suggestion is
when adding data, try to focus on one place, do not add multiple places, like the above, or all in
, or both in

Dynamic type security check

This is primarily a type-safety check for compatibility with older code to prevent the error of inserting Cat into a Dog collection

This check occurs at compile time so that problems can be found early

The corresponding class is the Collections utility class, with the methods shown below

The following code

// Dynamic type safety checks to prevent problems like putting Dog into Cat collections when compatible with older code

// === before check === =
List list = new ArrayList<Integer>();
// Add no error
list.add("a");
list.add(1);
// Error only when used
Integer i = (Integer) list.get(0); // This is an error

// === after checking === =
List list2 = Collections.checkedList(new ArrayList<>(), Integer.class);
// An error is reported on insertion
list2.add("a"); // The error is detected at compile time
list2.add(1);

Copy the code

conclusion

What generics do:

  1. Improved type safety: Prevents various types of conversion problems
  2. Improve program readability: What you see is what you get, you see what you put in, and you know what you get out
  3. Improved code reuse: Multiple types of data (e.g. Dog,Cat for Animal) can be grouped together to increase code reuse

Type erasure:

When the generic T is passed an argument, the type of the argument is erased to a qualified type (that is,
), defaults to Object if the type is not qualified

Wildcard qualification:

  1. <? extends T>: wildcard qualification for subtypes,Query oriented, such as the consumer gathering scenario
  2. <? super T>: Wildcard qualification for the supertype,Add primarily, such as producer collection scenarios

Afterword.

And finally, thank you for watching. Thank you