重新认识Java——泛型(通配、特性和注意点)

上一篇文章介绍了Java泛型中的基础及原理,本文将继续研究有关Java泛型的内容。本文的主要内容有:

  1. 泛型的特性
  2. 泛型通配
  3. 泛型类与普通类的不同点,也是日常开发要主要的点

泛型特性

泛型的兼容性

首先要强调的是,泛型是编译时才会检查合法性,编译后会直接擦除泛型信息。正由于这一点,所以在使用Eclipse编写源代码时,如果代码不合法,它会直接提示我们。Java编译器是向后兼容的,也就是低版本的源代码可以用高版本编译器进行编译。下面来看看那些兼容性代码。

  1. 引用和实例化都不包含泛型信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;
/**
* @author xialei
* @version 1.0 2016年6月28日下午9:03:44
*/
public class Compatibility {
public static void main(String[] args) {
// 下面编译通过
List list1 = new ArrayList();
list1.add("123");
list1.add(1);
}
}

上面的这段代码是可以通过编译的,这是JDK1.4之前的写法,所以可以验证JDK1.5之后的编译器是可以兼容JDK1.4之前的源代码的。不过,笔者在JDK1.8.x版本的编译器进行编译时,会抛出如下所示的警告信息。很显然,如果类被定义成泛型类,但是在实际使用时不使用泛型特性,这是不推荐的做法!

1
2
注: Compatibility.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
  1. 引用使用泛型,实例化不使用泛型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.List;
/**
* @author xialei
* @version 1.0 2016年6月28日下午9:03:44
*/
public class Compatibility {
public static void main(String[] args) {
// 编译不通过
List<String> list2 = new ArrayList();
list2.add("123");
list2.add(1); // 这里出错
}
}

上面的代码编译不通过,由于对引用使用了泛型,其中的所能容纳的对象必须为String 类型。这种写法实际上跟完整写法的作用一致,不过Eclipse仍然会警告。

  1. 引用不使用泛型,实例化使用泛型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.ArrayList;
import java.util.List;
/**
* @author xialei
* @version 1.0 2016年6月28日下午9:03:44
*/
public class Compatibility {
public static void main(String[] args) {
// 编译通过
List list3 = new ArrayList<String>();
list3.add("123");
list3.add(1);
}
}

上面的这段代码可以编译通过,其效果与1(不使用泛型)完全一致。结合2、3可以知道,编译时只能做引用的类型检查,而无法检查引用所指向对象的实际类型。

泛型与继承

在使用泛型时,引用的参数类型与实际对象的参数类型要保持一致(通配符除外),就算两个参数类型是继承关系也是不允许的。看看下面的2行代码,它们均不能通过编译。

1
2
ArrayList<String> arrayList1 = new ArrayList<Object>(); //编译错误
ArrayList<Object> arrayList1 = new ArrayList<String>(); //编译错误

下面来探讨一下为什么不能这么做。

  1. 第1种情况,如果这种代码可以通过编译,那么调用get()方法返回的对象应该是String,但它实际上可以存放任意Object类型的对象,这样在调用类型转换指令时会抛出ClassCastException。这样可以不是那么明显,来看看下面的代码。arrayList1中实际存放的Object对象,所以在进行类型转换时会抛出异常。这原本就是泛型想要极力避免的问题,所以Java允许这种写法。
1
2
3
ArrayList<Object> arrayList1 = new ArrayList<Object>();
arrayList1.add(new Object());
ArrayList<String> arrayList2 = arrayList1; //编译错误
  1. 第2种情况。虽然String类型的对象转换为Object不会有任何问题,但是这有什么意义呢?我们原本想要用String对象的方法,但最终将其赋予了一个Object类型的引用。如果需要使用String中的某些方法,必须将Object强制转换为String。这样不会抛出异常,但是却违背了泛型设计的初衷。

泛型与多态

下面来考虑一下泛型中多态问题。普通类型的多态是通过继承并重写父类的方法来实现的,泛型也不例外,下面是一个泛型多态示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年7月2日 下午7:50:35
*/
package demo.blog.java.generic;
/**
* @author xialei
* @version 1.0 2016年7月2日下午7:50:35
*/
public class Father<T> {
public void set(T t) {
System.out.println("I am father, t=" + t);
}
public T get() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年7月2日 下午7:50:57
*/
package demo.blog.java.generic;
/**
* @author xialei
* @version 1.0 2016年7月2日下午7:50:57
*/
public class Son extends Father<String> {
@Override
public void set(String t) {
super.set(t);
System.out.println("I am son.");
}
@Override
public String get() {
return super.get();
}
public static void main(String[] args) {
Father<String> father = new Son();
father.set("hello world");
}
}

上面定义了一个泛型父类和一个实际参数为String类型的子类,并“重写”了set(T)get()方法。Son类中的@Override注解也清楚地显示这是一个重写方法,最终执行的结果如下,与想象中的结果完全一致。

1
2
I am father, t=hello world
I am son.

真的这么简单么?虽然表面上(源代码层面)来看,泛型多态与普通类的多态并无二样,但是其内部的实时原理却大相径庭。

从上一篇文章可以知道,泛型类Father在编译后会擦除泛型信息,所有的泛型参数都会用Object类替代。实际上,Father编译后的字节码与下面的代码完全一致。

1
2
3
4
5
6
7
8
9
10
public class Father {
public void set(Object t) {
System.out.println("I am father, t=" + t);
}
public Object get() {
return null;
}
}

Son类的与最终会变为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Son extends Father {
@Override
public void set(String t) {
super.set(t);
System.out.println("I am son.");
}
@Override
public String get() {
return super.get();
}
public static void main(String[] args) {
Father father = new Son();
father.set("hello world");
}
}

FatherSon类的set()方法的参数类型不一样,所以,这并不是方法重写,而是方法重载!但是,如果是重载,那么Son类就应该会继承Father类的set(Object)方法,也就是Son会同时包含set(String)set(Object),下面来测试一下。

1
2
3
Son son = new Son();
son.set("test");
son.set(new Object()); // 编译错误

当set一个Object对象时,编译无法通过。这就很奇怪了,感觉跟之前学到的知识是相悖的。我们原本想通过重写方法来实现多态,但由于泛型的类型擦除,却最终变成了重载,所以类型擦除与多态有了矛盾。那么Java是怎么解决这个问题的呢?还是从字节码中找答案吧。Son类最终的编译结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{
public demo.blog.java.generic.Son();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method demo/blog/java/generic/Father."<init>":()V
4: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo/blog/java/generic/Son;
public void set(java.lang.String); // 我们重写的方法
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokespecial #16 // Method demo/blog/java/generic/Father.set:(Ljava/lang/Object;)V
5: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #25 // String I am son.
10: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: return
LineNumberTable:
line 17: 0
line 18: 5
line 19: 13
LocalVariableTable:
Start Length Slot Name Signature
0 14 0 this Ldemo/blog/java/generic/Son;
0 14 1 t Ljava/lang/String;
public java.lang.String get(); // 我们重写的方法
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #36 // Method demo/blog/java/generic/Father.get:()Ljava/lang/Object;
4: checkcast #39 // class java/lang/String
7: areturn
LineNumberTable:
line 23: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this Ldemo/blog/java/generic/Son;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #1 // class demo/blog/java/generic/Son
3: dup
4: invokespecial #43 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #44 // String hello world
11: invokevirtual #16 // Method demo/blog/java/generic/Father.set:(Ljava/lang/Object;)V
14: return
LineNumberTable:
line 27: 0
line 28: 8
line 29: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 args [Ljava/lang/String;
8 7 1 father Ldemo/blog/java/generic/Father;
LocalVariableTypeTable:
Start Length Slot Name Signature
8 7 1 father Ldemo/blog/java/generic/Father<Ljava/lang/String;>;
public java.lang.Object get(); // 编译器生成的方法
descriptor: ()Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #52 // Method get:()Ljava/lang/String;
4: areturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
public void set(java.lang.Object); // 编译器生成的方法
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #39 // class java/lang/String
5: invokevirtual #54 // Method set:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
}

编译结果有点长,为了方便,省去了无关的字节码。先注意到第77行和第90行,这里面多了一个Object get()方法和set(Object)方法,这两个方法在Son类源代码里面并不存在,这是编译器为了解决泛型的多态问题而自动生成的方法,称为“桥方法”。这两个方法的签名与Father类中的两个方法的签名完全一致,这才是真正的方法重写。也就是说,子类真正重写的我们看不到的桥方法,啊,多么痛的领悟!!!@Override注解只是假象,让人误以为他们真的是重写方法。

再看看set(Object)桥方法的实现细节,在第97行处先将Object对象强制转换为String对象,然后调用Son中的set(String)方法。饶了一个圈,最终才回到我们“重写”的方法。main方法中原本调用父类的set(Object)方法,由于子类通过桥方法重写了这个方法,所以最终的调用顺序是:set(Object) -> set(String)

这是脑海中必然会闪现出这么一个问题,既然调用的是set(Object)方法,那么可以在源代码中set一个Object对象么?下面来测试一下:

1
2
3
Father<String> father = new Son();
father.set("hello world");
father.set(new Object()); // 编译错误

上面会出现编译错误,如果是在运行时环境中,这样是可以运行的,不过会出现ClassCastException异常。

set(Object)桥方法的意义不同,Object get()并不仅仅解决泛型与重写的冲突,而更具有一般性。看看下面的代码,这是一个普通类的继承,

1
2
3
4
5
6
7
8
9
10
11
/**
* 普通类的继承。
* @author xialei
* @version 1.0 2016年7月2日下午8:41:47
*/
public class GeneralFather {
public Object get() {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 普通类的继承。
* @author xialei
* @version 1.0 2016年7月2日下午8:42:10
*/
public class GeneralSon extends GeneralFather {
@Override
public String get() {
return "";
}
}

子类的返回类型是父类的返回类型的子类,这是允许的,这种特性叫做Java返回值的协变性。而协变性的实现方法就是上面所述的桥方法。

这里还会有疑惑,set方法可以通过参数类型来确定调用的方法。但是,参数一样而返回值不一样是不能重载的。如果我们在源代码中通过编写String get()Object get()方法是无法通过编译的。虽然,编译器无法通过编译,但是JVM是可以编写这两种方法的,它调用方法时,将返回值也作为方法签名的一部分。有种只许州官放火,不许百姓点灯的感觉。可以看到,JVM做了不少我们认为不合法的事情,所以如果不深入研究底层原理,有些问题根本解释不了。

泛型通配

所谓泛型通配,是指在声明泛型类型的变量时,可以不必直接指定具体的泛型,而可以使用通配符号来代表一系列类型。通配符有2种:

  1. 无边界通配符,用<?>表示。
  2. 有边界通配符,用<? extends Object>或者<? super extends Object>来表示。(Object仅仅是一个示例)

为什么需要通配呢?先看下面的一个示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年7月2日 下午9:18:04
*/
package demo.blog.java.generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 解释为什么需要泛型通配。
* @author xialei
* @version 1.0 2016年7月2日下午9:18:04
*/
public class WhyNeedWildcard {
/**
* 创建一个能够打印所有集合对象中的元素的方法。
* @param collection
*/
public static void printCollection(Collection<Object> collection) {
for (Object object : collection) {
System.out.println(object);
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("t1");
list.add("t2");
printCollection(list); // 编译不通过。The method printCollection(Collection<Object>) in the type WhyNeedWildcard is not applicable for the arguments (List<String>)
}
}

我们的本意是想创建一个能够打印所有集合对象中的元素的方法,但是上一篇文章已经说过了,泛型参数不支持继承,所以编译不通过。解决这个问题的办法就是使用通配符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* 个人主页:http://hinylover.space
*
* Creation Date: 2016年7月2日 下午9:18:04
*/
package demo.blog.java.generic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 解释为什么需要泛型通配。
* @author xialei
* @version 1.0 2016年7月2日下午9:18:04
*/
public class WhyNeedWildcard {
/**
* 创建一个能够打印所有集合对象中的元素的方法。
* @param collection
*/
public static void printCollection(Collection<?> collection) {
for (Object object : collection) {
System.out.println(object);
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("t1");
list.add("t2");
printCollection(list); / 编译通过
}
}

无边界

无边界的通配符用<?>表示,上面的例子就是使用的无边界通配符。下面看看那些是合法的,哪些是不合法的。

1
2
3
List<?> list = new ArrayList<String>(); // 合法
List<?> list = new ArrayList<?>(); // 不合法
List<String> list = new ArrayList<?>(); // 不合法

对于带有通配符的引用变量,是不能调用具有与泛型参数有关的方法的。

1
2
3
4
List<?> list = new ArrayList<String>();
list.add(1); // 编译不通过
list.get(0); // 编译通过
int size = list.size(); // 由于size()方法中不含泛型参数,所以可以在通配符变量中调用

总结起来,无边界通配符主要用做引用,可以调用与泛型参数无关的方法,不能调用参数中包含泛型参数的方法。

有边界

有边界的通配符会对引用的泛型类型进行限定,包括:上边界通配和下边界通配。

  1. 上边界通配,用<? extends 类型>表示。其语法为:
1
List<? extends 类型1> x = new ArrayList<类型2>();

其中,类型2就只能是类型1或者是类型1的子类。下面代码验证合法性。

1
2
List<? extends Number> x = new ArrayList<Integer>(); //由于Integer是Number的子类,这是合法的
List<? extends Number> x = new ArrayList<String>(); //由于String不是Number的子类,这是不合法的
  1. 下边界通配,用<? super 类型>表示。其语法为:
1
List<? super 类型1> x = new ArrayList<类型2>();

其中,类型2就只能是类型1或者是类型1的超类。下面代码有验证合法性。

1
2
List<? super Integer> x = new ArrayList<Number>(); //由于Number是Integer的超类,这是合法的
List<? super Integer> x = new ArrayList<String>(); //由于String不是Integer的超类,这是不合法的

那么到底什么时候使用下边界通配,什么时候使用上边界通配呢?首先考虑一下怎样才能保证不会发生运行时异常,这是泛型要解决的首要问题,通过前面的内容可以看到,任何可能导致类型转换异常的操作都无法编译通过。

  • 上边界通配:可以保证存放的实际对象至多是上边界指定的类型,那么在读取对象时,我们总是可以放心地将对象赋予上边界类型的引用。
1
2
3
4
5
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);
List<? extends Number> list2 = list1;
Number a = list2.get(0); // 编译通过
  • 下边界通配:可以保证存放的实际对象至少是下边界指定的类型,那么在存入对象时,我们总是可以放心地将上边界类型的对象存入泛型对象中。
1
2
3
List<? super Integer> list3 = new ArrayList<Number>();
list3.add(1);
list3.add(2);

总结起来就是:

  • 如果你想从一个数据类型里获取数据,使用 ? extends 通配符。
  • 如果你想把对象写入一个数据结构里,使用 ? super 通配符。
  • 如果你既想存,又想取,那就别用通配符。

这就是《Effective Java》书中所说的PECS法则(Producer Extends, Consumer Super),Collections工具类中的copy方法就完美地诠释了这个法则。

1
2
3
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
...
}

这个方法的作用是将src列表完整地拷贝到dest列表中。src是原始列表,我们需要读取其中的元素,所以它是生产者,需要使用extends通配;dest是目标列表,需要将读取出来的元素存入这个列表中,所以他是消费者,使用super通配。

泛型中的若干问题

泛型与异常

在异常问题上泛型是比较特殊的,主要表现在:

1、泛型类不能继承Throwable类,所以泛型类的对象既不能捕获也不能抛出。下面的代码是不合法的。

1
2
3
public class Problem<T> extends Exception {
...
}

假设上边的代码是合法的,那么我们可以catch住上面的定义的异常类。

1
2
3
4
5
6
7
try {
} catch (Problem<String> e1) {
} catch (Problem<Integer> e1) {
}

我们知道Problem类编译后会擦除泛型信息,相当于同时catch住了两个相同的异常(都是原始异常类Problem),这在Java中是不予许的。

2、不能在catch子句中使用泛型变量,譬如:

1
2
3
4
5
6
7
public static <T extends Throwable> void doSomething(Class<T> t){
try {
...
} catch (T e) { //编译错误
...
}
}

假设上面合法,那么下面的情况呢?

1
2
3
4
5
6
7
8
9
public static <T extends Throwable> void doSomething(Class<T> t){
try {
...
} catch(T e) {
...
} catch(IndexOutOfBounds e) {
...
}
}

泛型在编译后会擦除泛型信息,所有的T都会用Throwable进行替换,如果上面是合法的,那么下面也是合法的。明显下面的catch顺序是不符合Java语法的。为了避免在运行时产生这种错误,所以Java干脆直接禁止了这种写法。

1
2
3
4
5
6
7
8
9
public static <T extends Throwable> void doSomething(Class<T> t){
try {
...
} catch(Throwable e) {
...
} catch(IndexOutOfBounds e) {
...
}
}

泛型与基本类型

泛型的实际类型必须是引用类型,而不能是基本类型,所以下面的代码都是不合法的。

1
2
3
List<int> list = new ArrayList<int>(); // Syntax error, insert "Dimensions" to complete ReferenceType
List<byte> list = new ArrayList<byte>(); // Syntax error, insert "Dimensions" to complete ReferenceType
...

泛型与数组

根据《Effective Java》书中所说,泛型与数组存在两个重要的不同点:

  1. 数组是协变的,而泛型不是。如果Son是Father的子类型,那么Son[]也是Father[]的子类型。而泛型则没有这种关系。
  2. 数组是可以具体化的,因此数组只有在运行时才知道其实际的元素类型。如果企图将String保存在Long数组中,会抛出ArrayStoreException异常。相比之下,泛型是通过编译时擦除泛型信息来实现的,因此,泛型只在编译时强化类型信息,并在运行时丢弃类型信息。

上面的不同点导致数组与泛型并不能很好的混合使用,下面的代码都是不合法的。

1
2
3
new ArrayList<String>[2]; // Cannot create a generic array of ArrayList<String>
new ArrayList<T>[2]; // Cannot create a generic array of ArrayList<T>
new T[2]; // Cannot create a generic array of T

看看下面的代码。

1
2
3
4
List<String>[] array = new ArrayList<String>[];
Object[] objectArray = array;
objectArray[0] = 1;
String c = array[0].get(0);

假设第1行代码是合法的,下面的3行代码都是没有问题的。objectArray数组的0号实际保存的是一个整型对象,而在第4行代码出却要转换为String类型,这会发生ClassCastException。异常的引入就是尽量消除这个运行时异常,所以第1行代码所示的数组是不合法的。

泛型实例化

泛型变量是不能被实例化的,这个是笔者在实际开发过程多次遇到过的。

1
Object c = new T(); // Cannot instantiate the type T

本意是想实例化一个类型为T的对象,但是这样是无法编译通过的。可以通过上一篇文章中的示例来实现:

1
2
3
public <T> T getObject(Class<T> t) throws Exception {
return t.newInstance();
}

泛型与静态方法和静态类

泛型类中的静态变量和静态方法和不可以使用泛型类所声明的泛型类型参数,下面的操作是不合法的。

1
2
3
4
5
6
7
public class Test<T> {
public static T one; //编译错误
public static T show(T one){ //编译错误
return null;
}
}

这是由于泛型的具体参数要在实例化是才能确定,而静态变量和静态方法无需实例化就可以调用。当对象都还没有创建时,就调用与泛型变量相关的方法,当然是错误的。不过,对于泛型方法,静态泛型方法是可以的,因为具体的泛型类型无需实例化就可以确定。

方法命名冲突

下面来看看由于使用泛型而引起的方法命名冲突问题。

1
2
3
4
5
6
7
8
public class Father<T> {
// 编译不通过,Name clash: The method equals(T) of type Father<T> has the same erasure as equals(Object) of type Object but does not override it
public boolean equals(T obj) {
// TODO Auto-generated method stub
return super.equals(obj);
}
}

我们的本意是想写一个与指定泛型类型对象判断相等性的方法,这个方法与Objectequals(Object)方法的含义本质上是不同的。Objectequals(Object)是判断相同类型的对象的相等,而我们定义的方法却不是这个含义。

以Father为例,看上去重载了Objectequals(Object)方法,因为我们定义的equals方法的参数是String类型的,也就是equals(String)。但是,由于Father在编译后会擦除泛型信息,所以equals(T)就被编译成了equals(Object),这与Objectequals(Object)方法签名一模一样,这不就是覆盖么!我们并不想覆盖父类的方法,却在事实上覆盖了。如果上面的代码可以编译并运行,对于开发者来说就是一个大坑。唯一的办法就是改另外一个方法名罗。

其他

泛型规范说明提及另一个原则“要支持擦除的转换,需要强行制一个类或者类型变量不能同时成为两个接口的子类,而这两个子类是同一接品的不同参数化。”
下面的代码是非法的:

1
2
class Calendar implements Comparable<Calendar>{ ... }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>{...} //ERROR

GregorianCalendar会实现Comparable和Compable,是同一个接口的不同参数化实现,这是不允许的。

总结

花了不少时间,将Java泛型中的核心内容整理完了,笔者收获颇丰,一些在平常开发中不解的问题也得到了解答。类型擦除是Java实现泛型的手段,也是被人说诟病的地方。文中的很多问题都是由类型擦除所引起的,也为开发者挖了不少“坑”。Java的进阶之旅还远没有结束,接下来还将继续学习Java。


参考文献:

http://blog.csdn.net/lonelyroamer/article/details/7868820
http://blog.csdn.net/lonelyroamer/article/details/7927212
《Effective Java》


本文由xialei原创,,转载请说明出处http://hinylover.space/2016/07/03/relearn-java-generic-2/