Java Development - Generics

1. What

1.1 什么是泛型 (What is Generics)

Java 泛型是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的数据类型。
Java Generics is a new feature introduced in JDK 5. Generics provide a compile-time type safety detection mechanism that allows programmers to detect illegal data types at compile time.
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数,然后在使用时再指定此参数具体的值,这样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
The essence of generics is a parameterized type. The data type to be manipulated is specified as a parameter, and then the specific value of this parameter is specified when used so that the type can be determined when used. This parameter type can be used in classes, interfaces, and methods, which are called generic classes, generic interfaces, and generic methods, respectively.

1.2 泛型的作用 (Need for Generics)

泛型参数可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 List persons = new ArrayList() 这行代码就指明了该ArrayList 对象只能传入 Person 对象,如果传入其他类型的对象就会报错。
Generic parameters can improve code readability and stability. The compiler can detect generic parameters and specify the incoming object type through generic parameters. For example, List persons = new ArrayList() This line of code indicates that the ArrayList object can only be passed in Person objects, and an error will be reported if other types of objects are passed in.
可以用于构建泛型集合。原生 List 返回类型是 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换。
Generics can be used to build generic collections. The return type of the native List is Object, which requires manual conversion of the type before it can be used. After using generics, the compiler automatically converts it.

2. Why

2.1 为什么要使用泛型 (Why use generics)

(1)保证了类型的安全性:泛型约束了变量的类型,保证了类型的安全性。例如List和ArrayList。List集合只能加入int类型的变量,ArrayList可以Add任何常用类型,编译的时候不会提示错误。
Guaranteed type safety: Generics constrain the type of variables and ensure type safety. For example List and ArrayList. The List collection can only add variables of type Integer, and ArrayList can add any common type, and no error will be prompted when compiling.
(2)提高程序的性能:泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。举例说明:
Improve the performance of the program: the generic variable has a fixed type, and when it is used, it already knows whether it is a value type or a reference type, avoiding unnecessary boxing and unboxing operations. for example:

使用泛型之前 (Before using generics):

List list = new ArrayList();
list.add("Without generics");
String str = (String)list.get(0);

使用泛型之后 (After using generics):

List<String> list2 = new ArrayList<String>();
list2.add("Use generics");
String str2 = list.get(0);  //the type is already specified as String, so there will be no boxing and unboxing operations

(3)提高方法、算法的重用性。上面的例子基本能说明这个优势。
Improve the reusability of methods and algorithms. The above example basically illustrates this advantage.

2.2 Java中的类型擦除机制 (Type erasure mechanism in Java)

在编程语言中,类型擦除是加载时过程。在程序运行时执行之前,通过该过程从程序中删除显式类型注释。不需要程序伴随类型的操作语义称为类型擦除语义,与类型传递语义形成对比。赋予类型擦除语义的可能性是一种抽象原则,确保程序的运行时执行不依赖于类型信息。
In programming languages, type erasure is a load-time process. This process removes explicit type annotations from a program before it is executed at runtime. The semantics of operations that do not require a program’s accompanying type are called type erasure semantics, in contrast to type passing semantics. The possibility of giving type erasure semantics is an abstraction principle that ensures that the runtime execution of a program does not depend on type information.

泛型被引入 Java 语言以在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器将类型擦除应用于:
Generics were introduced into the Java language to provide stricter type checking at compile time and to support generic programming. To implement generics, the Java compiler applies type erasure to:

  • 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或Object 。因此,生成的字节码只包含普通的类、接口和方法。
    Replaces all type parameters in the generic type with their bounds or Object if the type parameter is unbounded. Therefore, the generated bytecode contains only ordinary classes, interfaces and methods.
  • 必要时插入类型转换以保持类型安全。
    Type conversions are inserted when necessary to maintain type safety.
  • 生成桥方法以保留扩展泛型类型中的多态性。
    Generate bridge methods to preserve polymorphism in extended generic types.

泛型值存在于java的编译期,具体来说就是在编译成字节码时首先进行类型检查,接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法上的泛型参数,如果类型变量有限定则原始类型就用第一个边界的类型来替换,譬如 class Prd<T extends Comparable & Serializable> {} 的原始类型就是Comparable),如果类型擦除和多态性发生冲突时就在子类中生成桥方法解决,如果调用泛型方法的返回类型被擦除则在调用该方法时插入强制类型转换,反编译之后也不会有泛型。
Generic values exist at the compile time of Java, specifically when compiling into bytecode, type checking is performed first, followed by type erasure (that is, all type parameters are replaced with their qualified types, including classes, variables, and methods) If the type variable is bounded, the original type will be replaced with the type of the first boundary, for example, the original type of class Prd<T extends Comparable & Serializable> {} is Comparable), if type erasure and polymorphism conflicts, a bridge method is generated in the subclass to solve it. If the return type of the generic method is erased, a cast will be inserted when the method is called, and there will be no generics after decompilation.

在Java中,泛型本质上是编译器的行为,为了保证引入泛型机制但不创建新的类型,减少虚拟机的运行开销,所以通过擦除将泛型类转化为一般类。
Generics are essentially the behavior of the compiler in Java. In order to ensure that the generic mechanism is introduced without new type creations and VM performance penalty, the generic class is converted into a general class by type erasure.

2.3 桥方法 (Bridge Method)

2.3.1 什么是bridge method?

When compiling a class or interface that extends a parameterized class or implements a parameterized interface, the compiler may need to create a synthetic method, which is called a bridge method, as part of the type erasure process. You normally don’t need to worry about bridge methods, but you might be puzzled if one appears in a stack trace.
如果一个类继承了一个泛型类或者实现了一个泛型接口, 那么编译器在编译这个类的时候就会生成一个叫做桥接方法的混合方法(混合方法简单的说就是由编译器生成的方法, 方法上有synthetic修饰符), 这个方法用于泛型的类型安全处理, 用户一般不需要关心桥接方法。

2.3.2 更直观的了解bridge method

// 定义一个泛型接口
public interface Parent<T> {
    T bridgeMethod(T param);
}
// 定义一个类实现泛型接口
public class Child implements Parent<String> {
    public String bridgeMethod(String param) {
        return param;
    }
}
// 测试方法
public class BridgeMethodTest {
    public static void main(String[] args) throws Exception {
       // 使用java的多态
        Parent parent = new Child();
        System.out.println(parent.bridgeMethod("abc123"));// 调用的是实际的方法
        Class<? extends Parent> clz = parent.getClass();
        Method method = clz.getMethod("bridgeMethod", Object.class); // 获取桥接方法
        System.out.println(method.isBridge()); // true
        System.out.println(method.invoke(parent, "hello")); // 调用的是桥接方法
        System.out.println(parent.bridgeMethod(new Object()));// 调用的是桥接方法, 会报ClassCastException: java.lang.Object cannot be cast to java.lang.String`错误`
    }
}

使用jclasslib工具看一下编译器为我们生成了什么了
Use the jclasslib tool to see what the compiler has generated for us

这个是Parent.class的字节码, 可以看到泛型T被换成了Object, 所以方法的签名是:
This is the bytecode of Parent.class, you can see that the generic type T has been replaced with Object, so the signature of the method is:

public abstract Object bridgeMethod(Object param)
在这里插入图片描述

这个是Child.class的字节码, 可以看到生成了一个bridgeMethod的方法, 这个方法的签名是:
This is the bytecode of Child.class. You can see that a bridgeMethod method is generated. The signature of this method is:

public String bridgeMethod(String param)
在这里插入图片描述

这个是Child.class的字节码, 可以看到还生成了一个bridgeMethod的桥接方法, 这个方法的签名是:
This is the bytecode of Child.class. You can see that a bridge method of bridgeMethod is also generated. The signature of this method is:

public synthetic bridge Object bridgeMethod(Object param)
在这里插入图片描述

2.3.3 编译器生成bridge method的意义

简单来说, 编译器生成bridge method的目的就是为了和jdk1.5之前的字节码兼容. 因为泛型是在jdk1.5之后才引入的. 在jdk1.5之前例如集合的操作都是没有泛型支持的, 所以生成的字节码中参数都是用Object接收的, 所以也可以往集合中放入任意类型的对象, 集合类型的校验也被拖到运行期。
In general, the purpose of the compiler to generate bridge methods is to be compatible with the bytecode before jdk1.5. Because the generic type was introduced after jdk1.5. Before jdk1.5, operations such as collections were not specified. Type support, so the parameters in the generated bytecode are all received by Object, so you can also put any type of object into the collection, and the verification of the collection type is also dragged to the runtime.

但是在jdk1.5之后引入了泛型, 因此集合的内容校验被提前到了编译期, 但是为了兼容jdk1.5之前的版本java使用了泛型擦除, 所以如果不生成桥接方法就和jdk1.5之前的字节码不兼容了。
However, the generic type was introduced after jdk1.5, so the content verification of the collection was advanced to the compilation time, but in order to be compatible with the version of java before jdk1.5, the generic erasure was used, so if the bridge method is not generated, it is the same as jdk1. Bytecodes prior to 5 are no longer compatible.

上面可以看到在Parent.class中, 由于泛型擦除, class文件中泛型都是由Object替代了. 所以如果子类中要是不生成bridge method那么子类就没有实现接口中的方法, 这个java语义就不对了(虽然已经生成class文件了, 不会有编译错误)
As we can see above, in Parent.class, due to the erasure of the generic type, the generic type in the class file is replaced by Object. So if the subclass does not generate a bridge method, then the subclass does not implement the method in the interface, this The java semantics are wrong (although the class file has been generated, there will be no compilation error)

2.3.4 bridge method总结

因为java要兼容之前的版本, 因此在一些步骤上要做一些trick操作, 但是这些trick操作又不能对用户的使用产生困扰, 也就是说这些操作对用户应该是透明的. 不得不说java真的很强大, 我们平时用的只不过是java的冰山一角, 如果要更深入的了解java语言本身还有很多有意思的功能或者知识点需要了解。
Since java is compatible with the previous version, some trick operations are required in some steps, but these trick operations should not cause trouble to the user, that is to say, these operations should be transparent to the user. I have to say that java is really It is very powerful. What we usually use is just the tip of the java iceberg. If we want to have a deeper understanding of the java language itself, there are many interesting functions or knowledge points that need to be understood.

2.4 堆污染 (Heap pollution)

定义:当一个不带泛型的对象赋值给一个带泛型的变量时, 就有可能发生堆污染。
Definition: Heap pollution may occur when an object without generics is assigned to a variable with generics.

原因:在定义泛型对象(泛型类的对象)时,是否显示指定泛型类型不是强制的(可以在后面的运行中确定),这就造成了ClassCastException隐患,这个异常隐患不会在编译时抛出,而是在运行时抛出。
Root cause: When defining a generic object (object of a generic class), it is optional to display the specified generic type (it can be determined in a later run), which causes a hidden danger of ClassCastException, which will not be thrown during compiling, but at runtime.

由于泛型擦除,泛型是不能显式的完成转型、instanceof 和 new 表达式等操作的。比如下面的例子:
Due to type erasure, generics cannot explicitly perform operations such as casts, instanceof, and new expressions. For example the following example:

// statement 1
List list = new ArrayList<Integer>();
// statement 2 -- unchecked warning 
List<String> strList = list;

new ArrayList() 和 List strList 会被擦除为 ArrayList 和 List,当 list 指向 strList 时,编译器是无法确定 list 和 strList 的参数类型的,因为在运行时 list 的类型信息 “Integer” 和 strList 的类型信息 “String” 都已经被擦除了,这时就会产生 unchecked warning ,自然就会发生堆污染了。
new ArrayList() and List strList will be erased to ArrayList and List, when list points to strList, the compiler cannot determine the parameter types of list and strList, because the type information “Integer” and “String” has been erased, an unchecked warning will be generated, and heap pollution will naturally occur.

在正常情况下,当所有代码同时编译时,编译器会发出未经检查的警告,以引起对潜在堆污染的注意。如果单独编译代码的各个部分,则很难检测到堆污染的潜在风险。如果确保代码在没有警告的情况下编译,则不会发生堆污染。
In normal cases, when all code is compiled at the same time, the compiler issues unchecked warnings to draw attention to potential heap pollution. The potential risk of heap pollution is difficult to detect if parts of the code are compiled separately. If you make sure your code compiles without warnings, heap pollution won’t happen.

2.5 泛型的缺点 (Cons of Generics)

  • 不能是基本类型 (Cannot be a primitive type)
    例如int,因为实际类型是Object,Object类型无法持有基本类型。
    Eg: int, because the actual type is Object, the Object type cannot hold primitive types.
  • 无法取得带泛型的Class (Unable to get Class with generics)
    对ArrayList和ArrayList类型获取Class时,获取到的是同一个Class,也就是ArrayList类的Class。也就是说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是ArrayList。
    When obtaining Class for ArrayList and ArrayList types, the same Class is obtained, that is, the Class of the ArrayList class. That is, for all generic instances, regardless of the type of T, getClass() returns the same Class instance because they are all ArrayLists after compilation.
  • 无法判断带泛型的类型 (Unable to determine type with generics)
ArrayList<String> array = new ArrayList<>("hello");
// Compilation error:
if (array instanceof ArrayList<Integer>) {
    ...
}

原因和前面一样,并不存在ArrayList.class,而是只有唯一的ArrayList.class。
Same reason as before, there is no ArrayList.class, but only a unique ArrayList.class.

  • 不能实例化T类型 (Cannot instantiate type T)
    对于类型参数T,不能“new T()”。因为并不是每个类型都有一个空的构造器。Java是静态类型语言,从好的或坏的方面来说,我们不能简单的调用某一类型的空构造器,因为这个类型自身并不静态地知道有这样的构造器存在。
    Cannot “new T()” for type parameter T. Because not every type has an empty constructor. Java is a statically typed language, for better or worse, we can’t simply call an empty constructor of a type because the type itself doesn’t know statically that such a constructor exists.

  • (扩展)如何实例化一个T (How to instantiate a T)?

public static abstract class Parent<T> {
    protected abstract T getT();
}

public static class Child extends Parent<Integer> {
    @Override
    protected Integer getT(){
        return Integer.valueOf(0);
    }
}

2.6 泛型的好处 (Pros of generics)

  • 程序更加健壮 - 把运行时期的问题提前到了编译期间,只要编译时期没有警告,那么运行时就不会抛出ClassCastException的异常
    Program robustness - The runtime problem is brought forward to the compilation time. As long as there is no warning at compile time, the ClassCastException exception will not be thrown at runtime.
  • 避免了强制类型转换 - 消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。所有的强制转换都是自动和隐式的
    Avoid casts - Eliminates many casts in the source code. This makes the code more readable and reduces the chance of errors. All casts are automatic and implicit.
  • 提高代码的重用性 - 通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读
    Improve code reusability - By using generics, programmers can implement generic algorithms that can handle collections of different types, are customizable, and are type-safe and easy to read

3. How

3.1 泛型类 (Generic class)

泛型类:把泛型定义在类上 (Generic class: define generics in a class)

public class classname<T> {  
   private T data; 
}

注意事项:泛型类型必须是引用类型(非基本数据类型)
Note: Generic types must be of reference types (non-primitive data types)
定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔:
Define a generic class, add a pair of angle brackets after the class name, and fill in the type parameters in the angle brackets. There can be multiple parameters, and multiple parameters are separated by commas:
public class GenericClass<ab,a,c> {}
泛型标识可以为任意字符串,当然,这个参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表示:
The generic identifier can be any string. This parameter type is also standardized and cannot be as random as the above. Generally, we use a single uppercase letter to represent the type parameter:

  • T:任意类型 type
  • E:集合中元素的类型 element
  • K:key-value形式 key
  • V:key-value形式 value

泛型类继承 (Generic class inheritance)
子类是泛型类:当子类也是泛型类,并且也想指定父类的泛型类型时,两个类的泛型类型必须一致。
The subclass is a generic class: When the subclass is also a generic class, and you also want to specify the generic type of the superclass, the generic types of the two classes must be the same.

3.2 泛型接口 (Generic interface)

泛型接口的定义与泛型类完全一致
The definition of a generic interface is exactly the same as that of a generic class.

public interface Interfacename <A,B...> {
}

泛型接口实现类 (Generic interface implementation class)

实现类是泛型类 (The implementation class is a generic class)

  • 同泛型类继承,如果实现类是泛型类,并且也想指定父接口的泛型类型,那么两个泛型类型必须一致。
    Same as generic class inheritance, if the implementation class is a generic class and also wants to specify the generic type of the parent interface, then the two generic types must be the same.
public class TestList3<E> implements List<E> {
    
    @Override
    public int size() {
        return 0;
    }
    ...

如果泛型类型不一致则会编译错误
Compile error if generic types are inconsistent

// Cannot resolve symbol 'E'
public class TestList3<T> implements List<E> {
}

实现类不是泛型类(The implementing class is not a generic class)

  • 同泛型类继承,如果实现类不是泛型类,又想指定父接口的泛型类形时,那么需要明确的指出具体的类型。
    Same as generic class inheritance, if the implementation class is not a generic class and you want to specify the generic type of the parent interface, you need to specify the specific type.
public class TestList4 implements List<Integer> {
    @Override
    public int size() {
        return 0;
    }
    ...
}

class Test4 {
    public static void main(String[] args) {
        TestList4 list = new TestList4();
        list.add(1234);
    }
}

3.3 泛型方法 (Generic method)

泛型方法概述:把泛型定义在方法上
Overview of generic methods: defining generics on methods

public <T extends Comparable> resultType methodName(T element) {
  
}

注意要点:
方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
泛型方法是不依托于泛型类或泛型接口的,即普通类中也可以定义泛型方法。
Points to note:
The formal parameters defined in the method declaration can only be used in the method, while the type parameters defined in the interface and class declaration can be used in the whole interface and class. When calling the method, the compiler will determine the actual type represented by the type parameter T according to the actual object passed in.
Generic methods do not rely on generic classes or generic interfaces, that is, generic methods can also be defined in ordinary classes.

静态泛型方法 (Static generic method)

在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
Use generics in static methods in classes: static methods cannot access generics defined on the class; if the reference data type of the static method operation is uncertain, the generics must be defined on the method.

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法。
If the static method is to use generics, the static method must also be defined as a generic method.

public static <T> void show(T t){

}

3.4 泛型通配符 (Generic wildcards)

无界通配符 (Unbounded wildcard)
泛型List<?> 在没有赋值前,表示可以接受任何类型的集合赋值,赋值之后不能往里面随便添加元素,但可以remove和clear。List<?>一般作为参数来接收外部集合,或者返回一个具体元素类型的集合。常在以下情况下使用:
Before the generic List<?> is assigned, it means that it can accept any type of list assignment. After assignment, you cannot add elements to it, but you can remove and clear elements from the list. List<?> generally accepts an external collection as a parameter, or returns a collection of a specific element type. Often used in the following situations:

  • 编写可使用 Object 类中提供的功能使用的方法时
    When writing methods that can be used using functionality provided in the Object class
  • 当代码使用不依赖于类型参数的泛型类中的方法时
    When code uses methods in generic classes that do not depend on type parameters
public static void printList(List<?> list) {
       for (Object o : list) {
       		/* method body */
        }
}

extends
? extends T 描述了通配符上界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的子类。
? extends T describes the wildcard upper bound, that is, the specific generic parameter needs to meet the conditions: the generic parameter must be of type T or its subclass.
List<? extends Number> foo3 的通配符声明意味着下面任何一个都是合法的赋值:
The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number

1.读取 - 鉴于以上的分配,你能保证可以从 List foo3 get到哪种对象呢?
可以 get 到 Number 类型的对象,因为任何可以分配给 foo3 的 List 都包含一个 Number 或 Number 的子类。
不能读取 Integer ,因为 foo3可能指向 List < Double > 。
不能读取 Double,因为 foo3可能指向 List < Integer > 。

1.Reading - Given the above possible assignments, what type of object are you guaranteed to read from List foo3:
Number can be read form the list because any of the lists that could be assigned to foo3 contain a Number or a subclass of Number.
Integer cannot be read direclty from the list because foo3 could be pointing at a List.
Double cannot be read direclty from the listbecause foo3 could be pointing at a List.

不能将任何对象添加到 List < ?extends T > ,因为您不能保证它实际指向的 List 的类型,所以不能保证 List 中允许该对象。唯一的“保证”是您只能从中读取,并且您将得到 T 或 T 的子类。
我的理解:List < ?extends T > 的值只能在初始时定义,并且只支持读取T和T子类类型的元素。
You can’t add any object to List<? extends T> because you can’t guarantee what kind of List it is really pointing to, so you can’t guarantee that the object is allowed in that List. The only “guarantee” is that you can only read from it and you’ll get a T or subclass of T.

super
? super T 描述了通配符下界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的父类。
? super T describes the wildcard lower bound, that is, the specific generic parameter needs to meet the conditions: the generic parameter must be of type T or its superclass.
List<? super Integer> foo3 的通配符声明意味着下面任何一个都是合法的赋值:
The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer

new ArrayList<>()后面的<>只能是Integer本身或者它的父类比如Object,不填默认就是其本身
而foo3能添加的元素范围只能是super T中T本身或者T的子类。
The <> after new ArrayList<>() can only be Integer itself or its parent class such as Object, if not filled in, the default is itself
The range of elements that foo3 can add can only be T itself or a subclass of T in super T.
我的理解:
我们不能保证可以从 array 对象中读取到 Integer 类型的数据, 因为 array 可能是 List 类型的
我们不能保证可以从 array 对象中读取到 Number 类型的数据, 因为 array 可能是 List 类型的
唯一能够保证的是, 我们可以从 array 中获取到一个 Object 对象的实例
在此示例中,我们只能添加Integer及其子类型的元素。因为无法保证添加Integer的父类型是右边实际指向类型的子类。eg:不能添加Number,因为 foo3 有可能指向 List < Integer > 。
Integer may not be retrieved from the list because foo3 could be pointing at a List or List.
Number may not be retrieved from the listbecause foo3 could be pointing at a List.
The only guarantee is that you will get an instance of an Object or subclass of Object (but you don’t know what subclass).

PECS 原则: Producer Extends, Consumer Super
Producer extends: 如果我们需要一个 List 提供类型为 T 的数据(即希望从 List 中读取 T 类型的数据), 那么我们需要使用 ? extends T, 例如 List<? extends Integer>. 但是我们不能向这个 List 添加数据。
Consumer Super: 如果我们需要一个 List 来消费 T 类型的数据(即希望将 T 类型的数据写入 List 中), 那么我们需要使用 ? super T, 例如 List<? super Integer>. 但是这个 List 不能保证从它读取的数据的类型。
如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List。

PECS
Remember PECS: “Producer Extends, Consumer Super”.

“Producer Extends” - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list.

“Consumer Super” - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list.

If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List.

3.5 泛型和数组 (Generics and array)

这样创建一个泛型数组会编译报错:
Creating a generic array in this way will compile an error:

Generic<String>[] generics = new Generic<String>[2];

首先我们需要知道数组是具有协变性的,例如:
First we need to know that arrays are covariant, for example:

Integer[] intArr = new Integer[2];
Object[] objArr = intArr;

以上的写法是允许的,这就导致了问题的发生
The above way of writing is allowed, which leads to the problem.

objArr[0] = "123";

以上代码编译可以通过,但是因为向一个Integer的数组中赋值了字符串类型,所以在运行时会报ArrayStoreException。
The above code can be compiled and passed, but because a string type is assigned to an array of Integers, an ArrayStoreException will be reported at runtime.

通过泛型的学习,我们知道泛型是不具备协变性的,ArrayList numbers = new ArrayList(); 这种写法无法通过编译。我们从一个泛型容器中获取具体元素的时候,并不需要强制转型,因为如果获取一个Integer的元素,但是实际上获得的却是一个字符串类型,这就会导致类似转型异常的发生,泛型也就失去了意义。
Through the study of generics, we know that generics are not covariant, and ArrayList numbers = new ArrayList(); this way of writing cannot pass compilation. When we get a specific element from a generic container, we don’t need to cast it, because if we get an Integer element, but we actually get a string type, this will cause a similar conversion exception to occur. The type also loses its meaning.
结合这两个特点,我们就可以分析出我们想要的答案,那就是Java不支持泛型数组。
Combining these two characteristics, we can analyze the answer we want, that is, Java does not support generic arrays.

3.6 自限定泛型 (Self-bounded generics)

class SelfBounded<T extends SelfBounded<T>>

SelfBounded类接受泛型参数T,而T由一个边界限定,这个边界就是拥有T作为其参数的SelfBounded。
The SelfBounded class accepts a generic parameter T, and T is bounded by a bound that is SelfBounded with T as its parameter.

作用 (Effects):

  • 可以保证类型参数必须与正在定义的类相同
    It is guaranteed that the type parameter must be the same as the class being defined
  • 自限定只能强制作用于继承关系
    Self-bounded can only be enforced for inheritance relationships

自限定泛型事实上将产生确切的导出类型作为其返回值。协变参数类型–方法参数类型会随着自类而变化,自限定类型还可以产生于子类类型相同的返回类型。
Self-bounded generics will in fact produce the exact exported type as their return value. Covariant parameter types - method parameter types change with the self-class, and self-bunded types can also be generated from the same return type of the subclass type.

在非泛型代码中,参数类型不能随子类型发生变化。但是,在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型作为参数。如果不使用自限定类型,普通的继承机制就会介入,而你能够重载,就像在非泛型的情况下一样。
In non-generic code, parameter types cannot vary with subtypes. However, when using self-bounded types, there is only one method in the derived class, and this method accepts the derived type as an argument instead of the base type. If you don’t use self-bounded types, the normal inheritance mechanism kicks in and you can overload, just like in the non-generic case.

3.7 初始化泛型数组实例 (Initializing a genenric array instance)

无论是通过new ArrayList[10] 的形式还是通过泛型通配符的形式初始化泛型数组实例都是存在警告的,也就是说仅仅语法合格,运行时包含潜在的风险,因此这些方式初始化泛型数组都不是最优雅的方式。
Whether initializing a generic array instance in the form of new ArrayList[10] or in the form of a generic wildcard has warnings, that is to say, only the syntax is qualified, and the runtime contains potential risks, so these methods of initializing generic arrays are all Not the most elegant way.

声明带泛型的数组引用,但是不能直接创建带泛型的数组对象,可以通过java.lang.reflect.Array的newInstance(Class, int )创建T[]数组,示例如下:
Declare an array reference with generics, but you cannot directly create an array object with generics. You can create a T[] array through newInstance(Class, int ) of java.lang.reflect.Array. The example is as follows:

public class ArrayWithTypeToken<T> {
    private T[] array;

    public ArrayWithTypeToken(Class<T> type, int size) {
        array = (T[]) Array.newInstance(type, size);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T get(int index) {
        return array[index];
    }

    public T[] create() {
        return array;
    }
}
//...

ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
Integer[] array = arrayToken.create();

4. Samples

泛型类 (generic class):

public class Generic<T>{ 
    //The type of the key member variable is T, and the type of T is specified externally
    private T key;

    public Generic(T key) { //The type of the parameter key of the generic constructor is also T, and the type of T is specified externally
        this.key = key;
    }

    public T getKey(){ //The return value type of the generic method getKey is T, and the type of T is specified externally
        return key;
    }
}

泛型接口 (generic interface):
最典型的例子就是熟知的框架Mybatis-Plus中Mapper以及IBaseService接口
The most typical example is the Mapper and IBaseService interfaces in the well-known framework Mybatis-Plus

public interface BaseMapper<T> {
    //插入一条记录  参数:实体  返回:int
    Integer insert(T entity);
 
    //根据 ID 删除  参数:主键ID  返回:int
    Integer deleteById(Serializable id);
    
     //根据 columnMap 条件,删除记录  参数:表字段 map 对象  返回:int
    Integer deleteByMap(@Param("cm") Map<String, Object> columnMap);
 
     //根据 entity 条件,删除记录  参数:实体对象封装操作类(可以为 null)  返回:int
    Integer delete(@Param("ew") Wrapper<T> wrapper);
 
     //删除(根据ID 批量删除)  参数:主键ID列表  返回:int
    Integer deleteBatchIds(List<? extends Serializable> idList);
 
     //根据 ID 修改  参数:实体对象  返回:int
    Integer updateById(T entity);
    ......
}

泛型方法 (generic method):

public class TestUtil {

    public <T> T getFirst(List<T> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    public static void main(String[] args) {
        TestUtil testUtil = new TestUtil();
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Integer num = testUtil.getFirst(list);
        // 结果为1
        System.out.println(num);

        List<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");
        list2.add("c");
        String letter = testUtil.getFirst(list2);
        // 结果为a
        System.out.println(letter);
    }
}

上例中,TestUtil并不是泛型类,但是getFirst()却是泛型方法,它使用了作为泛型标识,在参数中以及返回值进行传递。
In the above example, TestUtil is not a generic class, but getFirst() is a generic method. It uses as a generic identifier and passes it in the parameters and return value.

泛型通配符(拷贝数据):
Generic wildcard (copy data)

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) 
  {
      for (int i=0; i<src.size(); i++) 
        dest.set(i,src.get(i)); 
  } 
}

上面的例子是一个拷贝数据的代码, src 是 List<? extends T> 类型的, 因此它可以读取出 T 类型的数据(读取的数据类型是 T 或是 T 的子类, 但是我们不能确切的知道它是什么类型, 唯一能确定的是读取的类型 is instance of T), , dest 是 List<? super T> 类型的, 因此它可以写入 T 类型或其子类的数据。
The above example is a code that copies data, src is of type List<? extends T>, so it can read data of type T (the read data type is T or a subclass of T, but we can’t be sure Knowing what type it is, the only thing that can be sure is that the type read is instance of T), dest is of type List<? super T>, so it can write data of type T or its subclasses.

非泛型代码(未使用自限定类型)
non-generic code

class OrdinarySetter{
    void set(Base base){
        System.out.println("OrdinarySetter.set(base)");
    }
}
class DerivedSetter extends OrdinarySetter{
    void set(Derived derived){
        System.out.println("DerivedSetter.set(derived)");
    }
}
public class OrdinaryArguments {
    public static void main(String[] args) {
        Base base = new Base();
        Derived derived = new Derived();
        DerivedSetter ds = new DerivedSetter();
        ds.set(derived);
        ds.set(base);
    }
}
/*
DerivedSetter.set(derived)
OrdinarySetter.set(base)
*/

上面set(derived)和set(base)都是合法的,因此DerivedSetter.set()没有覆盖OrdinarySetter.set(),而是重载了这个方法。
The above set(derived) and set(base) are both legal, so DerivedSetter.set() does not override OrdinarySetter.set(), but overloads this method.

自限定泛型 (Self-bounded generics)

interface SelfBoundingSetter<T extends SelfBoundingSetter<T>>{
    void set(T arg);
}
interface Setter extends SelfBoundingSetter<Setter>{}
public class SelfBoundingAndCovariantArguments {
    void testA(Setter s1, Setter s2, SelfBoundingSetter sbs){
        s1.set(s2);
        /** 
         * 下面这个调用会报编译错误
         * 编译期不能识别将基类当作参数传递给set()的尝试,因为没有任何方法具有这样的签名
         * 实际上,这个参数已经被覆盖
         * */
//        s1.set(sbs);
    }
}

导出类中方法只接受导出类型,而不是基类型作为参数。
Methods in an exported class only accept the derived type as a parameter, not the base type.

References

https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java
https://segmentfault.com/a/1190000008423240
https://www.jianshu.com/p/250030ea9b28

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
毕业设计,基于SpringBoot+Vue+MySQL开发的公寓报修管理系统,源码+数据库+毕业论文+视频演示 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本公寓报修管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此公寓报修管理系统利用当下成熟完善的Spring Boot框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。公寓报修管理系统有管理员,住户,维修人员。管理员可以管理住户信息和维修人员信息,可以审核维修人员的请假信息,住户可以申请维修,可以对维修结果评价,维修人员负责住户提交的维修信息,也可以请假。公寓报修管理系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。 关键词:公寓报修管理系统;Spring Boot框架;MySQL;自动化;VUE
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是王小贱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值