current position:Home>How generics work

How generics work

2022-01-27 04:17:56 Travel to Erba to learn from scriptures

This is my participation 8 The fourth of the yuegengwen challenge 3 God , Check out the activity details :8 Yuegengwen challenge

Why generics

Generic type , Namely Type parameterization , in other words , The type of data is not fixed String,Integer, It is passed in as a parameter . such as :

// String Is the parameter , yes List Arguments to the constructor .
List<String> list = new ArrayList<>();
 Copy code 

Let's take a simpler example : Now there's a need , A class is required , You can do whatever you want String prefix "Hello", Followed by "Android", Then you can return the content itself . We implement the following :

 public class WrapperString {
    private String content;
    
    public WrapperString(String content) {
        this.content = content;
    }

    //  Prefix plus Hello
    public String prefixHello() {
        return "Hello" + content;
    }

    //  Suffix plus Android
    public String suffixAndroid() {
        return content + "Android";
    }

    //  Get the content itself 
    public String getContent(){
        return content;
    }
}
 Copy code 

All of a sudden , one day , need Integer Also want to achieve , Then we need a variable that can hold String, It can save Integer, What we know , Only Object 了 , So we modified the code :

public class WrapperObject { //  Change the name here 
    private Object content;

    public WrapperObject(Object content) {
        this.content = content;
    }

    //  Prefix plus Hello
    public String prefixHello() {
        return "Hello" + content;
    }

    //  Suffix plus Android
    public String suffixAndroid() {
        return content + "Android";
    }

    //  Get the content itself 
    public Object getContent() {
        return content;
    }
}
 Copy code 

Then let's look at using :

public void test() {
    String str = "test";
    WrapperObject wrapper = new WrapperObject(str);
    Object obj = wrapper.getContent();

    //  To obtain the length of the , A strong turn is needed ,mmp
    int length = ((String) obj).length();

    //  Since the strong turn , Then turn around , It's dangerous 
    int value = ((Integer) obj).intValue();
}
 Copy code 

We see , Although with Object Solved the problem , But it's troublesome to use , And it's dangerous . So is there a better solution , Yes ! Generic ! Let's go straight to the code :

//  Pass generic parameters ,T It's just a sign , Write casually .
public class WrapperObject<T> {
    private T content;

    public WrapperObject(T content) {
        this.content = content;
    }

    public String prefixHello() {
        return "Hello" + content;
    }

    public String suffixAndroid() {
        return content + "Android";
    }

    public T getContent() {
        return content;
    }
}
 Copy code 

good , Let's look at usage :

public void test() {
    String str = "test";
    //  When created here , Type passed in 
    WrapperObject<String> wrapper = new WrapperObject<>(str);

    //  Then the returned value   Is the type passed in when creating 
    String content = wrapper.getContent();

    //  To obtain the length of the , No forced rotation 
    int length = content.length();

    //  It's a direct error , Can't take String Convert to Integer
    int value = ((Integer) content).intValue();
}
 Copy code 

Modified code , Simple and clear , And it's very convenient to use , We will directly The required type is passed in , Like a parameter , When we get it, it's the type we need , This is type parameterization .

The use of generics

We can understand generics as a Larger than the general type , Less than Object type The type of , such as ,T It must be Object, however Object Is not necessarily T, therefore T Less than Object,String or Integer It can be T, however T Is not necessarily String or Integer, therefore T Greater than String, So it can be simply understood as :

General type < Generic < Object type

1 Generic classes and generic interfaces

The use of generic classes is simple , Like the one we created above WrapperObject A class is a generic class , It has a characteristic , Namely The class name is followed by a type enclosed in angle brackets , Of course, there can be multiple , such as :

public class Fuck<A, B, C> {
    private A a;
    private B b;
    private C c;
}
 Copy code 

Generic interfaces are the same as generic classes , Because the interface is also a kind of class , For example, the common List,Map Interface :

public interface List<E> extends Collection<E> {

}
 Copy code 

You can see , Generic interface / class It can also be inherited , It's no different from the general class .

Our common ArrayList,HashMap etc. , Are all generic classes , And they are all container classes , So called Generic containers .

2 Generic methods

Generic methods are also simple , We know , The method is a black box , The entry is the parameter , The exit is the return value , So we can focus on these two aspects .

Generics as arguments

It's simple , We need to add... Before the return type Type enclosed in angle brackets , Then in the parameter list, you can use it like a general type , such as :

public <T> void test(T t) {

}

public static <T> void test(T t) {

}
 Copy code 
Generics as return values

Just like the parameters , Add... Before the return type Type enclosed in angle brackets , Then change the return type to generic :

public <T> T test(T t) {
    return t;
}

public static <T> T test(T t) {
    return t;
}
 Copy code 

The bounds of generics

We know , Generics are less than Object Of , Then there are more than general types , Then it must represent a range , This range is the boundary , It is called boundary for short .

1 The upper bound of generics

If we have a need now , Define a function , The input parameters are two int Type value , Return the sum of two numbers , Too simple. , Let's write directly :

//  Find the sum of two numbers 
public int add(int a, int b) {
    return a + b;
}
 Copy code 

After that , Suddenly a float, What do I do , So we found the problem : Not just int There is the operation of addition , Other float,double,long etc. , Anyone who Number All subclasses of have addition , So we expand the object of this addition , Change it to Number, as follows :

public Number add(Number a, Number b) {
    return a + b; //  This is a simulation method , There is no such thing 
}
 Copy code 

Then let's use it :

public void test() {
    int a = 10;
    int b = 20;
    //  This line reports mistakes : Number Cannot assign a value to int.
    int c = add(a, b);
}
 Copy code 

So we'll just Number Change to T Is that OK , no way ! because T No, " Add " This operation , Only Number And its subclasses have addition , So we need one yes Number Generics of subclasses of , This is equivalent to limiting the upper bound of generics , That is, he designated his father , The code is as follows :

//  adopt <T extends Number> To specify the upper bound 
public <T extends Number> T add(T a, T b) {
    return a + b;
}

public void test() {
    int a = 10;
    int b = 20;
    //  What's coming in is int, And back again int, And because the incoming is Number Subclasses of , So you can use addition 
    int c = add(a, b);
}
 Copy code 

The upper bound of generics can be class , Interface , Even another generic , You can also have multiple generics , such as :

public <E, T extends E> T add(T a, T b) {

}
 Copy code 

Here's a little bit of attention , Two " Generic classes " There is no inheritance relationship between , such as :Integer yes Number Subclasses of , however List<Integer> No List<Number> Subclasses of . because List<Integer> The whole is a type .

Through the above example , We can see , Generics can represent a dynamic type , It can also represent a range .

2 The lower bound of generics

Generics have no lower bound , There is no lower boundary , There is no lower boundary ! If you want to use the lower bound , You can use wildcards ?, such as :

public void test(List<? super Integer> list) {
        
}
 Copy code 

Because wildcards are another knowledge point , There's not much nonsense here .

3 Generic multi boundary

We know that generics can specify upper bounds , We also know that generics are equivalent to general types , General types can inherit a parent class , Multiple interfaces can be implemented , So generics specify an upper bound , It can be regarded as inheriting a parent class , So can you specify an interface upper bound , Sure !

public <T extends Number & Serializable> void test() {

}
 Copy code 

We can specify one class and multiple interfaces , Equivalent to java Single inheritance and multiple implementations of classes . And there's a rule , Class must follow extends Back , Interface to use & Connect at the back . Interfaces can have multiple , Class can only have one . such as :

//  error , because Number It's a class , It needs to be closely followed by extends Back .
public <T extends Serializable & Number> void test() {

}

//  correct , You can have multiple interfaces 
public <T extends Number & Serializable & Comparable> void test() {

}
 Copy code 

Tips: kotlin It's hard to write generics , Need to use where Connect , such as

fun getName(origin: T): T where T : Number, T : Parcelable {
    return origin
}

class User where T : Number, T : Parcelable {

}
 Copy code 

equivalent java Version of the code is :

private <T extends Number & Serializable> T getName() {
    return null;
}

class User<T extends Number & Serializable> {

}
 Copy code 

How generics are implemented

1 Generic erase

java When the code is compiled , All generics will be deleted , Become generic free code , This is called Generic erase , So now that it's erased , How does the runtime know the actual type of a generic . because jvm When erasing , In addition to using all the original generics Object Replace outside , The corresponding type conversion code will also be added , such as :

public class TypeTest<T> {
    private T t;

    public TypeTest(T t) {
        this.t = t;
    }

    public T getContent() {
        return t;
    }
}

public void test2() {
    TypeTest<String> hello = new TypeTest<>("hello");
    String content = hello.getContent();
}
 Copy code 

After generic erase , That is to say jvm After compiling , It will be transformed into the following code :

//  There will be T erase , Corresponding T Replace with Object
public class TypeTest {
    private Object t;

    public TypeTest(Object t) {
        this.t = t;
    }

    public Object getContent() {
        return t;
    }
}

public void test2() {
    TypeTest hello = new TypeTest("hello");
    //  There is a direct strong rotation 
    String content = (String) hello.getContent();
}
 Copy code 

therefore , In the compiled code , There is no shadow of generics , in other words , Generics only in Edit code period effective , Just for coder Easy to write , therefore java The generic , Exactly jvm The generic , Also called Pseudo generics .

2 Signature attribute

We know above ,jvm All generics are erased during compilation , But we can get generic information through reflection , Reflection works at runtime , The runtime must be after compile time , So why can we get information about generics , The reason is that : Signature attribute .

Signature The attribute is .class file Property sheet set Inside An attribute , It records information about generics , Compile time , Will be the original Coder The generic information in the attribute is erased , Then record it to Signature Attributes inside , Reflection api You will get information about generics directly from this .

About reflection , Can be in here notice .

copyright notice
author[Travel to Erba to learn from scriptures],Please bring the original link to reprint, thank you.
https://en.cdmana.com/2022/01/202201270417507230.html

Random recommended