文章目录
- 参考
- 泛型
- 类型擦除
- 泛型通配符
-
- [上界通配符 <? extends T>](#上界通配符 <? extends T>)
- [下界通配符 <? super T>](#下界通配符 <? super T>)
- [无限定通配符 <?>](#无限定通配符 <?>)
参考
泛型
泛型就是把类型作为参数。以public static <E> E get(E e)
为例,e
是方法参数,而E
是类型参数,在调用时才能确定具体类型。
泛型的优点是:
- 泛型提供类型安全检测机制,无需代码中显示类型转换,提升安全性。
- 泛型显式指定对象类型,提升可读性。
泛型有三种使用方法:泛型类/接口,泛型参数。
泛型类/接口
JDK的ArrayList
就是泛型类。List<E>
就是泛型接口。
E
是类型参数,JDK常用的有E(element), K(key), V(value), T(type)
。
由于实例化(new ArrayList<String>();
)后才能确定类型参数E = String
,因此只有成员变量和成员方法可以使用类的类型参数E
。静态字段不可以使用类型参数。静态方法可以使用自己的类型参数。
java
public class ArrayList<E> implements List<E> {
transient Object[] elementData; // 存储元素的`Object`数组
private int size; // `ArrayList`存储元素的容量
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
// elementData是存放`e`的`Object`数组。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
}
泛型接口的实现类可以是ArrayList<E>
这种不定类型参数。也可以是MyList
这种指定接口的类型参数,实例方法的类型参数为接口指定的类型参数。
java
public interface List<E> extends Collection<E> {
List<E> subList(int fromIndex, int toIndex);
}
public class MyList implements List<String> {
@Override
public List<String> subList(int fromIndex, int toIndex) {}
}
泛型方法
java
public static <K, V> V getValue(K key, V value) {}
方法的泛型参数<K, V>
在修饰符和返回类型之间。调用时确定实际对象类型类.<String, Integer>getValue("3", 1)
。
类型擦除
java脚本的编译分为两个阶段,一阶段是源码(.java文件)编译为字节码(.class文件),二阶段是字节码编译为JVM使用的二进制机器码。为了兼容早起版本,Java泛型在一阶段被擦除为原始类型,字节码文件里没有泛型参数。
java
List<String> list = new ArrayList<>();
System.out.println(list.getClass()); // class java.util.ArrayList
泛型通配符
由于泛型擦除,虽然Number
是Integer
的父类,但是ArrayList<Integer>
与ArrayList<Number>
的类型都是ArrayList
,没有继承关系。下面代码会报错Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
。
java
List<Number> nList;
nList = new ArrayList<Integer>();
为了扩大泛型参数的范围,引入泛型通配符概念。泛型通配符有3种:<?>, <? extends T>, <? super T>
。
上界通配符 <? extends T>
T
表示泛型类型的上界,泛型参数可以是T
及子类。因此下面代码不会报错。
java
List<? extends Number> nList;
nList = new ArrayList<Integer>();
<? extends T>
上界通配符修饰的变量只能读,不能写。这就是PECS(Producer Extends, Consumer Super)
中的PE
。
不能写的原因是上界通配符无法确定实际类型,如果2次写的实际类型不一致,如下代码,那就违背泛型的初衷了。有个特例,可以写null
,因为null
是所有类型的子类。
可以读的原因是实际类型必定是T
的子类,多态允许用T
表示实际类型。
java
List<Integer> iList = new ArrayList<>();
iList.add(3);
List<? extends Number> nList = iList;
Number i = nList.get(0); // 编译正确
nList.add(Integer.valueOf(33)); // 编译错误
下界通配符 <? super T>
T
表示泛型类型的下界,泛型参数可以是T
及父类。
java
? super T get(int index);
void set(? super T);
下界通配符修饰的变量只能写不能读。这就是PECS(Producer Extends, Consumer Super)
中的CS
。
不能读get()
的原因是无法确定实际类型。由于Object
是所有类的父类,因此特例:可以将元素赋值给Object对象。
能写(set)
的原因是:T
的子类型一定是<? super T>
的子类型。
java
List<Integer> iList = new ArrayList<>();
iList.add(3);
List<? super Integer> nList = iList;
nList.add(Integer.valueOf(33));
Number n = nList.get(0); // 编译错误
Object o = nList.get(0); // 编译正确
无限定通配符 <?>
java
? get(int index);
void set(?);
get()
方法只能赋值给Object对象。而set
方法不能被调用,是Object
也不能调用。因此无限定通配符修饰的对象是不可变对象。