[java basic syntax] explain generics in Java in detail

preface:

The knowledge of generics is actually ahead Generic and wrapper classes in Java This chapter has introduced some, but those knowledge is to pave the way for the later introduction of Java collection framework. Today's chapter will fully introduce generics in Java together with the previous chapter!

1. Review of the previous chapter

1.1 code example of generic class

In the previous chapter, we introduced the basic definition of generic classes. Here, we directly create and use a stack using generics to review the definition of generics

// The occurrence of < T > indicates that the current class is a generic class and t is a placeholder
class Stack<T>{
    private T[] elem;
    private int usedSize;
    public Stack(){
        this.elem=(T[])new Object[10];

    }
    // Stack entry (regardless of stack full)
    public void push(T val){
        this.elem[this.usedSize++]=val;
    }
    // Stack out (stack empty is not considered)
    public T pop(){
        this.usedSize--;
        return this.elem[this.usedSize];
    }
}
public class TestDemo{
    public static void main(String[] args){
        Stack<Integer> stack=new Stack<Integer>();
        stack.push(1);
        stack.push(2);
        int val=stack.pop();
        System.out.println(val);
        System.out.println(stack);
    }
}
// The results are: 2 and Stack@1b6d3586

Note: why is the construction method of the above code block like this: this.elem=(T[])new Object[10];

  • If it is written as this.elem=new T[10];, So we don't know what the specific type is at compile time, so we can't directly use generics to instantiate objects
  • The reason for using the above method is that the generic erasure mechanism occurs at this time, that is, the generic T is erased as Object, so the generic at this time has the characteristics of Object, so if it is written like this.elem=new T[10]; this.elem=new Object[10];
  • However, what we want is a non universal array of non Object type, that is, no forced type conversion is required in the later stage, so we can write this.elem=(T[])new Object[10] on the premise of erasure mechanism;

1.2 significance of generic classes

  • Automatically check the type. For example, during compilation, it will check whether the values you insert match according to the information of the specified generic. After checking, the generic information will be erased
  • Automatic type conversion. For example, as long as we use generics, there is no need to force type conversion when creating an instance of a specific type

1.3 how are generics compiled

  • Generics are a mechanism during compilation, that is, erasure mechanism
  • The erasure mechanism refers to erasing the generic T to Object during compilation (at this time, all generic information is erased, and the generated Java bytecode does not contain generic key type information)

Certification method:

  • If the toString method is not overridden, the instantiated object of a class is output, and the result is: type @ object address
  • The print result of the above code is: Stack@1b6d3586 , instead of stack < integer > @1b6d3586, that is, the generic information is erased during compilation

2. Definition of generic class

2.1 syntax

  • A type parameter

    class Generic class name<type parameter >{
        // Type parameters can be used directly in this code block
    }
    
  • Multiple type parameters

    class Generic class name<Type parameter 1, Type parameter 2, ..., type parameter  n>{
        // All type parameters can be used directly in this code block
    }
    
  • Generic classes can inherit classes (including generic classes)

    class Generic class name<type parameter > extends Parent class name<type parameter >{
        // All type parameters can be used directly in this code block
    }
    
  • A generic class can be an interface

    interface Generic class name<type parameter >{
        // Type parameters can be used directly in this code block
    }
    

Common type parameters: type parameters are generally represented by a capital letter, and often have the following names

  • E: Represents an Element, that is, an Element, used in a collection
  • K: Indicates Key, i.e. Key
  • 5: Represents Value, i.e. Value
  • N: Represents Number, that is, the value type
  • T: Represents Type, that is, Java Type
  • ? : Represents an ambiguous Java type

2.2 example

class Stack<T>{
    private T[] elem;
    private int usedSize;
    public Stack(){
        this.elem=(T[])new Object[10];

    }
    // Stack entry (regardless of stack full)
    public void push(T val){
        this.elem[this.usedSize++]=val;
    }
    // Stack out (stack empty is not considered)
    public T pop(){
        this.usedSize--;
        return this.elem[this.usedSize];
    }
}

3. Internal class

3.1 concept

A class defined inside a class is called an inner class

Classification:

  • Local internal class: a class defined in a method, which is rare
  • Instance inner class: refers to an inner class that is not decorated with static. It is also called non static inner class in some places
  • Static inner class: refers to the inner class decorated with static
  • Anonymous inner class: an inner class without a name

3.2 instance inner class

Example code:

class OuterClass{
    // In external classes, member variables can be defined normally
    public int data1=1;
    public static int data2=2;
    private int data3=3;
    
    // Define instance internal classes
    class InnerClass{
        public int data4=4;
        // Static variables in an instance internal class cannot be defined
        // public static int data5=5; 	 The variable cannot be defined
        // But add a final to define it
        public static final int data5=5;
        private int data6=6;
        
        public void func(){
            System.out.println("This is an internal class of strength func Method can also be defined normally");
            System.out.println(data1);
            System.out.println(data2);
            System.out.println(data3);
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);
        }
    }
}

Conclusion 1: a static member variable cannot be defined in the internal class of an instance

Because the calling of the class inside the instance needs to depend on the object, and the members decorated with static are static and independent of the object. For example, it is impossible to define static variables in ordinary methods

Conclusion 2: if you add a final, you can use static in the inner class of the instance

Because it represents a constant, which has been determined during compilation

Conclusion 3: the way to instantiate the internal class of an instance is to instantiate the external class first, and then instantiate it in the form of the second line of code below

OuterClass outerClass=new OuterClass();
OuterClass.InnerClass innerClass=outerClass.new InnerClass();

Conclusion 4: the methods in the inner class of the instance can also call some member variables of the outer class

innerClass.func();
// The result is:
// This is a func method of an internal class, which can also be defined normally
// 1 2 3 4 5 6

Conclusion 5: if the variable name defined in the internal class of the instance is the same as a variable name in the external class, the internal class of the instance calls the variables of the internal class by default. Using this also means the object of the internal class at this time. If you want to use the variable with the same name of the external class, you can call it through: external class name. This. External class variable name

Conclusion 6: when we look at the bytecode file of the static inner class we defined, it is actually like this

Application:

For example, when we create a linked list, the Node node is defined outside the LinkedList class, but the Node class can be written as an instance internal class

3.3 static internal class

Example code:

class OuterClass{
    // In external classes, member variables can be defined normally
    public int data1=1;
    public static int data2=2;
    private int data3=3;
    
    // Define static inner classes
    static class InnerClass{
        public int data4=4;
        public static final int data5=5;
        private int data6=6;
        
        public void func(){
            System.out.println("This is an internal class of strength func Method can also be defined normally");
            System.out.println(data1);
            System.out.println(data2);
            System.out.println(data3);
            System.out.println(data4);
            System.out.println(data5);
            System.out.println(data6);
        }
    }
}

Conclusion 1: the following is the method to instantiate the static inner class. Compared with the instance inner class, it does not need an external class to create an object

OuterClass.InnerClass innerClass=new OuterClass.InnerClass();

Conclusion 2: in static inner classes, ordinary member variables of outer classes cannot be called

Because ordinary member variables need to be called by objects of external classes

Conclusion 3: if you want to call ordinary member variables of external class in a static internal class, you can instantiate an external class object in the static internal class, and you can access the general member variables of the external class through this reference.

static class InnerClass{
    public OuterClass out=new OuterClass();
    System.out.println(out.data1);
}

Conclusion 4: when the inner class and the outer class have static variables with the same name, the inner class itself is called by default. To call the of an external class, you can use: external class name. Variable name

3.4 anonymous inner class

Example code:

Anonymous inner classes are not used to implement abstract methods

abstract class Person {
    public abstract void eat();
}
 
class Child extends Person {
    public void eat() {
        System.out.println("eat something");
    }
}
 
public class TestDemo {
    public static void main(String[] args) {
        Person p = new Child();
        p.eat();
    }
}
// The result is: eat something

If the above Child class is used only once, it will be troublesome to write a separate class, so anonymous inner classes can be used

abstract class Person {
    public abstract void eat();
}
 
public class TestDemo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
// The result is: eat something

Conclusion 1: because there is no name, anonymous inner classes can only be used once

Conclusion 2: the premise of using anonymous inner class is that you must inherit a parent class or implement an interface

Conclusion 3: the form of anonymous inner class is directly followed by a brace after the declared object, which writes the content to be used by the class

Application:

The most common case is the implementation of multithreading, because to implement multithreading, you must inherit the Thread class or the Runnable interface

4. Use of generic classes

4.1 syntax

Generic class<type argument > Variable name = new Generic class<type argument >(Construction method arguments);

4.2 example

Stack<Integer> stack=new Stack<Integer>();

4.3 Type Inference

When the compiler can deduce type arguments according to the context, the filling of type arguments can be omitted

In the above example, the latter type argument can be omitted

Stack<Integer> stack=new Stack<>();

5. Raw Type

Concept:

A bare type is a generic class without type parameters

Example: the generic class Stack < T > created by the above code is a bare type if we take out the Stack list and don't add < T > to use it. We can directly use it to instantiate objects

Stack list = new Stack();

be careful:

We should not use naked types ourselves. Naked types are mechanisms reserved for compatibility with older versions of the API. If you use it, it will be no different from not using generics, and the role and significance of generics will be lost

6. Type boundary of generic class

6.1 concept

When defining generic classes, you sometimes need to make certain constraints on the incoming type parameters, which can be constrained by type boundaries

be careful:

Generics have only an upper bound and no lower bound

6.2 syntax

class Generic class name<Type parameter extends Type boundary>{
    
}

The type parameters that can be passed in by the above generic class must be the class or subclass of the type boundary

6.3 examples

Example 1: let generic parameters only accept subtypes of numeric class Number

class Stack<T extends Number>{
    
}

Therefore, it is possible to pass a generic parameter Integer, but not String

Stack<Integer> l1;	// Correct, because Integer is a subtype of Number
Stack<String> l2;	// Compilation error because String is not a subtype of Number

Example 2: write a generic class Algorithm. We want a method in this class to find the maximum value of the array

  • In fact, my first idea is to write like this

    class Algorithm<T>{
        public T findMax(T[] array){
            T max=array[0];
            for(int i=0;i<array.length;i++){
                if(array[i]>max){
                    max=array[i];
                }
            }
        	return max;
        }
    }
    

    However, if an error is reported, if you want to estimate that the generic parameter is actually a class type, that is, the size comparison is a reference value, you need to use the Comparable interface or Comparator interface

  • Then I directly use the compareTo method, but I find that it can't be used for the following reasons

    This is because the T is erased as Object due to type erasure. We know that Object is the ancestor of all classes and does not inherit any classes or interfaces. Therefore, the compareTo method cannot be used

  • To this end, we have such a way of writing

    class Algorithm<T extends Comparable<T>>{
        public T findMax(T[] array){
            T max=array[0];
            for(int i=0;i<array.length;i++){
                if(array[i].compareTo(max)>0){
                    max=array[i];
                }
            }
            return max;
        }
    }
    

    Here, the type boundary is used to make a constraint, which represents the place where the Comparable interface is erased during erasure. Generally speaking, if it is written like this, the T must implement the Comparable interface, and when erasing, it will not be erased as an Object, but as a Comparable interface

Question: Example 2 inherits the Comparable interface. Why didn't you override the compareTo method?

Because the parameter type we want to pass in must implement the Comparable interface. Now that it has been implemented, the compareTo method has been overridden in this parameter type

7. Type erase

7.1 concept

  • Generics are a mechanism that works during compilation. In fact, there are not so many classes in operation. What type is it during operation? This is what type erasure does
  • Type erasure is mainly determined by its type boundary

Add: what does the compiler do in the type erasure phase?

  1. Replace the type variable with the erased type
  2. Add the necessary type conversion statements
  3. Add the necessary bridge method to ensure the correctness of polymorphism

7.2 example

Example 1: Object after erasure

class Stack<T>{
    
}

Example 2: type boundary after erasure (here is Comparable)

class Stack<T extends Comparable<T>{
    
}

8. Use of Wildcards (Wildcards)

8.1 introduction

The purpose of this code is to traverse the sequence table

class Generic{
    public static<T> void print(ArrayList<T> list){
        for(T t: list){
            System.out.print(t+" ");
        }
        System.out.println();
    }
}

In the above code, we use generics and specify that its type parameter is t, so when we use this method, we already know that its type is t. The T is specified by us. Sometimes the method itself does not know what the parameter type of the passed in sequence table is? How should I write it?

Here we need to use wildcards?

class Generic{
    // Since you don't know the specific type, you don't need to add < T > after static
    public static void print(ArrayList<?> list){
        // Because you don't know what the specific type is, you use Object
        for(Object obj: list){
            System.out.println(obj+" ");
        }
        System.out.println();
    }
}

8.2 wildcard - upper bound

Syntax:

<? extends upper bound>

Indicates that the type argument that can be passed in is any type of a subclass of the upper bound type

Example:

// The type argument that can be passed in the Stack object is any type of Stack of the Number subclass
public static void printAll(Stack<? extends Number> stack){
    
}

// The following calls are correct
printAll(new Stack<Integer>());
printAll(new Stack<Double>());
printAll(new Stack<Number>());

// The following call is a compilation error
printAll(new Stack<String>());
printAll(new Stack<Object>());

8.3 wildcard - lower bound

Syntax:

<? super Lower bound>

Indicates that the type argument that can be passed in is any type of the parent of the lower bound type

Example:

// The type argument that can be passed in the Stack object is any type of Stack of the Integer parent class
public static void printAll(Stack<? Super Integer> stack){
    
}

// The following calls are correct
printAll(new Stack<Integer>());
printAll(new Stack<Object>());
printAll(new Stack<Number>());

// The following call is a compilation error
printAll(new Stack<String>());
printAll(new Stack<Double>());

9. Parent child types in generics

We know that Object is the parent type of Number and Number is the parent type of Integer

However, classes such as stack < Object > are not the parent type of stack < number > and stack < number > is not the parent type of stack < integer >.

Because the parameter types of generic types do not participate in the composition of types

If you want to determine the parent-child type of a generic, you need to use wildcards, such as

Stack<?> Stack <? Parent type of extensions number >, stack <? Extensions number > is also the parent type of stack < integer >

10. Generic methods

10.1 syntax

Method qualifier <Type parameter list> Return value type method name(parameter list ){
    
}

10.2 example

Example 1: write a generic class Algorithm. We want a method in this class to realize the exchange of two values in the array. It is required to use this method without instantiating the object

class Algorithm{
    public static<T> swap(T[] array,T i, T j){
        T tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }
}

Example 2: write a generic class Algorithm. We want a method in this class that can find the maximum value of the array. It is required to use this method without instantiating the object

class Algorithm{
    public static<T extends Comparable<T>> T findMax(T[] array){
        T max=array[0];
        for(int i=1;i<array.length;i++){
            if(array[i].compareTo(max)>0){
                max=array[i];
            }
        }
        return max;
    }
}

10.3 Type Inference

When the compiler can deduce type arguments according to the context, the filling of type arguments can be omitted

Example: find the maximum value of the array through the Algorithm class of example 2 in the example

Integer[] array={1,4,2,9,10};
// Using < Integer > means that all the values we want to pass in are of Integer type
Integer ret=Algorithm.<Integer>findMax(array);

However, since we can judge from the above that the value is of Integer type, the above code can omit < Integer >

Integer[] array={1,4,2,9,10};
Integer ret=Algorithm.findMax(array);

11. Limitations of generics

  • Generic type parameters do not support basic data types
  • Cannot instantiate an object of generic type
  • Static properties cannot be declared using generic types
  • Cannot use instanceof to determine a generic type with a type parameter
  • Cannot create generic type array
  • Cannot create, catch, throw a generic class exception, that is, the exception does not support generics
  • Generic types are not part of formal parameters and cannot be overloaded

Tags: Java Back-end JavaSE

Posted on Wed, 10 Nov 2021 22:54:58 -0500 by electricshoe