public class Pair<T> {
T first;
T second;
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
}
Pair<Integer> minmax = new Pair<Integer>(1,100);
Pair<String> kv = new Pair<String>("name", "老王");
参数类型也可以多种,类 Pair 可以改成如下所示:
public class Pair<U, V> {
U first;
V second;
public Pair(U first, V second) {
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
}
这样一来,构造方法 Pair(U first, V second) 就可以接收不同类型的参数了,既可以是 Integer,也可以是 String:
public static < T extends Comparable<T> > T max( T[] arr ){
T max = arr[0];
for ( int i = 1; i < arr.length; i++ ){
if ( arr[i].compareTo( max ) > 0 ){
max = arr[i];
}
}
return(max);
}
max 方法计算一个泛型数组中的最大值,计算最大值需要进行元素之间的比较,要求元素实现 Comparable 接口,所以给类型参数设置了一个上边界 Comparable, T 必须实现 Comparable 接口。
上界为其他类型
上界类型,除了类、接口,也可以是其他类型,举个例子:
public class DynamicArray<E> {
private static final int DEFAULT_CAPACITY = 10;
private int size;
private Object[] elementData;
public DynamicArray() {
this.elementData = new Object[DEFAULT_CAPACITY];
}
private void ensureCapacity(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity >= minCapacity) {
return;
}
int newCapacity = oldCapacity * 2;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
public void add(E e) {
ensureCapacity(size + 1);
elementData[size++] = e;
}
public E get(int index) {
return (E) elementData[index];
}
public int size() {
return size;
}
public E set(int index, E element) {
E oldValue = get(index);
elementData[index] = element;
return oldValue;
}
}
public class DynamicArray<E> {
public < T extends E > void addAll( DynamicArray<T> c ){
for ( int i = 0; i < c.size; i++ ){
// 业务逻辑...
}
}
}
E 是 DynamicArray 的类型参数,T 是 addAll 的类型参数,T 的上界限定为 E。
解析通配符
有限定通配符
<? extends E> 表示有限定通配符,匹配 E 或 E 的某个子类型,具体什么子类型是未知的,如:
public class DynamicArray<E> {
public < T extends E > void addAll( DynamicArray<T> c ){
for ( int i = 0; i < c.size; i++ ){
// 业务逻辑...
}
}
}
改成有限定通配符方式:
public class DynamicArray<E> {
public void addAll( DynamicArray<? extends E> c ){
for ( int i = 0; i < c.size; i++ ){
// 业务逻辑...
}
}
}
“泛型”的字面意思就是广泛的类型。类、接口和方法代码可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型,这样,不仅可以复用代码,降低耦合,而且可以提高代码的可读性和安全性。在实际开发中,经常会使用到泛型,但语法非常令人费解,而且容易混淆对于其中的一些细节的地方,所以翻书去回顾下。
泛型类
首先看一段简单代码:
这就是一个简单的泛型类,T 表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入。如下代码所示,对于构造方法
Pair(T first, T second)
既可以传 Integer 类型的参数,也可以传 String 类型的参数:参数类型也可以多种,类 Pair 可以改成如下所示:
这样一来,构造方法
Pair(U first, V second)
就可以接收不同类型的参数了,既可以是 Integer,也可以是 String:那假如我们不用泛型类,参数类型直接用 Oeject ,其实也可以满足基本的需求,将类 Pair 修改成如下的方式:
但是这样会有什么坏处呢,假如我们在写代码的时候,不小心把类型弄错,但是编译器在编译的时候是不会有任何问题,在运行时候,就会抛类型转换异常:
但是如果使用了泛型的话,就可以避免这类错误,在编译期间,编译器就会报错:
总结一下就是:Java 泛型是通过擦除实现的,对于泛型类,Java 编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通 Pair 类代码及其使用代码一样,将类型参数 T 擦除,替换为 Object,插入必要的强制类型转换,泛型的两个好处就是:更好的安全性,更好的可读性。
泛型接口
接口也是可以泛型的,比如 Comparator 接口都是泛型的:
那么在实现这两个类的时候,实现方法里面就得指定具体的参数类型:
泛型方法
除了泛型类,方法也可以是泛型的,而且,一个方法是不是泛型的,与它所在的类是不是泛型没有什么关系。我们看个例子就知道了:
参数类型的限定
上界为指定的类
如果没有参数类型的限定,那么类型参数 T 在擦除的时候,只能把它当作 Object,但 Java 支持限定这个参数的一个上界,也就是说,参数必须为给定的上界类型或其子类型,这个限定是通过 extends 关键字来表示的。例如,上面的 Pair 类,可以定义一个子类NumberPair,限定两个类型参数必须为 Number:
限定类型后,如果类型使用错误,编译器会提示。指定边界后,类型擦除时就不会转换为 Object 了,而是会转换为它的边界类型 Number,这也是容易理解的。
上界为指定的接口
上面看到上界是指定的类,当然了,上界也可以是指定的接口,那么类型 T,就必须实现该上界接口,如下所示:
max 方法计算一个泛型数组中的最大值,计算最大值需要进行元素之间的比较,要求元素实现 Comparable 接口,所以给类型参数设置了一个上边界 Comparable, T 必须实现 Comparable 接口。
上界为其他类型
上界类型,除了类、接口,也可以是其他类型,举个例子:
E 是 DynamicArray 的类型参数,T 是 addAll 的类型参数,T 的上界限定为 E。
解析通配符
有限定通配符
<? extends E>
表示有限定通配符,匹配 E 或 E 的某个子类型,具体什么子类型是未知的,如:改成有限定通配符方式:
那
<T extends E>
和<? extends E>
到底有什么关系?<T extends E>
用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面。<? extends E>
用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是E或E的某个子类型。无限定通配符
形如
DynamicArray<? >
,称为无限定通配符,举个例子,在 DynamicArray 中查找指定的元素:无限定通配符,也可以改成类型参数,如下,两者写法是等价的:
但是通配符形式是比较简洁,但是有一个重要的限制:只能读,不能写,如下所示:
因为 ?问号就是表示类型安全无知,
? extends Number
表示是Number的某个子类型,但不知道具体子类型,如果允许写入,Java 就无法确保类型安全性,所以干脆禁止。现在我们再来看泛型方法到底应该用通配符的形式还是加类型参数。两者到底有什么关系?我们总结如下:超类型通配符
<? super E>
,称为超类型通配符,表示E的某个父类型,它与<? extends E>
正好相反,有了它,可以灵活的进行写入。我们给 DynamicArray 添加一个方法,将当前容器中的元素添加到传入的目标容器中:Integer 是 Number 的子类,将 Integer 对象拷贝入 Number 容器,这种用法应该是合情合理的,但 Java会 提示编译错误,理由我们之前也说过了,期望的参数类型是
DynamicArray<Number>
,DynamicArray<Integer>
并不适用。这里使用超类型通配符就可以解决这个问题:这样,编译器就不会报错了,所以总结一下:
<? super E>
用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象,它不能被类型参数形式替代。<? >
和<? extends E>
用于灵活读取,使得方法可以读取E或E的任意子类型的容器对象,它们可以用类型参数的形式替代,但通配符形式更为简洁。局限性
基本类型不能用于实例化类型参数
Pair<int> minmax = new Pair<int>(1,100);
是不支持的,解决方法是使用基本类型对应的包装类。Pair<Integer> minmax = new Pair<Integer>(1,100);
运行时类型信息不适用于泛型
Pair<Integer>.class
不支持if(p1 instanceof Pair<Integer>)
不支持if(p1 instanceof Pair<? >)
支持类型擦除可能会引发一些冲突
不能通过类型参数创建对象
T elm = new T();
不支持,但是可以借助反射机制实现:泛型类类型参数不能用于静态变量和方法,如下则是非法的:
Java 不支持创建泛型数组
Java中还支持多个上界,多个上界之间以&分隔,类似这样:
T extends Base & Comparable & Serializable