目录
JDK 1.5 为 Java 编程语言引入了几个新的扩展。其中泛型就是其一。
先看个示例
- import java.util.*;
-
- public class Main {
- public static void main(String[] args) {
-
- List myIntList = new LinkedList(); // 1
- myIntList.add(new Integer(0)); // 2
- Integer x = (Integer) myIntList.iterator().next(); // 3
-
- }
-
-
- }
以上 //3 行代码是不是很烦人,添加进集合是Intger类型数据,遍历的时候却需要强制转型成Integer 类型。这是因为编程人员是知道添加和遍历是同种类型,但编译器只能保证迭代器返回Object,为了确保对Integer类型的变量的赋值是类型安全的,需要进行强制转换。
程序员可以实际表达他们的意图,并将列表标记为受限制的,以包含特定的数据类型,这是泛型背后的核心思想。下面是上面使用泛型给出的程序片段的一个版本。
- import java.util.*;
-
- public class Main {
- public static void main(String[] args) {
-
- List<Integer> myIntList = new LinkedList<Integer>(); // 1'
- myIntList.add(new Integer(0)); // 2'
- Integer x = myIntList.iterator().next(); // 3'
- }
- }
注意变量myIntList的类型声明。它指定这不是一个任意的List,而是一个Integer的List,写的List
现在,会认为所完成的只是把这些杂乱的东西移走。我们没有在 // 3 上强制转换为Integer,而是在 // 1 上使用Integer作为类型参数。然而,这里有一个非常大的区别。编译器现在可以在编译时检查程序的类型正确性。当我们用类型List
以下是java.util包中List和Iterator接口定义部分代码。
- public interface List <E> {
- void add(E x);
- Iterator
iterator() ; - }
-
- public interface Iterator<E> {
- E next();
- boolean hasNext();
- }
这些代码应该都很熟悉,除了尖括号中的内容。它们是接口List和Iterator的形式类型参数的声明。
类型参数可以在泛型声明中使用,基本上可以在使用普通类型的地方使用。看到了泛型类型声明List的调用,例如List
当我们定义泛型类型
- import java.util.*;
-
- public class Main {
- public static void main(String[] args) {
- // 无编译器警告:
- List<Integer> list = new ArrayList<Integer>();
- list.add(1);
- list.add(2);
- // 无强制转型:
- Integer first = list.get(0);
- Integer second = list.get(1);
- }
-
- }
定义泛型类型
- import java.util.*;
-
- public class Main {
- public static void main(String[] args) {
- List<Number> list = new ArrayList<Number>();
- list.add(new Integer(123));
- list.add(new Double(12.34));
- Number first = list.get(0);
- Number second = list.get(1);
- }
-
-
- }
当我们定义泛型类型
- // 可以省略后面的Number,编译器可以自动推断泛型类型:
- List<Number> list = new ArrayList<>();
在Java标准库中的ArrayList
- public class ArrayList<T> implements List<T> {
- ...
- }
-
- List<String> list = new ArrayList<String>();
即类型ArrayList
要特别注意:不能把ArrayList
- import java.util.*;
-
- public class Main {
- public static void main(String[] args) {
- // 创建ArrayList<Integer>类型:
- ArrayList<Integer> integerList = new ArrayList<Integer>();
- // 添加一个Integer:
- integerList.add(new Integer(123));
- // “向上转型”为ArrayList<Number>:
- ArrayList<Number> numberList = integerList; //编译不通过
- // 添加一个Float,因为Float也是Number:
- numberList.add(new Float(12.34));
- // 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
- Integer n = integerList.get(1); // ClassCastException!
- }
-
- }
把一个ArrayList
实际上,编译器为了避免这种错误,根本就不允许把ArrayList
ArrayList
示例 1
- public class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
- }
示例 2 多个泛型类型
- public class Pair<K,V> {
- private K key;
- private V value;
-
- public Pair(K first, V last) {
- this.key = first;
- this.value = last;
- }
-
- public K getKey() {
- return key;
- }
-
- public void setKey(K key) {
- this.key = key;
- }
-
- public V getValue() {
- return value;
- }
-
- public void setValue(V value) {
- this.value = value;
- }
- }
- public class Person{
-
- /**
- * 泛型普通方法编写
- */
- public <T> void say(T t) {
-
- }
-
- }
- public class Person{
- public static
void say(T t){ - }
- }
- public interface IM <T>{
- public void show(T name);
- }
实现类可以指定泛型类型。
- public class Person implements IM<String> {
- @Override
- public void show(String name) {
-
- }
- }
实现类可以指定泛型类型,相当于将该泛型T传递父类。
- public class Person
implements IM{ - @Override
- public void show(T name) {
-
- }
- }
Java语言的泛型实现方式是擦拭法(Type Erasure)。
所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。
一个泛型类Pair
,这是编译器看到的代码:
- public class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
- }
而虚拟机根本不知道泛型。这是虚拟机执行的代码:
- public class Pair {
- private Object first;
- private Object last;
- public Pair(Object first, Object last) {
- this.first = first;
- this.last = last;
- }
- public Object getFirst() {
- return first;
- }
- public Object getLast() {
- return last;
- }
- }
Java使用擦拭法实现泛型,导致了:
1、编译器把类型
2、编译器根据
使用泛型的时候,我们编写的代码也是编译器看到的代码:
- public class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
- public static void main(String[] args) {
- Pair<String> p = new Pair<>("Hello", "world");
- String first = p.getFirst();
- String last = p.getLast();
- }
- }
而虚拟机执行的代码并没有泛型:
- public class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
- public static void main(String[] args) {
- Pair p = new Pair("Hello", "world");
- String first = (String) p.getFirst();
- String last = (String) p.getLast();
- }
- }
所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。
Java泛型的实现方式——擦拭法,我们就知道了Java泛型的局限:
1、
Pair<int> p = new Pair<>(1, 2); // 编译不通过
2、无法取得带泛型的Class。观察以下代码:
- public class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
- public static void main(String[] args) {
- Pair<String> p1 = new Pair<>("Hello", "world");
- Pair<Integer> p2 = new Pair<>(123, 456);
- Class c1 = p1.getClass();
- Class c2 = p2.getClass();
- System.out.println(c1==c2); // true
- System.out.println(c1==Pair.class); // true
- }
- }
因为T是Object,我们对Pair
换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair
3、无法判断带泛型的类型:
- Pair<Integer> p = new Pair<>(123, 456);
- // 编译错误
- if (p instanceof Pair<String>) {
- }
原因和前面一样,并不存在Pair
4、不能实例化T类型:
- public class Pair<T> {
- private T first;
- private T last;
- public Pair() {
- // 编译错误
- first = new T();
- last = new T();
- }
- }
擦拭后实际上变成了:
- public class Pair<T> {
- private T first;
- private T last;
- public Pair() {
- // 编译错误
- first = new T(); // 擦拭后 first = new Object();
- last = new T(); // 擦拭后 last = new Object();
- }
- }
不恰当的覆写方法
- public class Pair
{ - public boolean equals(T t) {
- return this == t;
- }
- }
这是因为,定义的equals(T t)方法实际上会被擦拭成equals(Object t),而这个方法是继承自Object的,编译器会阻止一个实际上会变成覆写的泛型方法定义。
换个方法名,避开与Object.equals(Object)的冲突就可以成功编译:
- public class Pair
{ - public boolean same(T t) {
- return this == t;
- }
- }
示例
- /**
- * 泛型类
- */
- class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
- /**
- * 直接运行
- * 不兼容的类型: Pair
无法转换为Pair - */
- public static void main(String[] args) {
- Pair<Integer> p = new Pair<>(123, 456);
- int n = add(p); // 编译失败
- System.out.println(n);
- }
-
- static int add(Pair<Number> p) {
- Number first = p.getFirst();
- Number last = p.getLast();
- return first.intValue() + last.intValue();
- }
- }
直接运行提示:
不兼容的类型: Pair
原因:
因为Pair
但是从add()方法的代码可知,传入Pair
- Number first = p.getFirst();
- Number last = p.getLast();
实际类型是Integer,引用类型是Number,没有问题。问题在于方法参数类型定死了只能传入Pair
上界通配符就可以解决这个问题,既可以传入 Number 或者 Integer 类型。
示例修改如下:
- /**
- * 泛型类
- */
- class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
-
- public static void main(String[] args) {
- Pair<Integer> p = new Pair<>(123, 456);
- int n = add(p);
- System.out.println(n);
- }
-
- /**
- *
- * 上界通配符使用, 泛型类型T的上界限定在Number了
- * 除了可以传入Pair
类型,还可以传入Pair类型,Pair类型等,因为Double和BigDecimal都是Number的子类。 - * Pair extends Number> p
- */
- static int add(Pair<? extends Number> p) {
- Number first = p.getFirst();
- Number last = p.getLast();
- return first.intValue() + last.intValue();
- }
- }
修改之后,传入Pair
除了可以传入Pair
如果对Pair extends Number>类型调用getFirst()方法,实际的方法签名变成了:
<? extends Number> getFirst();
即返回值是Number或Number的子类,因此,可以安全赋值给Number类型的变量:
Number x = p.getFirst();
extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)
- /**
- * 泛型类
- */
- class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
-
- public T getLast() {
- return last;
- }
-
- public void setFirst(T first) {
- this.first = first;
- }
-
- public void setLast(T last) {
- this.last = last;
- }
-
- public static void main(String[] args) {
- Pair<Integer> p = new Pair<>(123, 456);
- int n = add(p);
- System.out.println(n);
- }
-
- static int add(Pair<? extends Number> p) {
- Number first = p.getFirst();
- Number last = p.getLast();
-
- /**
- * 编译错误
- * 原因在于擦拭法。如果我们传入的p是Pair<Double>,显然它满足参数定义Pair<? extends Number>,然而,Pair<Double>的setFirst()显然无法接受Integer类型。
- * 这就是<? extends Number>通配符的一个重要限制:方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)
- */
- // p.setFirst(new Integer(first.intValue() + 100));
- // p.setLast(new Integer(last.intValue() + 100));
-
- /**
- * 唯一的例外是可以给方法参数传入null
- */
- //p.setFirst(null);
- // p.getFirst().intValue();
- return first.intValue() + last.intValue();
- }
- }
extends通配符的作用
Java标准库的java.util.List
- public interface List<T> {
- int size(); // 获取个数
- T get(int index); // 根据索引获取指定元素
- void add(T t); // 添加一个新元素
- void remove(T t); // 删除一个已有元素
- }
自己定义一个泛型扩展方法:
- int sumOfList(List<? extends Integer> list) {
- int sum = 0;
- for (int i=0; i<list.size(); i++) {
- Integer n = list.get(i);
- sum = sum + n;
- }
- return sum;
- }
方法参数类型是List extends Integer>而不是List
因此,方法参数类型List extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)、remove(? extends Integer)这些方法。换句话说,这是一个对参数List extends Integer>进行只读的方法(恶意调用set(null)除外)。
extends Number>表明:
换句话说,使用extends通配符表示可以读,不能写。
使用类似
泛型类型限定为Number以及Number的子类。
查看如下代码
- void set(Pair<Integer> p, Integer first, Integer last) {
- p.setFirst(first);
- p.setLast(last);
- }
传入Pair
和extends通配符相反,这次,希望接受Pair
这时候需要使用super通配符来改写这个方法:
- void set(Pair<? super Integer> p, Integer first, Integer last) {
- p.setFirst(first);
- p.setLast(last);
- }
需要注意的是Pair super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。
- class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
- public T getLast() {
- return last;
- }
- public void setFirst(T first) {
- this.first = first;
- }
- public void setLast(T last) {
- this.last = last;
- }
-
-
- static void setSame(Pair<? super Integer> p, Integer n) {
- p.setFirst(n);
- p.setLast(n);
- }
-
-
- public static void main(String[] args) {
- Pair<Number> p1 = new Pair<>(12.3, 4.56);
- Pair<Integer> p2 = new Pair<>(123, 456);
- setSame(p1, 100);
- setSame(p2, 200);
- System.out.println(p1.getFirst() + ", " + p1.getLast());
- System.out.println(p2.getFirst() + ", " + p2.getLast());
- }
-
- }
Pair super Integer>的setFirst()方法,它的方法签名实际上是:
void setFirst(? super Integer);
因此,可以安全地传入Integer类型。
Pair super Integer>的getFirst()方法,它的方法签名实际上是:
? super Integer getFirst();
这里无法使用Integer类型来接收getFirst()的返回值,即下面的语句将无法通过编译:
Integer x = p.getFirst();
因为如果传入的实际类型是Pair
注意:虽然Number是一个抽象类,我们无法直接实例化它。但是,即便Number不是抽象类,这里仍然无法通过编译。此外,传入Pair
Object obj = p.getFirst();
因此,使用 super Integer>通配符表示:
对比extends和super通配符
作为方法参数, extends T>类型和 super T>类型的区别在于:
一个是允许读不允许写,另一个是允许写不允许读。
类似 super Integer>通配符作为方法参数时表示:
即使用super通配符表示只能写不能读。
使用extends和super通配符要遵循PECS原则。
为了便于记忆,何时使用extends,super,我们可以用PECS原则:Producer Extends Consumer Super。
即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。
Java标准库的Collections类定义的copy()方法:
- import java.util.List;
-
- public class Collections {
- // 把src的每个元素复制到dest中:
- public static <T> void copy(List<? super T> dest, List<? extends T> src) {
- for (int i=0; i<src.size(); i++) {
- T t = src.get(i); // src是producer
- // T t = dest.get(0); // 编译失败
- // src.add(t); // 编译失败
- dest.add(t); // dest是consumer
- }
- }
- }
它的作用是把一个List的每个元素依次添加到另一个List中。它的第一个参数是List super T>,表示目标List,第二个参数List extends T>,表示要复制的List。可以简单地用for循环实现复制。在for循环中,可以看到,对于类型 extends T>的变量src,我们可以安全地获取类型T的引用,而对于类型 super T>的变量dest,我们可以安全地传入T的引用。
copy()方法的定义就完美地展示了extends和super的意图:
extends T>和 super T>作为方法参数的作用。实际上,Java的泛型还允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个?
- void sample(Pair> p) {
- }
因为>通配符既没有extends,也没有super,因此:
换句话说,既不能读,也不能写,那只能做一些null判断:
- static boolean isNull(Pair<?> p) {
- return p.getFirst() == null || p.getLast() == null;
- }
大多数情况下,可以引入泛型参数
- static <T> boolean isNull(Pair<T> p) {
- return p.getFirst() == null || p.getLast() == null;
- }
>通配符有一个独特的特点,就是:Pair>是所有Pair
- class Pair<T> {
- private T first;
- private T last;
-
- public Pair(T first, T last) {
- this.first = first;
- this.last = last;
- }
-
- public T getFirst() {
- return first;
- }
- public T getLast() {
- return last;
- }
- public void setFirst(T first) {
- this.first = first;
- }
- public void setLast(T last) {
- this.last = last;
- }
-
- public static void main(String[] args) {
- Pair<Integer> p = new Pair<>(123, 456);
- Pair<?> p2 = p; // 安全地向上转型
- System.out.println(p2.getFirst() + ", " + p2.getLast());
- }
-
-
- }
无限定通配符>很少使用,可以用