警惕泛型是不能协变和逆变的

什么叫协变(covariance)和逆变(contravariance)? Wiki上是这样定义的:

Within the type system of 阻 programming language,covariance and contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations (such as parameters, generics, and return types).

在编程语言的类型框架中,协变和逆变是指宽类型和窄类型在某种情况下(如参数、泛 型、返回值)替换或交换的特性,简单地说,协变是用一个窄类型替换宽类型,而逆变则是用宽类型覆盖窄类型。其实,在Java中协变和逆变我们已经用了很久了,只是我们没发觉而 已,看如下代码:

class Base{
	      public Number doStuff(){
		    return 0;
		}
}
class Sub extends Base{ 
	     public Integer doStuff(){
	       return 0;
	   }
}


子类的doStuff方法返回值的类型比父类方法要窄,此时doStuff方法就是一个协变方 法,同时根据Java的覆写定义来看,这又属于覆写。那逆变是怎么回事呢?代码如下:

class Base{

     public void doStuff(Integer i){
    	 
     }
}
class Sub extends Base{

    public void doStuff(Number n){

   }

}


子类的doStuff方法的参数类型比父类要宽,此时就是一个逆变方法,子类扩大了父类方法的输入参数,但根据覆写定义来看,doStuff不属于覆写,只是重载而已。由于此时的

doStuff方法已经与父类没有任何关系了,只是子类独立扩展出的一个行为,所以是否声明为doStuff方法名意义不大,逆变已经不具有特别的意义了,我们来重点关注一下协变,先看如下代码是否是协变:

public static void main(String[3args) {

       Base base = new Sub();

}

base变量是否发生了协变?是的,发生了协变,base变量是Base类型,它是父类,而 其赋值却是子类实例,也就是用窄类型覆盖了宽类型。这也叫多态(Polymorphism),两者 同含义,在Java世界里“重复发明”轮子的事情多了去了。

说了这么多,下面再来想想泛型是否也支持协变和逆变,答案是:泛型即不支持协变,也不支持逆变。很受伤是吧?为什么会不支持呢?

(1)     泛型不支持协变

数组和泛型很相似,一个是中括号,一个是尖括号,那我们就以数组为参照对象,看如下代码:

	
	public static void main(String[]args) {

		//数组支持协变

		Number[] n = new Integer [10];

		//编译不通过,泛型不支持协变

		List<Number>In =new ArrayList<Integer>();

		}
	
}


ArrayList是List的子类型,Integer是Number的子类型,里氏替换原则(LiskovSubstitution Principle)在此处行不通了,原因就是Java为了保证运行期的安全性,必须保证 泛型参数类型是固定的,所以它不允许一个泛型参数可以同时包含两种类型,即使是父子类

关系也不行。

泛型不支持协变,但可以使用通配符(Wildcard)模拟协变,代码如下所示:

//Number的子类型(包括Number类型)都可以是泛型参数类型

List<? extends Number> In = new ArrayList<Integer>();

? extends Number表示的意思是,允许Number所有的子类(包括自身)作为泛型参 数类型,但在运行期只能是一个具体类型,或者是Integer类型,或者是Double类型,或者 是Number类型,也就是说通配符只是在编码期有效,运行期则必须是一个确定类型。

(2)     泛型不支持逆变

Java虽然可以允许逆变存在,但在对类型赋值上是不允许逆变的,你不能把一个父类实例对象賦值给一个子类类型变量,泛型自然也不允许此种情况发生了,但是它可以使用 super关键字来模拟实现,代码如下。

//Integer的父类型(包括Integer)都可以是泛型参数类型

List<? super Integer> li = new ArrayList<Number>();

? super Integer”的意思是可以把所有Integer父类型(自身、父类或接口)作为泛型参 数,这里看着就像是把一个Number类型的ArrayList陚值给了 Integer类型的List,其外观 类似于使用一个宽类型覆盖一个窄类型,它模拟了逆变的实现。

泛型既不支持协变也不支持逆变,带有泛型参数的子类型定义与我们经常使用的类类型 也不相同,其基本的类型关系如表

Integer是Number的子类型?

V

ArrayList<Integer> 是 List<Integer> 的子类型?

V

Integer□是 Number[]的子类型?

V

List<Integer> 是 List<Number> 的子类型?

X

List<Integer> 是 List<? extends Integer〉的子类型?

X

List<Integer> 是 List<? super Integer〉的子类型?

X



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你是我的天晴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值