A Brief Talk on Java Generics

Posted by amorphous on Thu, 25 Jul 2019 12:18:54 +0200

I. Overview

Generics play an important role in Java and are widely used in face-to-face object programming and various design patterns.

What is generics? Why use generics?

Generics are "parameterized types". When referring to parameters, a method is defined with tangible parameters, and an argument is passed when a method is invoked.

The essence of generics is to parameterize types (in the absence of creating new types, the types specified by generics are used to control the specific restrictions on parameters).

2. Specific examples

package OSChina.Genericity;

import java.util.Random;

public class FruitGenerator<T> implements  Generator<T>{
    String[] fruits = new String[]{"apple","banana","Pear"};
    @Override
    public String next(T t) {
        Random random = new Random();
        System.out.println(fruits[random.nextInt((int)t)]);
        return fruits[random.nextInt((int)t)];
    }

    public static void main(String[] args) {
        FruitGenerator ff = new FruitGenerator();
        ff.next(3);
    }
}

It collapsed.

But why?

ArrayList can store any type. In the example, a String type is added, an Integer type is added, and then used as String, so the program crashes. In order to solve such problems (which can be solved at the compilation stage), generics came into being.

We'll change the code that initializes the list in the first line declaration, and the compiler will be able to help us find problems like this in the compilation phase.

After defining generics, compilation can't pass, and that's what you want!

CHARACTERISTICS

Generics are valid only at the compilation stage. Look at the following code:

package OSChina.Genericity;

import java.util.ArrayList;
import java.util.List;

public class Test2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        List<Integer> list2 = new ArrayList<Integer>();
        System.out.println(list.getClass());
        System.out.println(list.getClass()==list2.getClass());
    }
}

IV. Use of generics

There are three ways to use generics: generic classes, generic interfaces, and generic methods.

(1) Generic classes

package OSChina.Genericity;

//Here T can be written as arbitrary identifiers. Common parameters such as T, E, K, V are often used to represent generics.
//When instantiating generic classes, you must specify the specific type of T
public class Generic<T> {
    //The member variable key is of type T, and the type of T is specified externally.
    private T key;
    //The type of the generic constructor parameter key is also T, and the type of T is specified externally.
    public Generic(T key){
        this.key = key;
    }
    //The return value type of the generic method getKey is T, and the type of T is specified externally.
    public T getKey(){
        return key;
    }

    public static void main(String[] args) {
        //Generic type parameters can only be class types (including custom classes), not simple types.
        //The incoming argument type needs to be the same as the generic type parameter type, namely Integer.
        Generic<Integer> genericInteger = new Generic<Integer>(123456);
        //The incoming argument type needs to be the same as the generic type parameter type, namely String.
        Generic<String> genericString = new Generic<String>("Maggie Jiang");
        System.out.println("Generic testing, key is "+genericInteger.getKey());
        System.out.println("Generic testing, key is "+genericString.getKey());
    }
}

Generic parameters mean casual passing!

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

instanceof does not allow generic parameters

The following code cannot be compiled for the same reason that generic types are erased

(2) Generic Interface

Generic interfaces and generic classes are basically the same in definition and use. Generic interfaces are often used in various classes of producers. Here's 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 definition as a generic class. When declaring a class, you need to add the generic declaration to the class as well.
 * That is: class FruitGenerator < T > implements Generator < T >{
 * If generics are not declared, 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 that implements a generic interface passes in a generic argument:

package OSChina.Genericity;

import java.util.Random;

public class FruitGenerator implements  Generator<String>{
    String[] fruits = new String[]{"apple","banana","Pear"};
    @Override
    public String next() {
        Random random = new Random();
        System.out.println(fruits[random.nextInt(3)]);
        return fruits[random.nextInt(3)];
    }

    public static void main(String[] args) {
        FruitGenerator ff = new FruitGenerator();
        ff.next();
    }
}

(3) Generic wildcards

We know that integer is a subclass of number, and Generic < Integer > and Generic < Number > are actually the same basic type. So the question arises. In the method of using Generic < Number > as a parameter, can we use the instance of Generic < Integer > to pass in? Are generic types logically similar to Generic < Number > and Generic < Integer > considered to be paternal?

To clarify this problem, we use Generator < T > as a generic class to continue with the following examples:

public void showKeyValue(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);

// showKeyValue is a method compiler that will report an error for us: Generic < java.lang.Integer>. 
// cannot be applied to Generic<java.lang.Number>
// showKeyValue(gInteger);

From the prompt information, we can see that Generic < Integer > cannot be regarded as a subclass of Generic < Number >. 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 examples, how to solve the above problems? You can't always define a new way to deal with classes of Generic < Integer > type, which is obviously contrary to multiple concepts in java. So we need a reference type that logically represents both Generic < Integer > and Generic < Number > parent classes. This type of wildcard came into being.

We can change the above method:

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

Type wildcards are commonly used? Instead of specific type parameters, notice, here? It is a type argument, not a type parameter. Here? Like Number, String, Integer, it's a real type, can you? Think of it as the parent of all types. It's a real type.

This wildcard can be solved when the specific type is uncertain; when the type of operation is not required, only the functions in the Object class are used. Then you can use? Wildcards to represent unknown types.

(4) Generic methods

Generic classes specify the specific types of generics when instantiating classes.

A generic method specifies the specific type of a generic type when calling a method.

/**
 * Basic introduction of generic methods
 * @param tClass Input generic argument
 * @return T The return value is of type T
 * Explain:
 *     1)public The < T > between the return value and the return value is very important and can be understood as declaring this method generic.
 *     2)The generic method is only the method that declares < T > and the member method that uses generic type in the generic class is not the generic method.
 *     3)<T>It indicates that generic type T will be used in the method, and then generic type T can be used in the method.
 *     4)Like the definition of generic classes, T can be written as arbitrary identifiers here. Common parameters such as T, E, K, 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"));

1. Basic usage of generic methods

public class GenericTest {
   //This class is a generic class, as described above.
   public class Generic<T>{     
        private T key;

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

        //What I want to say is actually this. Although generics are used in the method, this is not a generic method.
        //This is just a normal member method in a class, except that its return value is to declare a generic type that the generic class has declared.
        //So we can continue to use the generic T in this method.
        public T getKey(){
            return key;
        }

        /**
         * This method is obviously problematic, and 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 cannot recognize it when using E as a parameter and return value type.
        public E setKey(E key){
             this.key = keu
        }
        */
    }

    /** 
     * This is a true generic approach.
     * Firstly, <T> between public and return value is essential, which indicates that this is a generic method and declares a generic T.
     * This T can appear anywhere in this generic method.
     * The number of generics can also be arbitrary 
     *    For example: public < T, K > K showKey Name (Generic < T > container){
     *        ...
     *        }
     */
    public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //Of course, this example is not very 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, just using Generic < Number > as a 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 the use of generic wildcards?
    //It also confirms that the generic wildcard section describes? As a type argument, which can be regarded as the parent class of all classes such as Number.
    public void showKeyValue2(Generic<?> obj){
        Log.d("Generic testing","key value is " + obj.getKey());
    }

     /**
     * This method is problematic, and the compiler will prompt us with an error message: "UnKnown class'E'"
     * Although we have declared <T>, it also shows that this is a generic method that can handle generic types.
     * However, only generic type T is declared, and generic type E is not declared, so the compiler does not know how to deal with this type.
    public <T> T showKeyName(Generic<E> container){
        ...
    }  
    */

    /**
     * This method is also problematic, and the compiler will prompt us with an error message: "UnKnown class'T'"
     * For compilers, the type T is not declared in the project, so compilation does not know how to compile the class.
     * So this is not a correct generic method declaration.
    public void showkey(T genericObj){

    }
    */

    public static void main(String[] args) {


    }
}

2. Generic Approaches in Classes

Of course, this is not the whole of generic methods. Generic methods can be used anywhere and in any scenario. But there is a very special case, when generic methods appear in generic classes, let's take another look at an 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, which can be of any type. It can be the same type as T, or it can be different.
        //Because generic methods declare generics < E > when declared, the compiler can correctly identify generics recognized in generic methods even if generics are not declared in generic classes.
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //A generic method is declared in a generic class, using generic T. Note that this T is a completely new type and can not be the same type as the T declared in a 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 here you can
        generateTest.show_1(apple);
        //The compiler will report an error because the generic type argument specifies Fruit and the incoming argument class is Person.
        //generateTest.show_1(person);

        //Both methods can be used successfully.
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //Both methods can be used successfully.
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}

3. Generic Method and Variable Parameters

If static methods are to use generics, they must also be defined as generic methods.

package OSChina.Genericity;

public class GenericFruit {
    //When using generics in static methods, generics must be defined in methods.
    public static <T> void printMsg(T...args){
        for(T t:args){
            System.out.println("Generic testing, it is "+t);
        }
    }

    public static void main(String[] args) {
        printMsg("1111",2222,"Maggie Jiang","0.00",55.55);
    }
}

5. Summary of generic methods

Use generic methods as much as possible!

(5) Upper and lower boundaries of generics

Adding an upper boundary to a generic type means that the incoming type argument must be a subtype of the specified type.

static?

Wrong report! String type is not a subclass of Number type

The upper and lower boundaries of generics must be added with the declaration of generics.

(6) About generic arrays

See many articles will mention generic arrays, after looking at sun's documentation, in java is "can not create an exact generic type of array".

That is to say, the following example is not possible:

It is possible to create generic arrays with wildcards

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

The following is allowed by wildcards: the type of an array cannot be a type variable unless it is wildcards, because for wildcards, the final data is extracted by explicit type conversion.

 

Jiang Shuying talks about Java@directory

Topics: Programming Java