After using generics for so long, I finally know what it is

A friend learned Java generics and doubled his salary directly!

1. General

Generics play an important role in java and are widely used in object-oriented programming and various design patterns.

What is generics? Why use generics?

Generic, or "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. So what about parameterized types?

As the name suggests, it is to parameterize the type from the original specific type, which is similar to the variable parameter in the method. At this time, the type is also defined as a parameter form (which can be called a type parameter),

Then pass in the specific type (type argument) when using / call.

The essence of generics is to parameterize types (the types of formal parameters are controlled by different types specified by generics without creating new types). That is, 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.

2. A chestnut

An example that has been cited countless times:

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

for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("generic test", "item =" + item);
}
Copy code

There is no doubt that the running result of the program will end in a crash:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
 Copy code

ArrayList can store any type. In the example, a String type and an Integer type are added. When they are used again, they are used in the form of String, so the program crashes. In order to solve such problems (which can be solved at the compilation stage), generics came into being.

We will change the first line of code to initialize the list, and the compiler will help us find such problems at the compilation stage.

List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100);  During the compilation phase, the compiler will report an error
 Copy code

3. Characteristics

Generics are only valid at compile time. Look at the following code:

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", "same type");
}
Copy code

Output result: D / generic test: same type.

The above example can prove that the program will take de genericization measures after compilation. In other words, generics in Java are only valid at the compilation stage. 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 where the object enters and leaves the method. That is, generic information does not enter the runtime phase.

This can be summed up in one sentence: generic types are logically regarded as multiple different types, which are actually the same basic types.

4. Use of generics

There are three ways to use generics: generic class, generic interface and generic method

4.3 generic classes

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.

The most basic way to write a generic class (it may be a little dizzy, which will be explained in detail in the following example):

class Class name <Generic ID: you can write any ID number to identify the type of the specified generic>{
  private Generic identity /*(Member (variable type)*/ var; 
  .....

}
}
Copy code

One of the most common generic classes:

//Here, T can be written as any identifier. Common parameters such as T, E, K and V are often used to represent generics
//When instantiating a generic class, you must specify the concrete 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;
}

}
Copy code

//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 the 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());
Copy code
12-27 09:20:04.432 13063-13063/? D/Generic testing: key is 123456
12-27 09:20:04.432 13063-13063/? D/Generic testing: key is key_vlaue
 Copy code

Do you have to pass in generic type arguments for a defined generic class? This is not the case. 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 test", "key is" + generic. Getkey());
Log.d("generic test", "key is" + generic1. Getkey());
Log.d("generic test", "key is" + generic2. Getkey());
Log.d("generic test", "key is" + generic3. Getkey());
Copy code

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
 Copy code

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){ }

4.4 generic interfaces

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

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

When a class implementing 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 a generic type, 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;
    }
}
Copy code

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 implementing a class to implement a generic interface, if a generic type has been passed into an argument type, all places where generics are used must 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)];
}

}
Copy code

4.5 generic wildcards

We know that Ingeter is a subclass of Number. At the same time, we also verified that generic < Ingeter > and generic < Number > are actually the same basic type in the features section. So the question is, in the method using generic < Number > as a formal parameter, can you use the instance of generic < Ingeter >? Logically similar to generic < Number > and generic < Ingeter >, can they be regarded as generic types with parent-child relationship?

To clarify this problem, we use the generic class generic < T > to continue to look at the following example:

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

showKeyValue(gNumber);

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

Through the prompt, we can see that Generic < integer > 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 never define a new method to handle classes of generic < integer > type, which is obviously contrary to the concept of multiple machines in java. Therefore, we need a reference type that can logically represent both generic < integer > and generic < number > 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());
}
Copy code

Type wildcards are generally used? Instead of specific type arguments, note that '?' Is a type argument, not a type parameter. Say it three times! Here '?' Is a type argument, not a type parameter! Here '?' Is a type argument, not a type parameter! To be more straightforward 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.

4.6 generic methods

In java, the definition of generic classes is very simple, but generic methods are more complex.

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

Generic class indicates the specific type of the generic when instantiating the 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 that use generics in generic classes 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 classes, T here can be written as any identifier. Common parameters such as T, E, K, V, etc. are often used to represent generics.
 */
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
  IllegalAccessException{
        T instance = tClass.newInstance();
        return instance;
}
Copy code
Object obj = genericMethod(Class.forName("com.test.test"));
Copy code

4.6.1 basic usage of generic methods

Just look at the above example, some students may still be very confused. Let's summarize my 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;
    }

    /**
     * There is obviously a problem with this method. The compiler will prompt us with the error message "cannot reslove symbol E"
     * Because generic e is not declared in the class declaration, the compiler will not recognize it when using E as formal parameter and return value type.
    public E setKey(E key){
         this.key = keu
    }
    */
}

/** 
 * This is a real generic method.
 * First & lt; between public and the return value; T> 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 & lt; T,K>  K showKeyName(Generic<T> container){
 *        ...
 *        }
 */
public &lt;T&gt; T showKeyName(Generic&lt;T&gt; 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, it is a common method, only using generic & lt; Number> This generic class is just a formal parameter.
public void showKeyValue1(Generic&lt;Number&gt; 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 the general wildcard described in the chapter,? It is a type argument and can be regarded as the parent of all classes such as Number
public void showKeyValue2(Generic&lt;?&gt; obj){
    Log.d("Generic testing","key value is " + obj.getKey());
}

 /**
 * There is a problem with this method. The compiler will prompt us with an error message: "unknown class' e '"
 * Although we declare & lt; T>, It also shows that this is a generic method that can handle generic types.
 * However, only the generic type T is declared, and the generic type E is not declared, so the compiler does not know how to deal with the type E.
public &lt;T&gt; T showKeyName(Generic&lt;E&gt; container){
    ...
}  
*/

/**
 * There is also a problem with this method. The compiler will prompt us with an error message: "unknown class't '"
 * For the compiler, T is not declared in the project, so the compiler does not know how to compile this class.
 * So this is not a correct generic method declaration.
public void showkey(T genericObj){

}
*/

public static void main(String[] args) {


}

}
Copy code

4.6.2 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&lt;T&gt;{
    public void show_1(T t){
        System.out.println(t.toString());
    }

    //A generic method is declared in a generic class, using generic E, which can be of any type. The type can be the same as T or different.
    //Because generic methods declare generics & lt; E>, Therefore, the compiler can correctly recognize the generics recognized in generic methods even if they are not declared in generic classes.
    public &lt;E&gt; void show_3(E t){
        System.out.println(t.toString());
    }

    //A generic method is declared in the generic class and generic t is used. Note that this t is a new type and can not be the same type as the T declared in the generic class.
    public &lt;T&gt; 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&lt;Fruit&gt; generateTest = new GenerateTest&lt;Fruit&gt;();
    //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);
}

}
Copy code

4.6.3 generic methods and 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);
    }
}
Copy code
printMsg("111",222,"aaaa","2323.4",55.55);
Copy code

4.6.4 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 a static method that uses generics in a class, you need to add additional generic declarations (define this method as a generic method)
     * Even if static methods want to use generics already 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){
}

}
Copy code

4.6.5 generic method summary

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 generize the entire class using generic methods,

Then 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.

4.6 Generic upper and lower boundaries

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());
}
Copy code
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);
Copy code

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;
}

}
Copy code

//This line of code will also report an error because String is not a subclass of Number
Generic<String> generic1 = new Generic<String>("11111");
Copy code

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, when the generic method is declared
//Public < T > t showkeyname (generic < T extensions 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;
}
Copy code

As can be seen from the above two examples, the addition of upper and lower boundaries of generics must be combined with the declaration of generics.

4.7 about generic arrays

I've seen that generic arrays are mentioned in many articles. After checking sun's documentation, it is "unable to create an exact array of generic types" in java.

In other words, the following example is not acceptable:

List<String>[] ls = new ArrayList<String>[10]; 
Copy code

It is possible to create generic arrays using wildcards, such as the following example:

List<?>[] ls = new ArrayList<?>[10]; 
Copy code

This is also possible:

List<String>[] ls = new ArrayList[10];
Copy code

Use below SunA document for To illustrate this problem:

List<String>[] lsa = new List<String>[10]; // Not really allowed.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List<Integer> li = new ArrayList<Integer>();    
li.add(new Integer(3));    
oa[1] = li; // Unsound, but passes run time store check    
String s = lsa[1].get(0); // Run-time error: ClassCastException.
Copy code

In this case, due to the erasure mechanism of JVM generics, the JVM does not know the generics information at runtime, so oa[1] can be assigned an ArrayList without exception,

However, a type conversion is required when fetching data, so ClassCastException will appear. If you can declare a generic array, there will be no warnings and errors in the above situation at the compile time, and errors will occur only at run time. The declaration of generic arrays is limited. In this case, you can prompt the code for type safety problems at compile time, which is much better than no prompt.

The following wildcard methods are allowed: the type of the array cannot be a type variable, unless it is a wildcard method, because for the wildcard method, the final extraction of data requires explicit type conversion.

List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
Object o = lsa;    
Object[] oa = (Object[]) o;    
List<Integer> li = new ArrayList<Integer>();    
li.add(new Integer(3));    
oa[1] = li; // Correct.    
Integer i = (Integer) lsa[1].get(0); // OK
 Copy code

5. Finally

The examples in this article are simply given to illustrate some ideas in generics, and do not necessarily have practical usability. In addition, when it comes to generics, I believe you use them most in collections. In fact, in the actual programming process, you can use generics to simplify development and ensure code quality.

It's almost enough to write here. Please give me a compliment and pay attention!

Keywords: Java Back-end

Added by DSM on Thu, 06 Jan 2022 11:57:52 +0200