The concept of generics

Generics are a new feature in Java SE5. The essence of generics is parameterization of types or parameterized types. In the case of not creating new types, different types specified by generics control the specific types of parameters.

The meaning of generics

For generic classes and methods, only concrete types can be used: either primitive types or custom classes. This rigid constraint can be very restrictive if you want to write code that can be applied to multiple types.

Before the introduction of generics, Java represented mutable objects, which were usually implemented using Object, but there were security risks when performing type casts. With generics:

  • Determine the type at compile time, keep it type-safe, and get what you put and get without worrying about throwing a ClassCastException.
  • Improve readability by explicitly knowing from the coding stage what types of objects generic collections, generic methods, and so on are being processed.
  • Generics combine processing code of the same type to increase code reuse and general flexibility of programs.

Here’s an example:

public static void method1(a) {
    List list = new ArrayList();
    List.add(22);
    List.add("hncboy");
    List.add(new Object());

    for(Object o : list) { System.out.println(o.getClass()); }}Copy the code

Before using generics, we could add any type to the collection, and the result of the traversal would be converted to Object. Since we were not sure what type was stored in the collection, the output would look like this.

class java.lang.Integer
class java.lang.String
class java.lang.Object
Copy the code

With generics, you can create a collection object that explicitly specifies its type. At compile time, you can determine what type the collection is stored in. The collection can then be traversed directly as an explicit String output.

public static void method2(a) {
    List<String> list = new ArrayList();
    list.add("22");
    list.add("hncboy");
    //list.add(new Object()); An error

    for(String s : arrayList) { System.out.println(s); }}Copy the code

Representation of generics

Generics can be defined in classes, interfaces, and methods, represented as generic classes, generic interfaces, and generic methods. The use of generics requires a declaration, declared by **< symbol >, which can be arbitrary. The compiler resolves generics by recognizing Angle brackets and the letters inside them. The type of a generic can only be a class, not a primitive data type. Angle brackets are also fixed and can only be placed after the class name or before the method return value.

General generics have a convention notation: E stands for Element, usually used in collections; T stands for Type and is usually used for classes; K represents Key, V represents Value, <K, V> is usually used to represent key-value pairs. ? Represents a generic wildcard.

Generic expressions are as follows:

  • Common symbols
  • Unbounded wildcard
  • The upper bound wildcard
    Parent is E
  • The lower wildcard
    is the parent of E

Use of generics

4.1 a generic class

Defining generics after the class name allows users to pass in different types when using the class. Generics defined on a class can be used directly in instance methods without definition, but generics on static methods need to be declared on static methods and cannot be used directly. Here’s an example:

public class Test<T> {
    
    private T data;

    public T getData(a) {
        return data;
    }

    /** * T is undefined */
    /*public static T get() { return null; } * /
    / * * correct term, the method of T and T though on the class, but are two refer to, can be exactly the same, each other * /
    public static <T> T get(a) {
        return null;
    }
    
    public void setData(T data) {
        this.data = data; }}Copy the code

4.2 Generic methods

A generic method is a specific generic type specified when a method is called. Although a generic defined on a class can be used directly in an instance method, the method is not a generic method. Here’s an example: The get method is generic, and the program can compile and run because each element in Angle brackets refers to an unknown type and can be any symbol. The String in Angle brackets is not java.lang.String, but a generic identifier. The first passed in is of type Integer, so the String identifier also refers to the Integer type and the return value is of type Integer. However, this generic notation should not be defined in real life either.

public class Test {

    public static <String, T, Hncboy> String get(String string, Hncboy hncboy) {
        return string;
    }

    public static void main(String[] args) {
        Integer first = Awesome!;
        Double second = 888.0; Integer result = get(first, second); System.out.println(result); }}Copy the code

4.3 Generic wildcards

? Is a generic unqualified wildcard, indicating that the type is unknown, undeclared, can match any class. This wildcard can only be read, not written, and does not operate on the return value. It is also possible to identify where unqualified wildcards occur with plain generics, although it is more succinct to use wildcards. Here’s an example:

Test1 () prints each element of the collection as a wildcard. Test2 () does the same thing as test1(), but uses the wildcard instead. Test3 () : int (String) : int (String); Type, of course, you can’t put it in collections, because all classes have null elements, so you can put them in collections. For example, if the main function passes a List, adding a String to the collection is impossible; Test4 () is also incorrectly written,? Return value cannot be returned; Use test5() to compare a List with a List<? Test5 (list) ¶ Java.util. List<java.lang.Integer> cannot be converted to java.util.List<java.lang.Object> because List is not a subclass of List.

public class Test {

    public static void test1(List
        list) {
        for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }}public static <T> void test2(List<T> list) {
        for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }}public static void test3(List
        list) {
        //list.add(1); capture of ?
        //list.add("1"); capture of ?
        list.add(null);
    }

    /*public static ? test4(List
       list) { return null; } * /
    
    public static void test5(List<Object> list) {
        for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }}public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        test1(list);
        test2(list);
        //test5(list);}}Copy the code

The upper and lower boundaries of generics <? Can be implemented by using generic wildcards. The extend T > and <? Super T>, we will use the Number class and its subclasses to illustrate the two types of upper and lower type boundaries. The Number class diagram is shown below.

<? Extends Number> represents a class of type Number or a subclass of Number, <? Super Integer> represents a parent class of type Integer or Integer. For example, method1 methods test for upper bound Number, and since arrayList1 and arrayList2 both have generics that are Number or subclasses of Number, So the insert succeeded, and arrayList3’s type String has nothing to do with Number, so the compilation fails. ArrayList4, arrayList5, and arrayList7 are all parent classes of Integer, Object, and Number. ArrayList7 is of type Double, so the insert fails.

public class Generic {
	
	public static void main(String[] args) {
		ArrayList<Integer> arrayList1 = new ArrayList<>();
       	 ArrayList<Number> arrayList2 = new ArrayList<>();
       	 ArrayList<String> arrayList3 = new ArrayList<>();
       	 method1(arrayList1);
       	 method1(arrayList2);
         //method1(arrayList3);
        
		ArrayList<Integer> arrayList4 = new ArrayList<>();
         ArrayList<Object> arrayList5 = new ArrayList<>();
         ArrayList<Number> arrayList6 = new ArrayList<>();
         ArrayList<Double> arrayList7 = new ArrayList<>();
         method2(arrayList4);
         method2(arrayList5);
         method2(arrayList6);
         //method2(arrayList7)
	}
    
    public static void method1(ArrayList<? extends Number> arrayList) {}public static void method2(ArrayList<? super Integer> arrayList) {}}Copy the code

4.4 Generic Interfaces

A generic interface is a generic type defined on an interface. When an undefined class implements an interface, it needs to declare that type. Here’s an example:

public interface CalcGeneric<T> {
    T add(T num1, T num2);
}

public class CalculatorGeneric<T> implements CalcGeneric<T> {

    @Override
    public T add(T num1, T num2) {
        return null; }}Copy the code

4.5 Generic Arrays

Arrays are covariant. What is an array covariant? For example, in this code, arrays can be defined as 1. Because an Integer is a subclass of Number, an Integer object is also a Number object, so an array of integers is also an array of numbers, which is a covariant of an array. Although this works at compile time, arrays actually store Integer objects, and adding a Double throws an ArrayStoreException at run time, which is flawed. The definition of an array as shown in 3 compiles incorrectly, while the code indicated in 4 is correct. Generics are immutable, there are no built-in covariant types, and when you use generics, the type information is erased by the type at compile time, so generics move this error detection to the compiler. Generics are designed to be type-safe so that run-time errors can be found at compile time, so generics do not support covariances. This line of code as shown in 5 has a compile error.

public class Test {

    public static void main(String[] args) {
        Number[] numbers = new Integer[10]; / / 1
        // java.lang.ArrayStoreException: java.lang.Double
        numbers[0] = new Double(1); / / 2
        //List
      
       [] list = new ArrayList
       
        [10]; / / 3
       
      
        List<String>[] list2 = new ArrayList[10]; / / 4
        //List
      
        list3 = new ArrayList
       
        (); / / 5
       
      }}Copy the code

4.6 Generic erase

Inside a generic type, you don’t get any information about generic parameter types. Generics are only valid at compile time. Generic types can be logically viewed as multiple different types, but they are essentially the same type. Because generics came after JDK5, you need to deal with non-generic libraries from before JDK5. The core motivation for erasers is that it enables generalized clients to be implemented with non-generalized libraries and vice versa, which is often referred to as “migration compatibility”.

Cost: Generics cannot be used in operations that explicitly reference runtime types, such as transformations, instanceof operations, and new expressions, because all information about the type of the parameter is lost. Whenever you write code for this class, remind yourself that it is just an Object. The catch statement cannot catch exceptions of generic types.

For example: The run output of this line of code is, so you can see that the generic erases the type at run time.

class java.util.ArrayList
class java.util.ArrayList
true
Copy the code
public static void method1(a) {
    List<Integer> integerArrayList = new ArrayList();
    List<String> stringArrayList = new ArrayList();

    System.out.println(integerArrayList.getClass());
    System.out.println(stringArrayList.getClass());
    System.out.println(integerArrayList.getClass() == stringArrayList.getClass());
}
Copy the code

When you compile the Java code into bytecode, you can also see that both collections are Java /util/ArrayList

public static method1(a)V
    L0
    LINENUMBER 14 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> (a)V
    ASTORE 0
    L1
    LINENUMBER 15 L1
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList.<init> (a)V
    ASTORE 1
Copy the code

Because of type erasure at run time, reflection can modify the classes that can be added to the collection at run time, but querying the collection after the addition throws a ClassCastException, as shown below.

public static void method4(a) throws Exception {
    ArrayList<String> stringArrayList = new ArrayList<>();
    stringArrayList.add("hnc");
    stringArrayList.add("boy");
    System.out.println(Previous length: + stringArrayList.size());

    // Add elements by reflectionClass<? > clazz = stringArrayList.getClass(); Method method = clazz.getDeclaredMethod("add", Object.class);
    method.invoke(stringArrayList, 60);

    System.out.println("After length:" + stringArrayList.size());
    // The storage type is still Integer
    // java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    for (int i = 0; i < stringArrayList.size(); i++) { System.out.println(stringArrayList.get(i).getClass()); }}Copy the code

Five, the summary

Generics are used in ordinary learning or quite a lot.

  • Arrays do not support generics
  • The type of a generic type cannot be an underlying data type
  • Generics are only valid at compile time

Java Programming ideas

Code out efficient Java development manuals

Java generics in detail