Java -- Generic explanation

Keywords: Java Back-end

background

Before Java introduces generics, programmers can build a collection with element type Object, which can store any data type Object. In the process of using this collection, programmers need to know the data type of each element, otherwise ClassCastException is easy to occur.

Such as the following cases:

package fanxing;

import java.util.ArrayList;

public class Demo1 {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("String"); // character string
        list.add(5000); // integer
        list.add(true);
        list.add(false); // boolean

        list.forEach(e->{
            String s = (String)e;
            System.out.println("--->"+s);
        });
    }
}

Similar to the above case, no error is reported during compilation, and only ClassCastException occurs during program execution.

This kind of problem is very terrible.


Next, let's learn about the use of generics.

brief introduction

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.

Type parameterization.

Add a generic constraint to the above case as follows:

Types can be checked during compilation before the program is executed. Generics can make program execution more secure.

Generic class, generic interface

The syntax requirements are as follows:

class Class name <Generic 1,Generic 2,,,,>{}

Case 1:

package fanxing;

public class FanxingClass {
    public static void main(String[] args) {
        Generic<String> stringGeneric = new Generic<String>("a");
        String key = stringGeneric.getKey();
        System.out.println(key);

        Generic<Integer> integerGeneric = new Generic<>(100);
        Integer key1 = integerGeneric.getKey();
        System.out.println(key1);
		
		// When a generic class creates an Object, if no type is specified, it operates according to the Object type
        Generic generic1 = new Generic(100000);
        Generic generic2 = new Generic("100000");
		
		// The specified generic class, the objects created according to different data types, are essentially the same type
        System.out.println(stringGeneric.getClass()); // class fanxing.Generic
        System.out.println(stringGeneric.getClass() == integerGeneric.getClass()); // true
    }
}

/**
 * Definition of generic classes
 * @param <T> Generic identifier --- type parameter;
 *           T When creating an object, specify the specific data type
 */
class Generic<T>{
    // The type T is specified externally when it is created
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

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

    @Override
    public String toString() {
        return "Generic{" +
                "key=" + key +
                '}';
    }
}

[Note:]

  • 1. Generic types do not support basic data types, only class types.
  • 2. Generic class. If no specific data type is specified, the operation type at this time is Object.
  • 3. The specified generic class, the objects created according to different data types, are essentially the same type
    System.out.println(stringGeneric.getClass() == integerGeneric.getClass()); // true
    

Case 2:

package fanxing;

import java.util.HashMap;
import java.util.Map;

public class FanxingClass2 {
    public static void main(String[] args) {
        Generic2<String, Object> stringObjectGeneric2 = new Generic2<>();
        stringObjectGeneric2.add("1","6666"); // value is string
        stringObjectGeneric2.add("2",222); // value is Integer
        
        System.out.println(stringObjectGeneric2.get("1"));
        System.out.println(stringObjectGeneric2.get("2"));
    }
}
class Generic2<K,V>{
    private K key;
    private V value;
    private Map<K,V> map = new HashMap<>();

    public void add(K key,V val){
        map.put(key,val);
    }
    public V get(K key){
        return map.get(key);
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }
}


[question] if a subclass inherits from the parent class, is the parent class generic?

package fanxing;

public class FatherChildTest1 {
    public static void main(String[] args) {
        
    }
}
class Father1<E>{
   private E key;

    public E getKey() {
        return key;
    }

    public void setKey(E key) {
        this.key = key;
    }
}
// If the subclass does not specify a type, the parent and subclass are the default Object
class Child1 extends Father1{
    @Override
    public Object getKey() {
        return super.getKey();
    }
}
// The subclass must be consistent with the generic wildcard in the parent class
class Child2<T> extends Father1<T>{
    @Override
    public T getKey() {
        return super.getKey();
    }
}
// When the generic types of subclasses are diversified, one must be the same as that of the parent class
class Child3<T,E> extends Father1<T>{
    @Override
    public T getKey() {
        return super.getKey();
    }
}
// This is the compilation error!
//class Child4<T> extends Father1<E>{
//
//}

// If the inherited parent class is specified as String, the subclass type must be limited to the defined String type
class Child5 extends Father1<String>{
    @Override
    public String getKey() {
        return super.getKey();
    }
}

[Note:]

  • 1. A generic class generates a subclass, and the subclass is also a generic class. The generic ID (one of them) of the subclass must be consistent with that of the parent class. (the subclass uses T, and the parent class must also be T)

  • 2. If the child class is not a generic class, the default type of the parent class is Object; If the parent class specifies a specific type, the type of the child class is also specified.

[Note:]

The current rules also apply to generic class interfaces!

Generic wildcard

When defining generic classes, generic methods and generic interfaces, we often encounter many different wildcards, such as T, E, K, V and so on.

In essence, using any one of A~Z as a wildcard is feasible and will not affect the operation of the program, but the commonly used ones are T, E, K, V,? these They have their own definitions and functions.

  • ? 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 general distribution

Let's take a look at an example:

package fanxing;

public class TPDemo {
    public static void main(String[] args) {
    	// Specifies that the generic type is Number
        NumTest<Number> numberNumTest = new NumTest<>();
        numberNumTest.setValue(11);
        test1(numberNumTest); // Compile without error and run successfully
    }
    // Set the generic type to Number
    public static void test1(NumTest<Number> num){
        Number value = num.getValue();
        System.out.println(value);
    }
}
class NumTest<E>{
    private E value;

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}

The above code is compiled and run successfully.

In Java types, due to the inheritance relationship of class Integer extensions number, what if an Integer type is passed at the beginning?

However, can't various subclasses of Dao Number have a similar inheritance relationship in generic types, or do you need to write different receiving and processing methods for different types, as shown below?

Of course, the answer is not required. You only need to use "yes" for uncertain types? Just use wildcards. As follows:

Type wildcard upper limit (extends)

The syntax is as follows:

class/Interface<? extends actual argument >

requirement:
The type of this generic type can only be an argument type or a subclass type of an argument type.

[question] what is the upper limit?

The upper limit is that the maximum type can only reach the specified type.

It's still very tongue twister. Look at the following examples:

public class TPDemo {
    public static void main(String[] args) {
        NumTest<Number> numberNumTest = new NumTest<>();
        numberNumTest.setValue(11);
        test1(numberNumTest);

        NumTest<Integer> integerNumTest = new NumTest<>();
        integerNumTest.setValue(22);
        test1(integerNumTest);
    }
    // Here is the upper limit of universal configuration
    public static void test1(NumTest<? extends Number> num){
        Number value = num.getValue();
        //Object value = num.getValue(); //  use? Wildcard. The data receiving method is changed to object type
        System.out.println(value);
    }
}
class NumTest<E>{
    private E value;

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}

The upper limit here is expressed as numtest <? Extensions Number >, that is? Represents the wildcard type. If the formal parameter is set, the maximum range of its type can only be Number.

? The type represented can only be Number type or subclass of Number, such as Integer.

[Note 2:]
Similar operations such as type assignment are not allowed in the corresponding method using the upper bound extends. As follows:

Because the num type here is numtest <? Extensions Number > is qualified, but there are many subclasses of Number (such as Integer, AtomicInteger, Double, Long, etc.), and the num here does not know exactly what type it is!

You can define your own inheritance classes for children, grandchildren, and grandchildren to test.

Lower limit of wildcard (super)

Syntax requirements:

class/Interface <? super actual argument >

requirement:

The type that requires this generic type can only be an argument type or a parent type of an argument type


Still refer to the following cases:

public class TPDemo {
    public static void main(String[] args) {

          NumTest<Object> objectNumTest = new NumTest<>();
          objectNumTest.setValue(222);
          testSuper(objectNumTest);

    }
    public static void testSuper(NumTest<? super Number> num){
    	//num.setValue(111); //  Compile without error
        Object value = num.getValue();
        System.out.println(value);
    }
}
class NumTest<E>{
    private E value;

    public E getValue() {
        return value;
    }

    public void setValue(E value) {
        this.value = value;
    }
}

Must be the specified type or its parent class.

[question] what happens if you pass subclasses of the specified type?

Compilation error

Summary of lower and upper limits

The upper and lower limits are relative to argument types.

  • Upper limit: the maximum range can only reach the argument type.
  • Lower bound: the lowest range is the argument type.

Type Erasure

Generic information only exists in the code compilation stage. At run time, information related to generics will be erased, which is called type erasure.

The following example is shown:

import java.util.ArrayList;

public class Demo3 {
    public static void main(String[] args) {
        ArrayList<String> strings = new ArrayList<>();
        ArrayList<Integer> integers = new ArrayList<>();

        System.out.println(strings.getClass().getSimpleName());
        System.out.println(integers.getClass().getSimpleName());

        System.out.println(strings.getClass() == integers.getClass());
    }
}

At compile time, specific applicable types are specified respectively, but at run time, the information is as follows:

Secondly, you can also obtain the type of the corresponding attribute according to the reflection to analyze and judge. The test code is as follows:

import java.lang.reflect.Field;

public class Demo3 {
    public static void main(String[] args) {
        NumTest1<Integer> integerNumTest1 = new NumTest1<>();
        Class<? extends NumTest1> aClass = integerNumTest1.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName());
        }
    }
}
// Specified T
class NumTest1<T>{
    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}


What if you add an upper bound constraint?

Reference materials

T, E, K, V? Wait, what the hell is it?

Posted by fipp on Thu, 21 Oct 2021 22:00:01 -0700