参考资料:
- Java核心技术卷一
秒懂Java类型(Type)系统
https://zhuanlan.zhihu.com/p/64584427
https://blog.csdn.net/ShuSheng0007/article/details/80720406
https://blog.csdn.net/ShuSheng0007/article/details/81809999
一、为什么要使用泛型程序设计
- 意味着编写的代码可以被很多不同类型的对象所重用。
- 使得程序具有更好的可读性和安全性。
二、定义简单的泛型类
package com.lagou.dachang;
import java.util.function.Supplier;
/**
* 简单泛型类
* @author yumingxing
* @version 1.0
* @date 2022/4/18 12:20
**/
public class Pair<T> {
private T first;
private T second;
public Pair() {
this.first = null;
this.second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
三、泛型方法
/**
* 定义泛型方法
*
* @param a 一个
* @return {@link T}
*/
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
//测试泛型方法
String middle = ArrayAlg.getMiddle("john", "Q", "Public");
//不兼容的类型: Number & Comparable<? extends Number & Comparable<?>> 无法转换为 String
//String middle1 = ArrayAlg.getMiddle(3.14,1722,0);
四、泛型变量的限定
有时,类或方法需要对类型变量加以约束。下面是一个典型的例子。我们要计算数组中的最小元素
//public static <T> Pair<T> minmax(T[] a) {
public static <T extends Comparable> Pair<T> minmax(T[] a) {
if (a == null || a.length == 0) {
return null;
}
T min = a[0];
T max = a[0];
for (int i = 1; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) {
min = a[i];
}
if (max.compareTo(a[i]) < 0) {
max = a[i];
}
}
return new Pair<>(min, max);
}
T extends Comparable
T extends Comparable & Serializable
五、泛型代码和虚拟机
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除(erased)类型变量,并替换为限定类型(无限定的变量用Object)。
public class Pair {
private Object first;
private Object second;
public Pair() {
this.first = null;
this.second = null;
}
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
public Object getFirst() {
return first;
}
public void setFirst(Object first) {
this.first = first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。例如,下面这个语句序列
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
类型擦除也会发生在泛型方法中
泛型擦除会对方法带来两个复杂问题,具体可以去了解
1)桥接方法
2)在虚拟机中,用参数类型和返回类型确定一个方法
等知识点
public static <T extends Comparable> T min(T[] a)
擦除后
public static Comparable min(Comparable[] a)
六、约束与局限性
- 不能用基本类型实例化类型参数 如:Pair
- 运行时类型查询只适用于原始类型 如:if (a instanceof Pair
) - 不能创建参数化类型数组 如:Pair
[] table = new Pair [10]; - 不能实例化类型变量
- 不能构造泛型数组
- 不能在静态 域或方法 中引用类型变量
- 不能抛出或捕获泛型类的实例
七、泛型类型的继承关系
八、通配符类型
Pair<? extend Employee> 表示任何泛型Pair类型,它的类型参数是Employee的子类,如Pair
不能将Pair
解决方法
?super Manager 这个通配符限制为Manager的所有超类型。
九、无限定通配符
Pair<?>
扩展
Type系统总览
Java Type体系,始于Type接口,其是Java编程语言中所有类型的父接口,是对Java编程语言类型的一个抽象,源码如下所示:
我们需要一个类和一个注解来作为讲解代码的基础。
package com.lagou.dachang.miaodong;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 泛型类 参数为 T 和 V
* @author yumingxing
* @version 1.0
* @date 2022/4/19 19:40
**/
public class TypeTest <T,V extends @Custom Number & Serializable> {
private Number number;
public T t;
public V v;
public List<T> list = new ArrayList<>();
public Map<String,T> map = new HashMap<>();
public T[] tArray;
public List<T> ltArray;
public TypeTest testClass;
public TypeTest<T,Integer> testClass2;
public Map<? super String,? extends Number> mapWithWildcard;
/**
* 泛型构造方法,泛型参数为X
*/
public <X extends Number> TypeTest(X x,T t){
number = x;
this.t = t;
}
/**
* 泛型构造方法,泛型参数为Y
*/
public <Y extends T> void method(Y y) {
t = y;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ANNOTATION_TYPE, CONSTRUCTOR, FIELD,
METHOD, PACKAGE, PARAMETER, TYPE, TYPE_PARAMETER, TYPE_USE})
public @interface Custom {
}
TypeVariable
类型变量,例如List
此接口源码如下:
interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
//返回此类型参数的上界列表,如果没有上界则放回Object. 例如 V extends @Custom Number & Serializable 这个类型参数,有两个上界,Number 和 Serializable
Type[] getBounds();
//类型参数声明时的载体,例如 `class TypeTest<T, V extends @Custom Number & Serializable>` ,那么V 的载体就是TypeTest
D getGenericDeclaration();
String getName();
//Java 1.8加入 AnnotatedType: 如果这个这个泛型参数类型的上界用注解标记了,我们可以通过它拿到相应的注解
AnnotatedType[] getAnnotatedBounds();
}
从typeVariable的定义看到其也有一个泛型参数,要求需要是GenericDeclaration 的子类,
//所有可以申明泛型参数的entities都必须实现这个接口
public interface GenericDeclaration extends AnnotatedElement {
public TypeVariable<?>[] getTypeParameters();
}
我们从源码中看到,只有三个类实现了这个接口,分别是:
- java.lang.reflect.Method
- java.lang.reflect.Constructor
- java.lang.Class
所以我们只能在类型(例如Class,Interface)、方法和构造函数这三个地方声明泛型参数
ParameterizedType
参数化类型,即带参数的类型,也可以说带<>的类型。例如List
其源码如下:
interface ParameterizedType extends Type {
//获取参数类型<>里面的那些值,例如Map<K,V> 那么就得到 [K,V]的一个数组
Type[] getActualTypeArguments();
//获取参数类型<>前面的值,例如例如Map<K,V> 那么就得到 Map
Type getRawType();
//获取其父类的类型,例如Map 有一个内部类Entry, 那么在Map.Entry<K,V> 上调用这个方法就可以获得 Map
Type getOwnerType();
}
GenericArrayType
泛型数组类型,用来作为数组的泛型声明类型。例如List
源码如下:
public interface GenericArrayType extends Type {
//获取泛型类型数组的声明类型,即获取数组方括号 [] 前面的部分
Type getGenericComponentType();
}
GenericArrayType 接口只有一个方法getGenericComponentType(),其可以用来获取数组方括号 [] 前面的部分,例如T[],在其上调用getGenericComponentType 就可以获得T. 值得注意的是多维数组得到的是最后一个[] 前面的部分,例如T[][], 得到的是T[].
WildcardType
通配符类型,即带有?的泛型参数, 例如 List<?>中的?,List<? extends Number>里的? extends Number 和List<? super Integer>的? super Integer 。
public interface WildcardType extends Type {
// 获取上界
Type[] getUpperBounds();
//获取下界
Type[] getLowerBounds();
}
Class
其是Type的一个实现类,是反射的基础,每一个类在虚拟机中都对应一个Calss 对象,我们可以用在运行时从这个Class对象中获取到类型所有信息。