Detailed explanation of Java generic type and its application

  • 2021-07-13 05:25:47
  • OfStack

Derive generics

We introduce the concept of generics through the following examples.


public class Test {

  public static void main(String[] args) {
    List list = new ArrayList();
    list.add("abc");
    list.add(2);

    for (int i = 0; i < list.size(); i++) {
      String name = (String) list.get(i); // error
      System.out.println("name:" + name);
    }
  }
}

An error is reported when the second element in the list is obtained, java. lang. ClassCastException: java. lang. Integer cannot be cast to java. lang. String. This is a common type conversion error.

When we put the element into the list, the specified type is not used, and the default Object type is used when we take out the element. Therefore, it is easy to have an exception of type conversion.

What we want to achieve is that the collection can remember the various types of elements in the collection, and can achieve that java. lang. ClassCastException exceptions will not occur at runtime as long as there are no problems at compile time. Generics just meet our needs.

What are generics?

Generics, that is, parameterized types. 1 When it comes to parameters, the most familiar thing is to define a method with tangible parameters, and then pass arguments when the method is called. So what about parameterized types? As the name implies, the type is parameterized from the original concrete type, similar to the variable parameters in the method. At this time, the type is also defined as a parameter (can be called a type parameter), and then the concrete type (type argument) is passed in when using/calling.

The essence of generics is to parameterize types, that is, to control the types specifically restricted by formal parameters through different types specified by generics without creating new types. During the use of generics, the data type of the operation is specified as one parameter, which can be used in classes, interfaces and methods, which are called generic classes, generic interfaces and generic methods respectively.

Characteristics of generics

The introduction of generics into Java is a major feature enhancement. Not only have languages, type systems, and compilers changed considerably to support generics, but class libraries have also been overhauled so that many important classes, such as collection frameworks, have become generic. This brings many benefits:

Type safe. The main goal of generics is to improve the type safety of Java programs. By knowing the type restrictions of variables defined using generics, the compiler can validate type assumptions to a much higher degree. Eliminate forced type casting. One side benefit of generics is that it eliminates many forced type conversions in source code. This makes the code more readable and reduces the chance of error. Potential performance gains. Generics bring the possibility of greater optimization. In the initial implementation of a generic, the compiler inserts cast-casts (which the programmer would specify if there were no generics) into the generated bytecode.

Named type parameter

The recommended naming convention is to use uppercase single letter names as type parameters. For common generic patterns, the recommended name is:

K: Keys, such as mapped keys V: Values, such as the contents of List and Set, or values in Map E: Element T: Generic

public class Generic<T> { 
  //key The type of is T 
  private T key;

  public Generic(T key) { 
  	// Generic constructor parameter key The type of is also T
    this.key = key;
  }

  public T getKey() { 
  	// Generic method getKey The return value type of is T
    return key;
  }
}

As defined above, a common generic class with member variables of type T and T of type specified externally. The same is true for generic methods and generic constructors.


Generic<Integer> genericInteger = new Generic<Integer>(123456); //1

Generic<String> genericString = new Generic<String>("key_vlaue"); // 2

System.out.println("key is " + genericInteger.getKey());
System.out.println("key is " + genericString.getKey());

Type parameters of generics can only be class types (including custom classes), not simple types. The argument type passed in needs to be the same as the type parameter type of the generic type, which is Integer/String.

As mentioned above, do you have to pass in generic type arguments for the defined generic class?

This is not the case. When using generics, if generic arguments are passed in, the corresponding restrictions will be made according to the passed generic arguments, and then generics will play the role of restrictions that should be played. A type defined using a generic method or member variable in a generic class can be any type without passing in a generic type argument.


Generic genericString = new Generic("111111");
Generic genericInteger = new Generic(4444);

System.out.println("key is " + genericString.getKey());
System.out.println("key is " + genericInteger.getKey());

The code snippet above will output the following result:

key is 111111
key is 4444

The generic guard or member variable used in a generic class can be any other type, such as Integer or String, without passing in generic type arguments. However, it should be noted that the type parameters of generics can only be class types, not simple types. And you cannot use instanceof operations on exact generic types. For different type arguments passed in, are the corresponding object instances generated of the same type? Look at the following example:


public class GenericTest {

  public static void main(String[] args) {

    Generic<Integer> name = new Box<String>("111111");
    Generic<String> age = new Box<Integer>(712);

    System.out.println("name class:" + name.getClass()); 
    System.out.println("age class:" + age.getClass()); 
    System.out.println(name.getClass() == age.getClass());  // true
  }

}

From the output structure, we can see that when using generic classes, although different generic arguments are passed in, different types are not really generated. There is only one generic class passed in different generic arguments in memory, that is, it is still the original most basic type (Generic in this example). Of course, logically we can understand many different generic types.

The reason for this is that the concept of generics in Java is put forward for the purpose that it only acts on the code compilation stage. During compilation, after the generic results are correctly verified, the relevant information of the generic will be erased. That is, a successfully compiled class file does not contain any generic information. Generic information does not enter the runtime phase.

Generic types are logically seen as many different types, but they are all the same basic types.

Wildcard character

Ingeter is a subclass of Number, and Generic < Ingeter > And Generic < Number > Is actually the same basic type. Then the problem comes, when using Generic < Number > Can you use Generic as a formal parameter < Ingeter > What about the instance of? Logically similar to Generic < Number > And Generic < Ingeter > Can you think of it as a generic type with parent-child relationship? Let's verify it by defining a method.


public void show(Generic<Number> obj) {
  System.out.println("key value is " + obj.getKey());
}

Make the following call


Generic<Integer> genericInteger = new Generic<Integer>(123);

show(genericInteger); //error Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>

We can see Generic through the prompt information < Integer > Can't be regarded as Generic < Number > Gets or sets a subclass of. It can be seen from this that the same generic type can correspond to multiple versions (because the parameter types are uncertain), and different versions of generic class instances are incompatible.

We cannot therefore define an show (Generic < Integer > obj), so we need a reference type that logically represents a parent class of both Generic and Generic. Wildcards of this type came into being.

Generic letters such as T, K, V, E are typed, and type parameters are given specific values. In addition to having types, you can also use wildcard characters to express types. Unknown types, type parameters with uncertain values, arbitrary types can only be used to declare types and method parameters, but not to define generic classes. Rewrite the method as follows:


public void show(Generic<?> obj) {
  System.out.println("key value is " + obj.getKey());
}

Here? Is a type argument, not a type parameter. That is to say, Number, String and Integer 1 are all actual types. Can you put? As the parent class of all types, it is a real type. Can solve when the specific type is uncertain, this wildcard character is? ; When you manipulate a type and do not need to use the specific functionality of the type, use only the functionality in the Object class. So you can use it? Wildcard characters to represent unknown types.

Generic upper and lower bounds

When using generics, we can also restrict the upper and lower boundaries of the passed generic type arguments, for example, type arguments can only be passed into the parent class or subclass of a certain type. Adds an upper boundary to a generic type, that is, the type argument passed in must be a subtype of the specified type.


public void show(Generic<? extends Number> obj) {
  System.out.println("key value is " + obj.getKey());
}

We qualify subclasses of parameter type Number in the entry parameters of generic methods.


Generic<String> genericString = new Generic<String>("11111");
Generic<Integer> genericInteger = new Generic<Integer>(2222);


showKeyValue1(genericString); // error
showKeyValue1(genericInteger);

When our input parameter is of type String, the compilation error is reported because the type String is not a subclass of the type Number.

The upper limit of type wildcard is passed through the form Generic < ? extends Number > Formal definition; Correspondingly, the lower limit of type wildcard is Generic < ? super Number > Form, whose meaning is just opposite to the upper limit of type wildcard, will not be elaborated here.

Generic array

You cannot create an array of an exact generic type in java, that is:


public class Generic<T> { 
  //key The type of is T 
  private T key;

  public Generic(T key) { 
  	// Generic constructor parameter key The type of is also T
    this.key = key;
  }

  public T getKey() { 
  	// Generic method getKey The return value type of is T
    return key;
  }
}
0

As above, compile and report errors, while it is ok to create generic arrays using wildcards:


public class Generic<T> { 
  //key The type of is T 
  private T key;

  public Generic(T key) { 
  	// Generic constructor parameter key The type of is also T
    this.key = key;
  }

  public T getKey() { 
  	// Generic method getKey The return value type of is T
    return key;
  }
}
1

JDK 1.7 simplifies generics, so another declaration is OK.

Because of the erasure mechanism of JVM generics, JVM does not know the generic information at runtime. Generic arrays the actual array of runtime objects can only be of primitive type (T [] is Object [], Pair [] is Pair []), while the actual runtime array objects may be of type T (although the runtime erases to the primitive type). The only way to successfully create a generic array is to create a new array of erased types and then transform it.


public class Generic<T> { 
  //key The type of is T 
  private T key;

  public Generic(T key) { 
  	// Generic constructor parameter key The type of is also T
    this.key = key;
  }

  public T getKey() { 
  	// Generic method getKey The return value type of is T
    return key;
  }
}
2

At runtime, the exit of the array object is transformed and output, and the entry method has been type-safe at compile time, so the exit method can safely force the type conversion to ensure success.

Summary

This paper mainly talks about the related concepts and applications of Java generics. Generics enable the compiler to check types during compilation to improve type safety and reduce exceptions thrown at run time due to object type mismatches. Introduces the concept of generics, how to use generics to simplify development while ensuring the quality of code.


Related articles: