Talk about Java generic wildcards T, E, K, V,?

|Foreword

Java generics is a new feature introduced in JDK 5. Generics provides a compile time type safety detection mechanism, which allows developers to detect illegal types at compile time.

The essence of generics is parameterized type, that is, the data type operated on is specified as a parameter.

|Benefits of generics

In the absence of generics, the "arbitrary" of parameters is realized by referring to the type Object. The disadvantage of "arbitrary" is to make explicit forced type conversion, which requires developers to predict the actual parameter types. In the case of cast errors, the compiler may not prompt errors, and exceptions occur only when running, which is itself a security risk.

The advantage of generics is that it can check type safety 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
    }

  /**
    * Do not specify type
    */
  public void noSpecifyType(){
    GlmapperGeneric glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // Cast required
    String test = (String) glmapperGeneric.get();
    System.out.println(test);
  }

  /**
    * Specify type
    */
  public void specifyType(){
    GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric();
    glmapperGeneric.set("test");
    // Cast is not required
    String test = glmapperGeneric.get();
    System.out.println(test);
  }
}

The specififytype method in the above code omits the cast. It can check the type safety during compilation and can be used on classes, methods and interfaces.

|Wildcards in generics

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

Commonly used T, E, K, V,?

In essence, these are wildcards, no difference, but a conventional thing in coding. For example, we can replace t in the above code with any letter between A-Z, which will not affect the normal operation of the program, but if we replace T with other letters, it may be less readable. Usually, t, E, K, V,? It is agreed as follows:

  • ? Represents an ambiguous java type
  • T (type) represents a specific java type
  • K V (key value) respectively represents the Key Value in the java Key Value
  • E (element) stands for Element

? Unbounded wildcards

Let's start with a small example.

I have a parent Animal and several subclasses, such as dogs and cats. Now I need a list of animals. My first idea is like this:

List<Animal> listAnimals

But the boss does think so:

List<? extends Animal> listAnimals

Why use wildcards instead of simple generics? Wildcards are 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<>();
  // No error will be reported
    countLegs( dogs );
 // report errors
    countLegs1(dogs);
}

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

Therefore, for types that are uncertain or do not care about the actual operation, you can use infinite wildcards (a question mark in angle brackets, i.e. <? >), indicating that you can hold any type. For example, the countLegs method defines the upper bound, but does not care about the specific type, so it can support all subclasses of the incoming Animal without error. Not countLegs1.

Upper bound wildcard <? extends E>

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

Using extensions in type parameters means that the parameters in this generic type must be e or subclasses of E, which has two advantages:

  • If the type passed in is not e or a subclass of E, the compilation will not succeed
  • You can use the method of E in generics, otherwise you have to convert to e to use it
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 upper limits of type parameters in the type parameter list, separate them with commas

Lower bound wildcard <? super E>

Lower bound: declared with super, indicating that the parameterized type may be the specified type or the parent type of this type until Object

Using super in type parameters means that the parameters in this generic type must be e or the parent class 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 is a subclass of Animal
class Dog extends Animal {

}

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

? Difference between and T

? And t both represent uncertain types. The difference is that we can operate on T, but for? No, for example:

// sure
T t = operate();

// may not
?car = operate();

To summarize briefly:

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

Difference 1: ensure the consistency of generic parameters through T

// Ensure the consistency of generic parameters through T
public <T extends Number> void
test(List<T> dest, List<T> src)

//Wildcards are uncertain, so this method cannot guarantee that two lists have the same element type
public void
test(List<? extends Number> dest, List<? extends Number> src)

As in the following code, the agreed T is a subclass of Number, but the declaration uses String, so an error will be reported in red.

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 testNon method (such as assignment), type conversion is still required for dest and src.

Difference 2: type parameters can be multi qualified, but wildcards cannot

Use the & symbol to set multi boundaries and specify that the generic type t must be a common subtype of MultiLimitInterfaceA and MultiLimitInterfaceB. At this time, the variable t has all qualified methods and properties. For wildcards, because it is not a definite type, it cannot be multi qualified.

Difference 3: wildcards can use super class qualification, but type parameters cannot

Type parameter T has only one type qualification method:

T extends A

However, wildcards can be qualified in two ways:

? extends A
? super A

|Difference between class < T > and class <? >

The difference between "t" and "t" was introduced earlier. What is the difference between class < T > and < class <? >? Class < T > and class <? >

The most common is the use in the reflection scene. Here, a piece of emitted code is used to illustrate it.

// Generate multiLimit by reflection
// Object, it is obvious here that we need to use cast
MultiLimit multiLimit = (MultiLimit)
Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance();

For the above code, if the type of reflection is not a MultiLimit class at runtime, a java.lang.ClassCastException error 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:

Class < T > when instantiating, t should be replaced by a specific class. Class <? > it is a generic type and can represent any type, so it is mainly used for restrictions when declaring. For example, we can do this to declare:

// sure
public Class<?> clazz;
// No, because T needs to specify the type
public Class<T> clazzT;

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

If you also want to use public class < T > clazzt; In this case, the current class must also specify t,

public class Test3<T> {
    public Class<?> clazz;
    // No error will be reported
    public Class<T> clazzT;

|Summary

This paper sorts out some points in JAVA generics, which are not very complete, for reference only. If there is anything wrong in the text, please correct it.

Transferred from: Uncle Lei_GLMapper Link: https://juejin.cn/post/6844903917835419661

Like, watch and share three companies

Added by JPnyc on Fri, 12 Nov 2021 09:07:24 +0200