[Kaka on Java] Java generics


   students, today let's learn about generics in Java. Generics are widely used in object-oriented programming and various design patterns.

Generic concept

   generic, i.e. "parameterized type". When referring to parameters, the most familiar thing is that there is a formal parameter when defining the method, and then the arguments are passed when the method is called. Parameterized type is to parameterize the type from the original specific type, which is similar to the variable parameters in the method. At this time, the type is also defined as the parameter form (which can be called type parameter), and then the specific type (type parameter) is passed in when using / calling.

   the essence of generics is to parameterize types (control the types of formal parameters through different types specified by generics without creating new types). In other words, during the use of generics, the data type of the operation is specified as a parameter. This parameter type can be used in classes, interfaces and methods, which are called generic classes, generic interfaces and generic methods respectively.

Take a chestnut

Let's first look at the code:

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
	//The value must be cast when it is obtained
    String item = (String)arrayList.get(i);
    //There will be no error during the above forced transformation compilation, and the runtime will report an exception Java lang.ClassCastException
    Log.d("Generic testing","item = " + item);
}

Problems faced

  • When we get a value, we must cast it.
  • When we insert a value, we cannot constrain the expected type. Suppose we expect to use the values of String type to store the String collection, because ArrayList only maintains an array referenced by Object. We cannot prevent the data of Integer type (Object subclass) from being added to the values of String type. However, when we use data, we need to convert the obtained Object object to the type we expect (String). If an unexpected type (such as Integer) is added to the collection, we will not receive any error prompt during compilation. But when we run the program, we will report an exception:
    Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at generic.BeforeGeneric.main(BeforeGeneric.java:24)

This is obviously not what we expect. If the program has potential errors, we prefer to be informed of errors at compile time rather than exceptions at run time. In order to solve such problems (which can be solved in the compilation stage), generics came into being.

characteristic

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("Generic test results","The type is the same");
}

Output result: generic test result: the type is the same

Through the above example, it can be proved that the program will take de genericization measures after compilation. In other words, it is only valid in the compilation stage of Java. In the compilation process, after the generic results are correctly verified, the relevant information of the generic will be erased, and the methods of type checking and type conversion will be added at the boundary of the object entering and leaving the method. That is, generic information does not enter the runtime phase.
Summary: generic types are logically seen as multiple different types, which are actually the same basic types.

Use of generics

  there are only three types of generic: generic methods, generic classes, and generic interfaces

Generic class

   generic types are used in the definition of classes and are called generic classes. Through generics, you can complete the operation of a group of classes and open the same interface to the outside world. The most typical are various container classes, such as List, Set and Map.

//Here, t can be written as any identifier. Common parameters in the form of T, E, K and V are often used to represent generics
//When instantiating a generic class, you must specify the specific type of T
public class Generic<T>{ 
    //key the type of this member variable is t, and the type of T is specified externally  
    private T key;

    public Generic(T key) { //The type of generic constructor parameter key is also t, and the type of T is specified externally
        this.key = key;
    }

    public T getKey(){ //The return value type of the generic method getKey is t, and the type of T is specified externally
        return key;
    }
}
---------------------------------------------------------------------------
//Type parameters of generic types can only be class types (including custom classes), not simple types
//The type of the passed in argument must be the same as that of the generic type parameter, that is, Integer
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//The input argument type must be the same as the type parameter type of generic type, that is, String
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("Generic testing","key is " + genericInteger.getKey());
Log.d("Generic testing","key is " + genericString.getKey());

The generic class defined does not have to pass in generic type arguments. When using generics, if you pass in generic arguments, you will make corresponding restrictions according to the passed in generic arguments. At this time, generics will play the role of restriction that they should play. If the generic type argument is not passed in, the type defined by the generic method or member variable in the generic class can be any type.
Take an example:

Generic generic = new Generic("111111");
Generic generic1 = new Generic(4444);
Generic generic2 = new Generic(55.55);
Generic generic3 = new Generic(false);

Log.d("Generic testing","key is " + generic.getKey());
Log.d("Generic testing","key is " + generic1.getKey());
Log.d("Generic testing","key is " + generic2.getKey());
Log.d("Generic testing","key is " + generic3.getKey());
-------------------------------------------------------------
D/Generic testing: key is 111111
D/Generic testing: key is 4444
D/Generic testing: key is 55.55
D/Generic testing: key is false

be careful:

  • The type parameter of a generic type can only be a class type, not a simple type.
  • The instanceof operation cannot be used on an exact generic type. If the following operation is illegal, an error will occur during compilation.
    if(ex_num instanceof Generic<Number>){} 
    

generic interface

The definition and use of generic interfaces and generic classes are basically the same. Generic interfaces are often used in various kinds of producers. You can see an example:

//Define a generic interface
public interface Generator<T> {
    public T next();
}

When a class that implements a generic interface does not pass in a generic argument:

/**
 * When a generic argument is not passed in, it is the same as the definition of a generic class. When declaring a class, you need to add the declaration of the generic to the class
 * Namely: class fruitgenerator < T > implements generator < T >{
 * If you do not declare generics, such as: class fruitgenerator implements generator < T >,
 * The compiler will report an error: "Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

When a class implementing a generic interface passes in a generic argument:

/**
 * When passing in generic arguments:
 * Define a producer to implement this interface, although we have only created a generic interface generator < T >
 * However, we can pass in countless arguments for T to form countless types of Generator interfaces.
 * When the implementation class implements the generic interface, if the generic type has been passed into the argument type,
 * Then all places where generics are used should be replaced with the passed in argument type
 * Namely: generator < T >, public T next(); The T in should be replaced with the incoming String type.
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}

Generic wildcard

We know that Ingeter is a subclass of Number. At the same time, we also verified that Generic and Generic are actually the same basic type in the features section. So the question is, in the method using Generic as a formal parameter, can you use the instance of Generic to pass in? Logically similar to Generic and Generic, can Generic be regarded as a Generic type with parent-child relationship?
To clarify this problem, let's continue to look at the following example using Generic class:

public void showKeyValue1(Generic<Number> obj){
    Log.d("Generic testing","key value is " + obj.getKey());
}
--------------------------------------------------------
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);

// The compiler of showKeyValue will report an error for us: generic < Java lang.Integer> 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

Through the prompt message, we can see that Generic cannot be regarded as a subclass of ` Generic. It can be seen that the same Generic type can correspond to multiple versions (because the parameter type is uncertain), and different versions of Generic class instances are incompatible.
Back to the above example, how to solve the above problem? You can't always define a new method to deal with Generic classes, which is obviously contrary to the concept of multiple machines in java. Therefore, we need a reference type that can logically represent both Generic and Generic parent classes. This type wildcard came into being.

We can change the above method:

public void showKeyValue1(Generic<?> obj){
    Log.d("Generic testing","key value is " + obj.getKey());
}

Type wildcards are generally used? Instead of specific type arguments, notice that '?' Is a type argument, not a type parameter. More straight white point means, here? Like Number, String and Integer, it is an actual type, which can be set as "Number", "String" and "Integer"? As a parent of all types. Is a real type.

When the specific type is uncertain, the wildcard is; When operating a type, only the functions in the Object class are used when the specific functions of the type do not need to be used. So you can use it? Wildcard to table unknown type.

generic method

In Java, generic class definition is very simple, but generic methods are very complex.

In particular, the member methods in most generic classes we see also use generics, and some even contain generic methods in generic classes, so it is very easy for beginners to misunderstand generic methods.

Generic class refers to the specific type of generic that needs to be specified when instantiating a class; Generic method refers to the specific type of generic when calling the method

/**
 * Basic introduction to generic methods
 * @param tClass Generic argument passed in
 * @return T The return value is of type T
 * explain:
 *     1)public < T > is very important in the middle of the return value, which can be understood as declaring this method as a generic method.
 *     2)Only methods that declare < T > are generic methods,
 *        Member methods in generic classes that use generics are not generic methods.
 *     3)<T>Indicates that the method will use generic type T. only then can generic type t be used in the method.
 *     4)Like the definition of generic class, T here can be written as any identifier,
 * 		  Common parameters such as T, E, K and V are often used to represent generics.
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}
------------------------------------------------------------------------
Object obj = genericMethod(Class.forName("com.test.test"));

Basic use of generic methods

Let's summarize the generic method through another example.

public class GenericTest {
   //This class is a generic class, which has been introduced above
   public class Generic<T>{     
        private T key;

        public Generic(T key) {
            this.key = key;
        }

        //What I want to say is this. Although generics are used in the method, it is not a generic method.
        //This is just an ordinary member method in the class, but its return value is the declared generic of the declared generic class.
        //Therefore, we can continue to use the generic type T in this method.
        public T getKey(){
            return key;
        }

    /** 
     * This is a real generic method.
     * First, the < T > between public and the return value is essential, which indicates that this is a generic method and declares a generic t
     * This T can appear anywhere in the generic method
     * The number of generics can also be any number 
     *    For example: public < T, k > k showkeyname (generic < T > container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //Of course, this example is not appropriate, just to illustrate the characteristics of generic methods.
        T test = container.getKey();
        return test;
    }

    //This is not a generic method, but a common method. It just uses the generic < number > class as a formal parameter.
    public void showKeyValue1(Generic<Number> obj){
        Log.d("Generic testing","key value is " + obj.getKey());
    }

    //This is not a generic method, it is also a common method, but uses generic wildcards?
    //At the same time, this also confirms what is described in the chapter of generic wildcards,? It is a type argument and can be regarded as the parent of all classes such as Number
    public void showKeyValue2(Generic<?> obj){
        Log.d("Generic testing","key value is " + obj.getKey());
    }
}

Generic methods in classes

Of course, this is not the whole of generic methods. Generic methods can be used anywhere and in any scenario. However, there is a very special case. When generic methods appear in generic classes, let's take another example:

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //A generic method is declared in the generic class, using generic E,
        //This generic E can be of any type. The type can be the same as T or different.
        //Since generic methods declare generics < E > when declared, even if generics are not declared in generic classes,
        //The compiler can also correctly recognize generics recognized in generic methods.
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //Declare a generic method in the generic class and use generic T. note that this t is a new type,
        //It can be different from the T declared in the generic class.
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple is a subclass of Fruit, so you can
        generateTest.show_1(apple);
        //The compiler will report an error because the generic type argument specifies Fruit and the passed in argument class is Person
        //generateTest.show_1(person);

        //Using both methods can be successful
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //Using both methods can also be successful
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

Generic method variable parameters

Take another example of generic methods and variable parameters:

public <T> void printMsg( T... args){
    for(T t : args){
        Log.d("Generic testing","t is " + t);
    }
}
------------------------------------------------------------
printMsg("111",222,"aaaa","2323.4",55.55);

Static methods and generics

Static methods need to pay attention to one situation, that is, the static methods in the class use generics: static methods cannot access the generics defined on the class; If the reference data type of static method operation is uncertain, the generic type must be defined on the method.

That is, if static methods want to use generics, static methods must also be defined as generic methods.

public class StaticGenerator<T> {
    /**
     * If you define static methods that use generics in a class,
     * Additional generic declarations need to be added (defining this method as a generic method)
     * Even if static methods want to use generic types that have been declared in generic classes, they can't.
     * For example: public static void show (T) {..}, The compiler will prompt an error message:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){
    }
}

Generic summary method

Generic methods can make methods change independently of classes. The following is a basic guiding principle:

Whenever you can, you should try to use generic methods. That is, if you use generic methods to generize the entire class, you should use generic methods. In addition, for a static method, the parameters of generic types cannot be accessed. Therefore, if static methods want to use generic capabilities, they must be made generic methods.

Generic upper and lower bounds

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

public void showKeyValue1(Generic<? extends Number> obj){
    Log.d("Generic testing","key value is " + obj.getKey());
}
------------------------------------------------------------
Generic<String> generic1 = new Generic<String>("11111");
Generic<Integer> generic2 = new Generic<Integer>(2222);
Generic<Float> generic3 = new Generic<Float>(2.4f);
Generic<Double> generic4 = new Generic<Double>(2.56);

//In this line of code, the compiler will prompt an error because the String type is not a subclass of the Number type
//showKeyValue1(generic1);

showKeyValue1(generic2);
showKeyValue1(generic3);
showKeyValue1(generic4);

If we change the definition of generic class:

public class Generic<T extends Number>{
    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}
------------------------------------------------------------
//This line of code will also report an error, because String is not a subclass of Number
Generic<String> generic1 = new Generic<String>("11111");

Another example of generic methods:

//When adding upper and lower boundary restrictions in generic methods, you must add upper and lower boundaries on the < T > between the permission declaration and the return value,
//That is, it is added when the generic type is declared
//public <T> T showKeyName(Generic<T extends Number> container),
//The compiler will report an error: "Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
    System.out.println("container key :" + container.getKey());
    T test = container.getKey();
    return test;
}

From the above two examples, we can see that the addition of the upper and lower boundaries of generics must be together with the declaration of generics.

epilogue

   the examples in this article are mainly for the purpose of explaining some ideas in generics. When it comes to generics, I believe that what you use most is in the collection. In fact, in the actual programming process, you can use generics to simplify development and ensure code quality. The above is the main content of Java generics shared by kaka today. If there is any mistake, I hope students will actively point it out in the comment area. You are welcome to share and learn together.

Keywords: Java Spring Back-end Agile development

Added by asukla on Mon, 21 Feb 2022 05:13:15 +0200