一 容器类:请取出同一类型的物品
1 不使用泛型
// A是B的父类
List list = new ArrayList<>();
list.add(new A());
list.add(new A());
list.add(new String());
System.out.println((A)list.get(0));
System.out.println((A)list.get(1));
// 编译时不报错,运行时报错:java.lang.ClassCastException
System.out.println((A)list.get(2));
2 不恰当泛型
// A是B的父类
List<A> list = new ArrayList<>();
list.add(new A());
list.add(new B());
// list.add(new String()); // 加上时,编译会报错
System.out.println((B)list.get(0));
// 编译时不报错,运行时报错:java.lang.ClassCastException
System.out.println((B)list.get(1));
- 补救方案:先进行类型判断
A a1 = list.get(1);
if(a1.getClass() == B.class){
System.out.println((B)a1);
}
3 正确使用泛型
// A是B的父类
List<A> list = new ArrayList<>();
list.add(new A());
list.add(new B());
// 如果把B放入A的容器中,就把B当成A使用,不要想着强转回来使用了。
System.out.println(list.get(0));
System.out.println(list.get(1));
4 总结
- 没有泛型时,不能依赖编译器的语法检查,List 不完全等同于 List<?>,添加泛型时,该方法的属性将多一个Signature;。
- 泛型能够给编译器提供类型检查。
- 父类容器虽然可以放入子类,但取出来尽量不要还原回子类进行使用,费时费力。
注:一个接受Collection<?>的方法和一个接受List的方法构成重载,调用传参为ArrayList时将导致编译错误。
二 泛型通配符
1 准备
public class Main {
static class A {
}
static class B extends A {
}
static class C<T>{
private T t;
public C(){
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
}
2 测试
(1)类型擦除和类型强转
- java
public class Main {
public static void main(String[] args) {
C<A> cA = new C<>();
// 类型强转:这就是为什么1.1节中编译不报错,运行时报错的原因。编译时类型擦错,并不进行检查。
// Object obj = cA.getT();
// A t = (A)obj;
A t = cA.getT();
}
}
- 字节码
public static void main(java.lang.String[]);
Code:
0: new #2 // class Main$C
3: dup
4: invokespecial #3 // Method Main$C."<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method Main$C.getT:()Ljava/lang/Object;
12: checkcast #5 // class Main$A
15: astore_2
16: return
(2)C<A>和C<B>是相同类型吗?
public static void main(String[] args) {
C<A> cA = new C<>();
C<B> cB = new C<>();
// 为什么Class<? extends C> 而不是Class<C> Class<?> Class<? super C> ?
Class<? extends C> aClass = cA.getClass();
Class<? extends C> bClass = cB.getClass();
boolean b = aClass == bClass; // true
}
(3)通配符作为入参
-
demo1
public static void main(String[] args) {
C<A> cA = new C<>();
C<B> cB = new C<>();
test(cA);
test(cB); // 编译时错误:Error: java: 不兼容的类型
}
public static void test(C<A> c){
A a = c.getT();
}
-
demo2:
C<Object>
不是C<A>
的父类
public static void main(String[] args) {
C<A> cA = new C<>(new A());
C<B> cB = new C<>(new B());
test(cA); // 编译时错误:Error: java: 不兼容的类型
test(cB); // 编译时错误:Error: java: 不兼容的类型
}
public static void test(C<Object> c) {
Object t = c.getT();
}
-
demo3:
C<?>
接收所有类型
public static void main(String[] args) {
C<A> cA = new C<>(new A());
C<B> cB = new C<>(new B());
test(cA);
test(cB);
}
public static void test(C<?> c) { // 或者C c也行
Object t = c.getT();
}
-
demo4:定义上界限,泛型是A或者继承A即可。
public static void main(String[] args) {
C<A> cA = new C<>(new A());
C<B> cB = new C<>(new B());
test(cA);
test(cB);
}
public static void test(C<? extends A> c) {
A t = c.getT();
}
-
demo5:定义下界限,泛型是A或A的父类。
public static void main(String[] args) {
C<A> cA = new C<>(new A());
C<B> cB = new C<>(new B());
test(cA);
test(new C<Object>());
test(cB); // 编译时报错:
}
public static void test(C<? super A> c) {
Object t = c.getT();
}
(4)统配符作为返回值
-
demo1:可以修改,但是需要是泛型是A或者A的子类.
public static void main(String[] args) {
C<A> test = test();
test.setT(new A());
test.setT(new B());
test.setT(new Object()); // 编译错误
}
public static C<A> test() {
C<A> cA = new C<>(new A());
return cA;
}
-
demo2:?返回,无法修改,只能读取
public static void main(String[] args) {
C<?> test = test();
test.setT(new A()); // 编译错误
test.setT(new B()); // 编译错误
test.setT(new Object()); // 编译错误
}
public static C<?> test() {
C<A> cA = new C<>(new A());
return cA;
}
-
demo3:?extends 返回,无法修改,只能读取,和demo2相同,但是类中的泛型是什么。
public static void main(String[] args) {
C<? extends A> test = test();
test.setT(new A()); // 编译错误
test.setT(new B()); // 编译错误
test.setT(new Object()); // 编译错误
}
public static C<? extends A> test() {
C<A> cA = new C<>(new A());
return cA;
}
-
demo4:可以修改,但泛型需要是A或者继承A的类,和demo1相同。
public static void main(String[] args) {
C<? super A> test = test();
test.setT(new A());
test.setT(new B());
test.setT(new Object()); // 编译错误
}
public static C<? super A> test() {
C<A> cA = new C<>(new A());
return cA;
}
- 结论:
- 泛型返回时,如果可以修改则指定泛型类型即可,如demo1
- 泛型返回时,如果不可以修改,则使用demo3,这样可以知道泛型类型是什么。这也是为什么c.getClass()方法返回Class<? extends C>的原因。
(5)泛型多继承
static class A {}
static interface D {}
// extends 类或接口 & 接口 & 接口 ...
static class C<T extends A & D & Comparable> {
private T t;
}
3 总结
- 编译时进行类型擦除,取出时进行类型强转。
- Class类型相同,但是泛型类型不同。
- 传参:Class类型相同情况下
- ?:接收所有类型的泛型类型
- ? extends T:接收泛型类型为T或者T的子类
- ? super T:接收泛型类型为T或者T的父类
- T:只接收泛型类型为T
- 返回:都支持读取
- ?:无法修改
- ? extends T:无法修改,但知道泛型类型为T
- ?super T:支持修改为T或T的子类的对象
- T:支持修改为T或T的子类的对象
- 泛型继承:T extends 类或者接口 & 接口 & 接口 ...