T, E, K, V, ? in Java generics Wildcards, are you sure you understand everything?

Table of contents

foreword

The benefits of generics

wildcards in generics

summary


foreword

Java generics (generics) is a new feature introduced in JDK 5. Generics provide a compile-time type safety detection mechanism that allows developers to detect illegal types at compile time.

The essence of generics is a parameterized type, which means that the type of data being manipulated is specified as a parameter.

The benefits of generics

In the absence of generics, the "arbitrary" of parameters is realized by reference to the type Object. The disadvantage of "arbitrary" is that explicit mandatory type conversion is required, and this conversion requires developers It is carried out when the actual parameter type can be predicted. For the case of mandatory type conversion errors, the compiler may not prompt the error, and an exception will only occur during runtime, which is a security risk in itself.

Then the advantage of generics is that type safety can be checked at compile time, and all casts are automatic and implicit.

public class GlmapperGeneric<T> {
		private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
  
    public static void main(String[] args) {
        // do nothing
    }

  /**
    * 不指定类型
    */
  public void noSpecifyType(){
    GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 需要强制类型转换
    String test = (String) glmapperGeneric.get();
    System.out.println(test);
  }

  /**
    * 指定类型
    */
  public void specifyType(){
    GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // 不需要强制类型转换
    String test = glmapperGeneric.get();
    System.out.println(test);
  }
}

The specifyType method in the code above omits mandatory conversion, which can check type safety at compile time, and can be used on classes, methods, and interfaces.

wildcards in generics

When we define generic classes, generic methods, and generic interfaces, we often encounter many different wildcards, such as T, E, K, V, etc. What do these wildcards mean?

Commonly used T, E, K, V, ?

In essence, these are all wildcards, there is no difference, it is just a convention when coding. For example, T in the above code, we can replace it with any letter between AZ, and it will not affect the normal operation of the program, but if it is replaced by other letters instead of T, the readability may be weaker. Typically, T, E, K, V, ? It is agreed that:

? Represents an uncertain java type
T (type) represents a specific java type
KV (key value) represents the Key Value in the java key value
E (element) represents Element

? unbounded wildcard

Let’s start with a small example. I have a parent class Animal and several subclasses, such as dogs, cats, etc. Now I need a list of animals. My first idea is like this:

List<Animal> listAnimals

But the boss's idea is really like this:

List<? extends Animal> listAnimals

Why use wildcards instead of simple generics? Wildcards are actually meaningless when declaring local variables, but they are very important when you declare a parameter for a method.

static int countLegs (List<? extends Animal > animals ) {
    int retVal = 0;
    for ( Animal animal : animals )
    {
        retVal += animal.countLegs();
    }
    return retVal;
}

static int countLegs1 (List< Animal > animals ){
    int retVal = 0;
    for ( Animal animal : animals )
    {
        retVal += animal.countLegs();
    }
    return retVal;
}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
 	// 不会报错
    countLegs( dogs );
	// 报错
    countLegs1(dogs);
}

When calling countLegs1, it will be red, and the error message is as follows:

Therefore, for the type that is not sure or does not care about the actual operation, you can use an unlimited wildcard (a question mark in angle brackets, that is, <?>), which means that you can hold any type. Like the countLegs method, the upper bound is limited, but it doesn't care what the specific type is, so it can support all subclasses of Animal passed in, and no error will be reported. And countLegs1 will not work.

Upper bound wildcard < ? extends E>

Upper bound: declared with the extends keyword, indicating that the parameterized type may be the specified type, or a subclass of this type.

Using extends in the type parameter means that the parameter in this generic type must be E or a subclass of E, which has two advantages:

1) If the type passed in is not E or a subclass of E, the compilation will fail

2) The method of E can be used in generics, otherwise it has to be converted to E to use

private <K extends A, E extends B> E test(K arg1, E arg2){
    E result = arg2;
    arg2.compareTo(arg1);
    //.....
    return result;
}

If there are multiple type parameter upper bounds in the type parameter list, separate them with commas

Nether wildcard <? super E>

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

Using super in the type parameter indicates that the parameter in this generic type must be E or a superclass of E.

private <T> void test(List<? super T> dst, List<T> src){
    for (T t : src) {
        dst.add(t);
    }
}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    new Test3().test(animals,dogs);
}
// Dog 是 Animal 的子类
class Dog extends Animal {

}

The dst type is "greater than or equal to" the type of src. The "greater than or equal to" here means that the range represented by dst is larger than that of src, so the container that can hold dst can also hold src.

? difference from T

 

? and T both represent indeterminate types, the difference is that we can operate on T, but right? No, like this:

// 可以
T t = operate();

// 不可以
?car = operate();

A brief summary:

T is a definite type, usually used in the definition of generic classes and generic methods, ? Is an indeterminate type, usually used for calling code and formal parameters of generic methods, and cannot be used to define classes and generic methods.

Difference 1: Use T to ensure the consistency of generic parameters

// 通过 T 来 确保 泛型参数的一致性
public <T extends Number> void
test(List<T> dest, List<T> src)

//通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
public void
test(List<? extends Number> dest, List<? extends Number> src)

As in the code below, the agreed T is only a subclass of Number, but String is used in the declaration, so an error will be reported.

There is no guarantee that two Lists have the same element type

GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric<>();
List<String> dest = new ArrayList<>();
List<Number> src = new ArrayList<>();
glmapperGeneric.testNon(dest,src);

The above code will not report an error in the compiler, but when entering the internal operation of the testNon method (such as assignment), for dest and src, type conversion is still required.

Difference 2: Type parameters can be multi-qualified but wildcards cannot

Use & symbol to set multi-boundary (Multi Bounds), specify generic type T must be a common subtype of MultiLimitInterfaceA and MultiLimitInterfaceB, then variable t has all limited methods and properties. For wildcards, because it is not a definite type, multiple qualifications cannot be made.

Difference 3: Wildcards can be qualified by superclasses but type parameters cannot

The type parameter T has only one type qualification:

T extends A

But the wildcard character ? can be qualified in two ways:

? extends A
? super A

The difference between Class<T> and Class<?>

Introduced earlier? and T, so what is the difference between Class<T> and <Class<?>? Class<T> and Class<?>

The most common use is in the reflection scene, here is a piece of emission code to illustrate.

// 通过反射的方式生成  multiLimit 
// 对象,这里比较明显的是,我们需要使用强制类型转换
MultiLimit multiLimit = (MultiLimit)
Class.forName("com.glmapper.bridge.boot.generic.MultiLimit")

For the above code, at runtime, if the reflected type is not MultiLimit class, java.lang.ClassCastException will be reported.

In this case, the following code can be used instead, so that the type problem can be directly checked at compile time:

When Class<T> is instantiated, T is replaced with a concrete class. Class<?> It is a wild generic type, and ? can represent any type, so it is mainly used for the restriction of declaration. For example, we can declare like this:

// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;

So when you don't know what type of Class to declare, you can define a Class<?>.

Then if you also want public Class<T> clazzT; In this case, you must let the current class also specify T.

public class Test3<T> {
public Class<?> clazz;
// 不会报错
public Class<T> clazzT;

summary

This article sorts out some points in JAVA generics piecemeal, not very comprehensive, for reference only. If there is something inappropriate in the text, please correct me.

Guess you like

Origin blog.csdn.net/weixin_39570751/article/details/131285791