Java generics T, E, K, V,?, Are you still confused?

preface

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. "Generally, t, E, K, V are 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 wildcard

Let's start with a small example. The original text is here.

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 previous session, but does not care about the specific type, so all subclasses of the incoming Animal can be supported 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   yes   Animal   Subclass of   
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

//  adopt   T   come   ensure   Consistency of generic parameters   
public <T extends Number> void  
test(List<T> dest, List<T> src)  
  
//Wildcards are   Uncertain, so this method can't guarantee two   List   Has 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 "and" t "was introduced earlier. For class < T >   and  < Class<?>   What's the difference? 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.

//  Generated by reflection    multiLimit    
//  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 with a concrete class. Class<?>   It's a generic type,? It can represent any type, so it is mainly used for restrictions when declaring. For example, we can state:

//  sure   
public Class<?> clazz;  
//  No, because   T   Type needs to be specified   
public Class<T> clazzT;  
 

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

Then if you want to   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;  
 

I hope I can help you. If there is anything wrong, please forgive me!

Tags: Java Back-end

Posted on Sun, 28 Nov 2021 07:05:00 -0500 by dta