在开始前,我们先看一段有点“误导性”的代码,下面的代码在编译时不会产生任何异常。
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
(二)以前的泛型实现
其实在泛型引入前,泛型也是有其实现方式的,其一便是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();
}
}
但如果这样则会产生两个问题:
但引入了泛型以后,可以控制传入:
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(AnyType a){
a.echoInfo();
}
}
(三)冗长的传入
但是引入泛型也非全然没有缺点,JAVA中泛型不具有协变性,避免了数组的bug,但是这也使得继承变得稍稍复杂了些,在引入了泛型之后,一个简单的继承会变成下图这样:
从而,为了使得泛型更通用,泛型提出了通配符的概念: extends/supers Superclass>
如果一个类或者方法想传入下面的ArrayList
>
参考资料:
不变性、协变性和逆变性 https://www.cnblogs.com/Figgy/p/4575719.html
数据结构与算法分析(Java语言描述) Page 8