泛型的extends和super通配符
extends通配符
首先定义一个类:
class User {
private T t1;
private T t2;
public User() {
}
public User(T t1, T t2) {
this.t1 = t1;
this.t2 = t2;
}
public T getT1() {
return t1;
}
public void setT1(T t1) {
this.t1 = t1;
}
public T getT2() {
return t2;
}
public void setT2(T t2) {
this.t2 = t2;
}
}
有一个add()方法,参数类型为User
public static int add(User user) {
Number t1 = user.getT1();
Number t2 = user.getT2();
return t1.intValue() + t2.intValue();
}
-
在主函数中调用add()方法
public static void main(String[] args) { User
user = new User<>(123, 356); System.out.println(add(user)); } 原因是:因为
User
并不是User
的子类,所以编译阶段就会报User
错误。无法转换为javase.generics.upcasting.User -
想要接收
User
,可以使用User<? extends Number>
。public static int add(User<? extends Number> user) { Number t1 = user.getT1(); Number t2 = user.getT2(); return t1.intValue() + t2.intValue(); }
这样改造后,不仅可以接收
Integer
,还可以传入Double
、BigDecimal
等子类。这就称为extends通配符。
使用extends通配符,有什么要注意的点?
User类可以简化看作三种元素构成:T t
、T get()
、set(T t)
。
使用了extends通配符后,上述User
类可以简化地理解为:
class User<? extends Number> {
private <? extends Number> t;
public <? extends Number> getT1() {
return t1;
}
public void setT1(<? extends Number> t1) {
this.t1 = t1;
}
}
现在分别尝试调用get()
、set()
方法。
public static int add(User<? extends Number> user) {
Number t1 = user.getT1();
Number t2 = user.getT2();
user.setT1(new Integer(12321));
user.setT2(new Integer(431243));
return t1.intValue() + t2.intValue();
}
结果:get()
方法没有问题,set()
方法编译无法通过。
原因:
Java泛型擦拭机制:编译器总是将Object
,并且在需要转型的时候,编译器会根据T
的类型自动为我们实行安全地强制转型。
-
对于
get()
方法来说在本例中,因为Java泛型擦拭机制的存在,所以编译器将
<? extends Number>
处理成Object
,在返回结果时,自动安全的转型。但因为Java是一门静态语言,运行完成编译器会将结果自动转型为
Integer
、Double
、BigDecimal
等Number
的哪一种子类型,在运行完成前是不知道的,所以在此例中,想要安全的接收返回结果,只能用Number
去接收。 -
对于
set()
方法来说同样是因为泛型擦拭机制,在运行前编译器并不能识别出
User<? extends Number> user
是哪一种类型,但是很显然,除了User
类型之外的其他子类型的user set()
方法都不能传入Integer
类型参数。所以在编译阶段,
set()
方法就会报java: 不兼容的类型: java.lang.Integer无法转换为capture#1, 共 ? extends java.lang.Number
错误。
super通配符
extends通配符User<? extends Number>
可以接收Integer
、Double
和BigDecimal
等Number
的子类型。
super通配符和extends通配符刚好相反。User<? super Integer>
可以接收Number
、Object
等父类型。
同样User类可以简化看作三种元素构成:T t
、T get()
、set(T t)
。
使用了extends通配符后,上述User
类可以简化地理解为:
class User<? super Integer> {
private <? super Integer> t;
public <? super Integer> getT1() {
return t1;
}
public void setT1(<? super Integer> t1) {
this.t1 = t1;
}
}
同样是调用get()
和set()
:
public static int test(User<? super Integer> user) {
Integer t1 = user.getT1();
Integer t2 = user.getT2();
user.setT1(12321); //Integer自动装箱
user.setT2(12321);
return t1 + t2;
}
结果:set()
方法没有问题,get()
方法编译无法通过。
原因:
-
对于
get()
方法来说和extends通配符同样的道理,同样是由于擦拭机制,编译器会将
<? super Integer>
处理成Object
,在返回结果时,自动地安全转型。由于Java的静态语言特性,在运行前只能知道返回值可能是
Integer
类型、Number
和Object
等超类型。因为无法具体知道是哪一种类型,所以在使用
Integer
去接收参数时,在编译阶段就会报java: 不兼容的类型: capture#1, 共 ? super java.lang.Integer无法转换为java.lang.Integer
错误。 -
对
set()
方法来说一个子类对象自然也是一个父类对象。即无论
User<? super Integer> user
是哪一种Integer
的超类,都不妨碍一个Integer
对象自然也是一个超类(如是一个Number
对象)。因此一个Integer对象总是可以作为传入参数并生效的。
总结
无论是<? extends Number>
还是<? super Integer>
都可以理解为就是某一种类型。
-
在
<? extends Number>
中,这种类型一定是Number
的一个子类型,只不过目前还不知道这种类型是什么,所以用?
代替。 -
在
<? super Integer>
中,这种类型一定是Integer
的一个超类型,只不过目前还不知道这种类型是什么,所以用?
代替。
上两节描述的两种情况,其实背后都是因为Java泛型的擦拭机制导致的,同时这种Java泛型的擦拭机制的制定是因为Java需要先编译后执行的静态语言特性,为了让编译器能在早期来帮助静态校正程序和提前找到错误,必须牺牲一定的灵活性——编译器无法准确判断泛型类型,从而实现安全转型的都报错误。