preface

Generics are the focus of the basic knowledge of Java, even though we are beginners Java, have learned to generics, feel oneself to master for the use of Java generics (all illusion), the next day, when we look into source code to read some frame, you will find, will is the use of simple, but look not to understand others is how to write generic code, Can also like this, yes, others write out of the code that is called art, and I……

To discuss

Why do generics exist in the Java language, while JavaScipt, like some dynamic languages Python, has no concept of generics?

For statically compiled languages like Java and C#, the type of the parameter must be specified when passing it.

public class StackInt {

    private int maxSize;
    private int[] items;
    private int top;

    public StackInt(int maxSize){
        this.maxSize = maxSize;
        this.items = new int[maxSize];
        this.top = -1;
    }

    public boolean isFull(a){
        return this.top == this.maxSize-1;
    }

    public boolean isNull(a){
        return this.top <= -1;
    }

    public boolean push(int value){
        if(this.isFull()){
            return false;
        }
        this.items[++this.top] = value;
        return true;
    }

    public int pop(a){
        if(this.isNull()){
            throw new RuntimeException("No data in current stack");
        }
        int value = this.items[top];
        --top;
        returnvalue; }}Copy the code

When I initialize a StackInt object using the constructor here, can I pass in a String? We’re going to pass in an int, we’re going to pass in a String, we’re going to get an error in the syntax check phase, statically compiled languages like Java, we’re going to specify the type of the argument

What problems do generics solve?

Parameter insecurity: The introduction of generics allows you to identify code problems at compile time, not run time

Generics require that the actual data types be specified at declaration time, and the Java compiler performs strong type checking on generic code during compilation and raises alarms when code violates type safety. Early detection, early treatment, and early prevention of potential problems, at compile time to find and fix errors is far less costly than at run time.

Avoid type conversions:

Not using generics:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);    // The cast is required when the Value is fetched
Copy the code

Using generics:

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // No cast is required
Copy the code

Repetitive coding: By using generics, common coding can be achieved, collections of different types can be handled, and are type-safe and easy to read. Like the StackInt class above, we can’t write a stack for each type. That would be too cumbersome, but generics are a good solution

extension

The disadvantages of the StackInt class are that it is too concrete, not abstract enough, and not very reusable. For example, in other scenarios, I need to store strings on the stack, or other types of strings, and the StackInt class cannot do that. So what’s the way to do that? Write another StackString class, no way, that’s not exhausting. We’ll just have to introduce the base class Object.

public class StackObject {

    private int maxSize;
    private Object[] items;
    private int top;

    public StackObject(int maxSize){
        this.maxSize = maxSize;
        this.items = new Object[maxSize];
        this.top = -1;
    }

    public boolean isFull(a){
        return this.top == this.maxSize-1;
    }

    public boolean isNull(a){
        return this.top <= -1;
    }

    public boolean push(Object value){
        if(this.isFull()){
            return false;
        }
        this.items[++this.top] = value;
        return true;
    }

    public Object pop(a){
        if(this.isNull()){
            throw new RuntimeException("No data in current stack");
        }
        Object value = this.items[top];
        --top;
        returnvalue; }}Copy the code

Stackobjects can store arbitrary types of data, so what are the advantages and disadvantages of doing so?

Advantages:StackObjectClasses have become relatively abstract, and we can store any type of data in them without having to write repetitive code

Disadvantages:

1. The Object represented by Object is relatively abstract, and it loses the characteristics of its type. Therefore, when we do some operations, we may frequently unpack and unpack the objects

, look at the case diagram, we understand that for the two Numbers, 12345 and 54321, will be the two together, this is a very common operation, but an error that the compiler to our tip is that + operation operation can not be used in two Object types, you can only type conversion, this is also our above said to generics can solve the problem, We need to do this, int sum = (int)val1 + (int)val2; At the same time, when it comes to unpacking, there is a certain performance loss, about unpacking is not described here, you can refer to my essay – in-depth understanding of Java packing and unpacking

2, for the value we push in, when we take out, we tend to forget the type conversion, or do not remember its type, type conversion error, which may cause trouble in the following business, such as the following scenario: It is not safe to expose errors until runtime, and it is against the principles of software development that problems should be identified and resolved as early as possible at compile time

3. Using Object is too vague and has no specific type meaning

It is better not to use Object, because Object is the base class of all types. That is to say, it removes some characteristics of types, such as numbers. For numbers, addition operation is one of its characteristics, but with Object, it loses this characteristic, and loses type-specific behavior

Introduction of generic

What are generics?

Generics: are parameterized classes or interfaces, conventions for types

A generic class

class name<T1.T2. .Tn> { / *... * / }
Copy the code

In general, class names in generics are called prototypes, and parameters specified by <> are called type parameters. <> is the convention of type, and T is the type, which is a placeholder that we specify at call time

Use generics to improve the StackObject class above, but arrays and generics don’t work well together. You cannot instantiate an array with a parameterized type, for example the following code fails:

public StackT(int maxSize){
    this.maxSize = maxSize;
    this.items = new T[maxSize];
    this.top = -1;
}
Copy the code

It is not allowed to create generic arrays directly in Java. This is because Java generics are actually pseudo-generics compared to C++ and C# syntax, as discussed later. However, you can create generic arrays by creating an erased array and then transforming it.

private int maxSize;
private T[] items;     
private int top;

public StackT(int maxSize){
    this.maxSize = maxSize;
    this.items = (T[]) new Object[maxSize];
    this.top = -1;
}
Copy the code

In fact, do you really need to store generics, or is it better to use containers? Going back to the original code, you need to know that generic types can’t be primitive types, they need to be wrapper classes

Said to the above are not allowed to create an array of generic directly in Java, in fact, the generic Java it is difficult for us to pass the new way to instantiate objects, not just instantiate objects, or even get T true type is very difficult, of course, through reflection mechanism is available, the Java access to real there are three types of way, Respectively is:

1, class name

2. Object.getClass

3, class. ForName (” fully qualified class name “)

In this case, however, neither 1 nor 2 can be done, even though we explicitly pass in an Integer type outside, new StackT

(3); But in StackT

Class, you can’t get the real type by using t.class. The second method does not pass in the object. There is no way to instantiate the new method. How does Java get the real type of a generic class? How does Java get the type of a generic through reflection

But in C# generics and C++ templates, this is easy to do, so Java generics are pseudo generics. Java is not unable to do the same as C#, but to accommodate the older JDK syntax compromise, which is why it can’t do this, which is to say that the type erasure of generics.

Before we talk about type erasure, let’s talk about generic interfaces and generic methods

A generic interface

Interfaces can also declare generics, which have the syntax form:

public interface Content<T> {
    T text(a);
}
Copy the code

Generic interfaces can be implemented in two ways:

  • Subclasses that implement interfaces explicitly declare generic types
public class ContentImpl implements Content<Integer> {
    private int text;

    public ContentImpl(int text) {
        this.text = text;
    }

    public static void main(String[] args) {
        ContentImpl one = new ContentImpl(10); System.out.print(one.text()); }}// Output:
/ / 10
Copy the code
  • Subclasses that implement interfaces do not explicitly declare generic types
public class ContentImpl<T> implements Content<T> {
    private T text;

    public ContentImpl(T text) {
        this.text = text;
    }

    @Override
    public T text(a) { return text; }

    public static void main(String[] args) {
        ContentImpl<String> two = new ContentImpl<>("ABC"); System.out.print(two.text()); }}// Output:
// ABC
Copy the code

Generic method

Generic methods are methods that introduce their own type parameters. Generic methods can be normal methods, static methods, and constructors.

Generic methods have the following syntax:

public <T> T func(T obj) {}
Copy the code

Whether or not you have a generic method doesn’t matter.

The syntax of a generic method includes a list of type parameters, in Angle brackets, that appear before the method’s return type. For static generic methods, the type parameter part must appear before the method return type. Type parameters can be used to declare return value types and can serve as placeholders for the actual type parameters derived from the generic method.

When using generic methods, it is usually not necessary to specify type parameters, because the compiler will figure out the type for us. This is called type argument inference. Type inference only works for assignment operations, not otherwise. If the result of a generic method call is passed as an argument to another method, the compiler does not perform inference. The compiler thinks that after calling a generic method, the return value is assigned to a variable of type Object.

public class GenericsMethod {
    public static <T> void printClass(T obj) {
        System.out.println(obj.getClass().toString());
    }

    public static void main(String[] args) {
        printClass("abc");
        printClass(10); }}// Output:
// class java.lang.String
// class java.lang.Integer
Copy the code

Mutable argument lists can also be used in generic methods

public class GenericVarargsMethod {
    public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<T>();
        Collections.addAll(result, args);
        return result;
    }

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A"."B"."C"); System.out.println(ls); }}// Output:
// [A]
// [A, B, C]
Copy the code

Type erasure

In fact, Java can be roughly divided into two phases, the compile phase and the run phase

In the case of Java generics, T has been erased after the compile phase, so at run time, it has lost the details of T, and when we instantiate an object, such as T c = new T(); At run time, if you want new T(), you need to know the type of T. In fact, T is replaced by an Integer, and the JVM does not know the type of T, so there is no way to instantiate it.

So what does type erasure do? It does the following:

  • Replaces all type parameters in a generic type with Object, using a type boundary if specified. Therefore, the generated bytecode contains only ordinary classes, interfaces, and methods.
  • Erasing the occurrence of a type declaration, that is, removing it<>The content of the. Such asT get()The method declaration becomesObject get()List<String>Becomes aList. If necessary, type conversions are inserted to preserve type safety.
  • Bridge methods are generated to preserve polymorphism in extended generic types. Type erasure ensures that no new classes are created for parameterized types; Therefore, generics incur no runtime overhead.

Let’s look at an example:

import java.util.*;

public class ErasedTypeEquivalence {

    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = newArrayList<Integer>().getClass(); System.out.println(c1 == c2); }}/* Output:
true
*/
Copy the code

ArrayList

and ArrayList

should be different types. Different types have different behaviors. For example, if you try to put an Integer into ArrayList

, you get completely different behavior (failure) than if you put an Integer into ArrayList

(success). But the output is true, which means that with generics, any specific type information is erased, and ArrayList
and ArrayList

are treated by the JVM as the same type class java.util.arrayList at run time




Here’s another example to complement the puzzle:

import java.util.*;

class Frob {}
class Fnorkle {}
class Quark<Q> {}

class Particle<POSITION.MOMENTUM> {}

public class LostInformation {

    public static void main(String[] args) {
        
        List<Frob> list = new ArrayList<>();
        Map<Frob, Fnorkle> map = new HashMap<>();
        Quark<Fnorkle> quark = new Quark<>();
        Particle<Long, Double> p = newParticle<>(); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); System.out.println(Arrays.toString(map.getClass().getTypeParameters())); System.out.println(Arrays.toString(quark.getClass().getTypeParameters())); System.out.println(Arrays.toString(p.getClass().getTypeParameters())); }}/* Output:
[E]
[K,V]
[Q]
[POSITION,MOMENTUM]
*/
Copy the code

According to the JDK documentation, class.gettyPeParameters () “returns an array of TypeVariable objects representing the type parameters declared in the generic declaration…” This implies that you can find these parameter types. But as the output in the example above shows, you only see identifiers that are used as placeholders for parameters, which is not useful information.

The harsh reality is that you can’t get any information about generic parameter types inside generic code.

The above two examples are from the fifth edition of “Java Programming Ideas” – On Java 8 examples, this article with the help of this example, trying to explain Java generics is the use of type erasing mechanism here, the ability is not enough, there are errors, please correct. The book On Java 8, which is open source On Github and has been translated into Chinese by enthusiastic partners, is now available at On Java 8

Erasure problem

The cost of erasure is significant. Generics cannot be used in operations that explicitly reference runtime types, such as transformations, instanceof operations, and new expressions. Because all type information about parameters is lost, when writing generic code, you must always remind yourself that you only seem to have type information about parameters.

Consider the following code snippet:

class Foo<T> {
    T var;
}
Copy the code

It looks like when you create an instance of Foo:

Foo<Cat> f = new Foo<>();
Copy the code

The code in Class Foo should know that it now works on top of Cat. Generic syntax is also replaced where it strongly implies that all T’s occur throughout the class, as in C++. But that’s not the case. When you code this class, you have to remind yourself: “No, this is just an Object.”

succession

Generics are implemented based on type erasure, so generic types cannot be cast upward.

Upcasting refers to initializing a parent class with a subclass instance, which is an important representation of polymorphism in object orientation.

Integer inherits Object; ArrayList inherits List; But List

does not inherit from List
.

This is because generic classes do not have their own Class objects. For example, there is no List. Class or List

. Class; the Java compiler treats both as list.class.

How to solve the above problems:

We can compensate for erasations by explicitly passing in the source Class, an object of Class

clazz, such as the instanceof operation. Trying to use instanceof in an application will fail. Type tags can use dynamic isInstance() to improve the code:

public class Improve<T> {
	
    // Wrong method
    public boolean  f(Object arg) {
        // error: illegal generic type for instanceof
        if (arg instanceof T) {
            return true;
        }
        return false;
    }
    // Improve the method
    Class<T> clazz;
    
	public Improve(Class<T> clazz) {
        this.clazz = clazz;
    }

    public boolean f(Object arg) {
        returnkind.isInstance(arg); }}Copy the code

Instantiation:

Trying in new T() doesn’t work, partly because of erasure and partly because the compiler can’t verify that T has a default (no-parameter) constructor.

The solution in Java is to pass in a factory object and use it to create a new instance. A convenient factory object is just a Class object, so if you use a type token, you can use newInstance() to create a new object of that type:

class Improve<T> {
    Class<T> kind;

    Improve(Class<T> kind) {
        this.kind = kind;
    }
    
    public T get(a){
        try {
            return kind.newInstance();
        } catch (InstantiationException |
                IllegalAccessException e) {
            throw newRuntimeException(e); }}}class Employee {
    @Override
    public String toString(a) {
        return "Employee"; }}public class InstantiateGenericType {
    public static void main(String[] args) {
        Improve<Employee> fe = newImprove<>(Employee.class); System.out.println(fe.get()); }}/* Output:
Employee
*/
Copy the code

By improving the code this way, you can create instances of objects, but note that newInstance(); Method calls to parameterless constructors will throw InstantiationException if the incoming type has no parameterless constructor.

Generic arrays:

The StackT class can create a generic array by explicitly passing in the source class. The StackT class can create a generic array by explicitly passing in the source class.

public class StackT<T> {

    private int maxSize;
    private T[] items;
    private int top;

    public StackT(int maxSize, Class<T> clazz){
        this.maxSize = maxSize;
        this.items = this.createArray(clazz);
        this.top = -1;
    }

    public boolean isFull(a){
        return this.top == this.maxSize-1;
    }

    public boolean isNull(a){
        return this.top <= -1;
    }

    public boolean push(T value){
        if(this.isFull()){
            return false;
        }
        this.items[++this.top] = value;
        return true;
    }

    public T pop(a){
        if(this.isNull()){
            throw new RuntimeException("No data in current stack");
        }
        T value = this.items[top];
        --top;
        return value;
    }

    private T[] createArray(Class<T> clazz){
        T[] array =(T[])Array.newInstance(clazz, this.maxSize);
        returnarray; }}Copy the code

The border

Sometimes you may want to restrict the types that can be used as type parameters in parameterized types. Type boundaries can set constraints on type parameters of generics. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses.

To declare bounded type parameters, list the name of the type parameter, followed by the extends keyword, followed by its limiting class or interface.

The syntax for a type boundary is as follows:

<T extends XXX>
Copy the code

Example:

public class GenericsExtendsDemo01 {
    static <T extends Comparable<T>> T max(T x, T y, T z) {
        T max = x; // Suppose x is the initial maximum
        if (y.compareTo(max) > 0) {
            max = y; / / y is greater
        }
        if (z.compareTo(max) > 0) {
            max = z; // Now z is bigger
        }
        return max; // Return the largest object
    }

    public static void main(String[] args) {
        System.out.println(max(3.4.5));
        System.out.println(max(6.6.8.8.7.7));
        System.out.println(max("pear"."apple"."orange")); }}// Output:
/ / 5
/ / 8.8
// pear
Copy the code

Example description:

The above example declares a generic method, and the type parameter T extends Comparable

indicates that the type in the incoming method must implement the Comparable interface.

Multiple type boundaries can be set. The syntax is as follows:

<T extends B1 & B2 & B3>
Copy the code

Note: The first type argument after the extends keyword can be a class or an interface; the other type arguments can only be interfaces.

The wildcard

Wildcards are a very important topic in Java generics. A lot of times, we don’t really understand wildcards very well, right? It’s different from the generic type T, it’s confusing, but it’s actually pretty easy to understand,? And T are indeterminate types, the difference is that we can operate on T, but right? No, like this:

/ / can
T t = operate();
/ / can't
? car = operate();
Copy the code

But that’s not the cause of our confusion, though? And T both stand for indeterminate types. T is commonly used in the definition of generic classes and generic methods. The calling code and parameters normally used for generic methods cannot be used to define classes and generic methods. Back to the original StackT class, we are going to use this as a base to explain the above view:

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

        StackT<Integer> stackT = new StackT<>(3, Integer.class);
        stackT.push(8);
        StackT<String> stackT1 = new StackT<>(3, String.class);
        stackT1.push("Seven");
        test(stackT1);

    }
    public static void test(StackT stackT){ System.out.println(stackT.pop()); }}// Output: 8
Copy the code

I’m going to write a test method, pass it StackT, and it’s going to print the string “7”. There’s nothing wrong with that. StackT1 is defined as StackT

, but the compiler can see that stackt.pop () does not have a String method

We need to change the parameter of the test method to:

public static void test(StackT<String> stackT){
    System.out.println(stackT.pop());
}
Copy the code

This gets back to the essence of our problem. Changing the parameter to StackT

limits generics, but the problem is that if we need to pass in an object of type StackT

, StackT is, Because the method parameter qualifies StackT

, an error is reported


That’s the wildcard, right? It’s working. Change the method parameter to StackT
< span style = “max-width: 100%; clear: both; min-height: 1em; Wildcards are usually used for generic parameters, not generic class definitions.

public static void test(StackT
        stackT){
    System.out.println(stackT.pop());
}
Copy the code

But we don’t usually use this either, because it still loses the nature of type, which is that when an unbounded generic wildcard is used as a parameter, the caller does not qualify the actual parameter type to be passed. However, inside the method, the parameters of the generic class and the return value of the method are generic, cannot be used!

Here, STACKT. push doesn’t work because I don’t know. Is it an Integer or is it a String or is it some other type, so it’s going to get an error.

When we accept a generic stack StackT as a parameter, we want to express a constraint relationship, but not a rigid constraint like StackT

. Java is an object-oriented language, so there is a mechanism for inheritance. The constraint I want is that all generic stacks I can accept are derived from Number, i.e., not like? Unbounded wildcards lose class characteristics without being as rigid as StackT

constraints, which leads to the concept of upper bounded wildcards.

Upper bound wildcard

You can use the ** upper bound wildcard ** to narrow the type range of a type parameter.

Its syntax is:

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

        StackT<Integer> stackT = new StackT<>(3, Integer.class);
        stackT.push(8);
        StackT<String> stackT1 = new StackT<>(3, String.class);
        stackT1.push("Seven");
        StackT<Double> stackT2 = new StackT<>(3, Double.class);
        
        / / by
        test(stackT);
        test(stackT2);
        //error
        test(stackT1);

    }
    
    public static void test(StackT<? extends Number> stackT){ System.out.println(stackT.pop()); }}Copy the code

So that’s a class qualification, but the requirements have changed, and the constraint I want now is that all the generic stacks I can accept are the parent of Number, or the parent of Number, so if there’s an upper bound, there’s a lower bound

Lower bound wildcard

** The lower-bound wildcard ** restricts an unknown type to a specific type or superclass type of that type.

Note: Upper and lower wildcards cannot be used at the same time.

Its syntax is:

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

        StackT<Number> stackT1 = new StackT<>(3, Number.class);
        stackT1.push(8);
        StackT<Double> stackT2 = new StackT<>(3, Double.class);
        StackT<Object> stackT3 = new StackT<>(3, Object.class);
        / / by
        test(stackT1);
        test(stackT3);
        //error
        test(stackT2);

    }

    public static void test(StackT<? super Number> stackT){ System.out.println(stackT.pop()); }}Copy the code

This ensures that our test method only accepts methods above type Number. The advanced syntax of generics may be circumvented when writing business code, but it is useful to know the advanced syntax of generics if you are writing frameworks where you do not know the context in which the users of the framework are using them.

Wildcards and upward transitions

Earlier, we mentioned that generics cannot be upturned. However, we can transition up by using wildcards.

public class GenericsWildcardDemo {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<Number> numList = intList;  // Error

        List<? extends Integer> intList2 = new ArrayList<>();
        List<? extends Number> numList2 = intList2;  // OK}}Copy the code

Wildcard boundary problems, for a more in-depth solution to this problem, see the reprinted article Java Generic solution to upper and lower wildcards

Generic constraint

  • The type parameters of a generic type cannot be value types
Pair<int.char> p = new Pair<>(8.'a');  // Error compiling
Copy the code
  • Cannot create an instance of a type parameter
public static <E> void append(List<E> list) {
    E elem = new E();  // Error compiling
    list.add(elem);
}
Copy the code
  • Static members of type parameters cannot be declared
public class MobileDevice<T> {
    private static T os; // error

    // ...
}
Copy the code
  • Type parameters cannot use type conversion orinstanceof
public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // Error compiling
        // ...
    }
}
List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // Error compiling
Copy the code
  • Cannot create an array of type parameters
List<Integer>[] arrayOfLists = new List<Integer>[2];  // Error compiling
Copy the code
  • You cannot create, catch, or throw parameterized type objects
// Extends Throwable indirectly
class MathException<T> extends Exception { / *... * / }    // Error compiling

// Extends Throwable directly
class QueueFullException<T> extends Throwable { / *... * / // Error compiling
public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...}}Copy the code
  • Methods that only have the same generic class but different type parameters cannot be overloaded
public class Example {
    public void print(Set<String> strSet) {}public void print(Set<Integer> intSet) {}// Error compiling
}
Copy the code

practice

The generic name

Generic types have some common names:

  • E – Element
  • K – Key
  • N – Number
  • T – Type
  • V – Value
  • S,U,V etc. – 2nd, 3rd, 4th types

Recommendations for using generics

  • Clears type checking warnings
  • List takes precedence over array
  • Use of generics is preferred to improve code generality
  • Give preference to generic methods to limit the scope of generics
  • Use restricted wildcards to increase the flexibility of the API
  • Give priority to heterogeneous containers that are type safe

References:

Understand Java generics in depth

On Java 8

Extends T> and super T> bounds for Java generics

July’s live class – Java Advanced Syntax – generics