Java笔记(一) 协变性、数组与泛型

前言

在开始前,我们先看一段有点“误导性”的代码,下面的代码在编译时不会产生任何异常。

package test;

public class Test {
	private interface Shape{
	}
	private class Square implements Shape{	
	}
	private class Circle implements Shape{
	}
	public static void main(String[] args) {
		Shape[] arr=new Square[5];
		arr[0]=new Test().new Circle();
	}
}

但是如果你运行,则会发生 :

Exception in thread "main" java.lang.ArrayStoreException: test.Test$Circle
    at test.Test.main(Test.java:17)
 

协变性

发生上面情形的原因,便是Java数组中的bug,编译器在编译时,并不能预知运行时的情况,所以它能判断的,只有一个,也即“套皮”是否是对的,也就是,Shape这个皮能否套下Square这个Object,如果它能,编译也就通过了。在非数组的情况下,它也就通过了,否则我们也无法用接口来承载类了。

但如果是数组,那情况就变得复杂了起来,Java中的数组,具有协变性(covariance),也即能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型;以上面为例,Circle IS-A Shape;Square[] IS-A Shape[];Circle是能够放进Shape[]这个皮套中的,所以从编译器的角度来说,它也是能放进Square[]中的,但如果真的这么干,就会出错。这也就是数组的bug。而且使用数组的集合也是协变的:

public class Test {
	private interface Shape{
	}
	private class Square implements Shape{	
	}
	private class Circle implements Shape{
	}
	public static void main(String[] args) {
		ArrayList arraylist=new ArrayList();
		arraylist.add(new Test().new Square());
		arraylist.add(new Test().new Circle());
		Circle circle=(Circle)arraylist.get(0);//报错
	}
}

为了解决这个Bug,让问题得以在编译器阶段就被发现,Java在Java5后,引入了泛型的概念。

泛型

(一)泛型引入后的区别

引入了泛型以后,由于泛型不再具有协变性,ArrayList<Square>套皮ArrayList<Shape>将失败,上文提到的问题在编译阶段就会被发现。

(二)以前的泛型实现

其实在泛型引入前,泛型也是有其实现方式的,其一便是Object作为参数传入的方式,所有的类都继承自Object,所以我们可以这样操作。其二是用接口实现泛型。

在那之前,先引入一个东西(其实可以单开写个几万字的),对象是封装了操作的一组数据,其封装方式不同,所以当以错误的方式打开时,就会出错。我们传递一个类时,无论它是Square还是Circle,只要打开方式(强转回去的时候)正确,就不会有事。以泛型方法举例:

class Prototype{
	public void echoInfo(){
		System.out.println("this is prototype");
	}
}
class Experimental extends Prototype{
	
}
public class Test {
	
	public static void main(String[] args) {
		printItem(new Experimental ());
	}
	public static void printItem(Object a){
		Prototype EC=(Prototype)a;
		ec.echoInfo();
	}
}

但如果这样则会产生两个问题:

  • 必须知道要强转成什么,而且无法控制传入非Prototype的子类
  • 不转的话,除了寥寥几个toString等方法你调不了别的

但引入了泛型以后,可以控制传入:

class Prototype{
	public void echoInfo(){
		System.out.println("this is prototype");
	}
}
class Experimental extends Prototype{
	
}

public class Test {
	
	public static void main(String[] args) {
		printItem(new Experimental());
	}
	public static <AnyType extends Prototype> void printItem(AnyType a){
		a.echoInfo();
	}
}

(三)冗长的传入

但是引入泛型也非全然没有缺点,JAVA中泛型不具有协变性,避免了数组的bug,但是这也使得继承变得稍稍复杂了些,在引入了泛型之后,一个简单的继承会变成下图这样:

从而,为了使得泛型更通用,泛型提出了通配符的概念:<? extends/supers Superclass>

如果一个类或者方法想传入下面的ArrayList<Square>和ArrayList<Circle>,你需要这句话:

<AnyType extends Comparable<? super AnyType>>

 

参考资料:

不变性、协变性和逆变性 https://www.cnblogs.com/Figgy/p/4575719.html

数据结构与算法分析(Java语言描述) Page 8

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值