This is the third day of my participation in the August Text Challenge.More challenges in August

Why generics

By generics, we mean parameterization of the type, that is, the type of the data is not fixed String, Integer, but as parameters. Such as:

// String is the argument to the List constructor. List<String> list = new ArrayList<>();Copy the code

Let’s look at a simpler example: There is a requirement for a class that can precede any String with “Hello” followed by “Android” and then return the content itself. We implement as follows:

public class WrapperString { private String content; public WrapperString(String content) { this.content = content; } public String prefixHello() {return "Hello" + content; } public String suffixAndroid() {return content + "Android"; } public String getContent(){return content; }}Copy the code

All of a sudden, Integer was needed, so we needed a variable that could hold both String and Integer, and all we knew was Object, so we changed the code:

Public class WrapperObject {private Object content; public WrapperObject(Object content) { this.content = content; } public String prefixHello() {return "Hello" + content; } public String suffixAndroid() {return content + "Android"; } public Object getContent() {return content; }}Copy the code

Then let’s look at usage:

public void test() { String str = "test"; WrapperObject wrapper = new WrapperObject(str); Object obj = wrapper.getContent(); MMP int length = ((String) obj).length(); Int value = ((Integer) obj).intValue(); }Copy the code

As we can see, using Object solves the problem, but it is cumbersome and dangerous to use. Is there a better solution? Yes! Generic! Let’s go straight to the code:

// Pass the generic argument, T is just a token, write whatever you want. 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 the code

Ok, let’s look at usage:

public void test() { String str = "test"; WrapperObject<String> wrapper = new WrapperObject<>(STR); String Content = wrapper.getContent(); Int length = content.length(); Int value = ((Integer) content).intValue(); }Copy the code

The modified code is simple, straightforward, and very easy to use. We just pass in the type we want, just like a parameter, and when we get it, that’s the type parameterization.

The use of generics

We can think of generics as a type that is larger than the general type and smaller than Object, for example, T must be Object, but Object doesn’t have to be T, so T is less than Object, String or Integer can be T, but T doesn’t have to be String or Integer, So T is greater than String, so it can be simply understood as:

Generic < generic < Object type

Generic classes and generic interfaces

WrapperObject (WrapperObject, WrapperObject, WrapperObject, WrapperObject, WrapperObject)

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

Generic interfaces are the same as generic classes, because interfaces are also classes, such as the common List and Map interfaces:

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

}
Copy the code

As you can see, generic interfaces/classes can also be inherited, just like normal classes.

All of our common ArrayList, HashMap, etc., are generic classes, and they’re all container classes, so they’re called generic containers.

2 generic methods

Generic methods are also very simple. We know that the method is a black box, the entry is the parameter, and the exit is the return value, so we just focus on those two aspects.

Generics as parameters

Simply, we need to add Angle brackets before the return type, and then we can use it in the argument list like a normal type, for example:

public <T> void test(T t) {

}

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

}
Copy the code
Generic as the return value

As with arguments, the return type is preceded by Angle brackets and then changed to generic:

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

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

The boundary of the generic type

We know that generics are smaller than Object and larger than ordinary types, so they must represent a range. This range is called a boundary, or a boundary for short.

The upper bound of the generic type

Let’s say we have a requirement that we define a function that takes two ints and returns the sum of two numbers.

Public int add(int a, int b) {return a + b; }Copy the code

Float, double, long, etc. All subclasses of Number have addition, so we extend the addition object to Number as follows: float, double, long

public Number add(Number a, Number b) { return a + b; // This is a simulation method, there is no actual}Copy the code

Then we use it:

public void test() { int a = 10; int b = 20; // Error: Number cannot be assigned to int. int c = add(a, b); }Copy the code

So can we just change the Number to T? No! Since T does not add, only Number and its subclasses add, we need a generic of Number’s subclass. This is equivalent to limiting the upper bound of the generic, i.e., specifying its parent, as follows:

Public <T extends Number> T add(T a, T b) {return a + b; } public void test() { int a = 10; int b = 20; C = add(a, b); c = add(a, b); }Copy the code

The upper bound of a generic can be a class, an interface, or even another generic, or there can be multiple generics, such as:

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

}
Copy the code

Note that there is no inheritance relationship between the two “generic classes”. For example, Integer is a subclass of Number, but List

is not a subclass of List

. Because List

is all one type.


From the above examples, we can see that generics can represent either a dynamic type or a range.

The lower bound of the generic type

Generics have no lower bounds, no lower bounds, no lower bounds! If you want to use a lower bound, you can use a wildcard, okay? , such as:

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

Because wildcards are another topic, I won’t talk too much here.

Generic types with multiple boundaries

We know that generics can specify an upper bound, and we know that generics are equivalent to general types. General types can inherit a parent class, which can implement multiple interfaces, so generics specify an upper bound, which can be seen as inheriting a parent class. Can we specify an interface upper bound?

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

}
Copy the code

We can specify a single class and multiple interfaces, equivalent to single inheritance and multiple implementations of Java classes. And one of the rules is that classes must follow extends immediately and interfaces use an & concatenation. There can be multiple interfaces and only one class. Such as:

// Error because Number is a class and needs to be followed by extends. Public <T extends Serializable & Number> void test() {} Public <T extends Number & Serializable & Comparable> void test() {}Copy the code

Tips: Kotlin’s generic approach is a bit tricky, requiring where concatenation, for example

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

class User where T : Number, T : Parcelable {

}
Copy the code

The equivalent Java version of the code is:

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

class User<T extends Number & Serializable> {

}
Copy the code

How generics are implemented

Generic erase

When Java code is compiled, it removes all generics to code that does not contain generics. This is called generic erasure, so how does the runtime know the actual type of generics? In addition to replacing all generic types with Object, the JVM also adds the corresponding type coercion code, 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 the code

After the generics are erased, which is when the JVM compiles, the code is changed to the following:

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"); String content = (String) hello.getContent(); }Copy the code

Therefore, in the compiled code, there is no shadow of generics, that is to say, generics only take effect in the editing code period, is to facilitate the coder to write, so Java generics, exactly is the JVM generics, also called pseudo-generics.

2 people attribute

We know that the JVM erases all generics at compile time, but we can get generics information by reflection. Reflection works at runtime, which must be after compile time, so we can get generics information because of the Signature property.

The Signature property is a property in the.class file’s property sheet. It records generic information. At compile time, the generic information in the original Coder property is erased, and then recorded in the Signature property, from which the reflection API obtains generic information.

About reflection, you can see it here.