数组的协变性与范型的不可变性

记得以前面试的时候曾被问过一个问题:数组和List的区别是什么?当时答的无非就是效率,容量固定,List不能存基本类型等等。当Java发展到了1.5之后,出现了泛型版本的List,又为这个问题的解答加入了一笔。下面就来讲一下与这个话题相关的内容。

1. 数组的协变性

数组的协变性(covariant)是指如果类Base是类Sub的基类,那么Base[]就是Sub[]的基类。而泛型是不可变的(invariant),List<Base>不会是List<Sub>的基类,更不会是它的子类。

数组的协变性可能会导致一些错误,比如下面的代码:

public static void main(String[] args) {
    Object[] array = new String[10];
    array[0] = 10;
}       

它是可以编译通过的,因为数组是协变的,Object[]类型的引用可以指向一个String[]类型的对象。但是运行的时候是会报出如下异常的:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer 试图将错误类型的对象存储到一个对象数组时抛出的异常。

原因就像呼吸一样简单。

但是对于泛型就不会出现这种情况了:

public static void main(String[] args) {
    List<Object> list = new ArrayList<String>();
    list.add(10);
}  

这段代码连编译都不能通过,编译错误 Type mismatch: cannot convert from ArrayList<String> to List<Object> 。

2. 数组的具体化

第二个要讲的问题是数组是具体化的(reified),而泛型在运行时是被擦除的(erasure)。这句话的意思是数组是在运行时才去判断数组元素的类型约束,而泛型正好相反,在运行时,泛型的类型信息是会被擦除的,只有编译的时候才会对类型进行强化。

所以上面的例子中,数组的方法会在运行时报出ArrayStoreException,而泛型根本无法通过编译。

3. 泛型与数组的结合

这个问题的标题有些误导,其实泛型与数组根本没有任何结合的可能性。List<E>[]、List<Integer>[]、或者E[]的写法都是错误的。因为这些方法无法提供类型的安全性。我们来分别举2个例子来说明它们会导致的错误。

public void testMethod1() {
    List<Integer>[] array = new List<Integer>[10];
    List<String> list = new ArrayList<String>();
    Object[] objs = array;
    objs[0] = list;
}  

这段代码假设array这个泛型数组是可以被创建的,那么它就可以被Object[]类型的变量引用,进而导致了array中的元素变成了List<String>类型了,发生了类型错误。

第二个例子是关于类型参数数组的:

public <E> void testMethod2(E e) {
    E[] array = new E[10];
    Object[] objs = array;
    objs[0] = e;
    objs[1] = "string";
    objs[2] = 10;
    objs[2] = true;
}  

与上边的那个例子相似,array可以放入任何类型的元素了,这个是很不安全的。

这里有一个例外的情况可以说一下,无限制通配符类型的泛型是可以创建泛型数组的。

List<?>[] listArray = new List<?>[10]; 
时间: 2024-11-05 17:33:16

数组的协变性与范型的不可变性的相关文章

由Cannot create a generic array of ArrayList&lt;xx&gt;引出的学习--Java范型

最近在用Java写某个程序时希望写出一个包括ArrayList<xx>的数组 自己定义如下: ArrayList<edge>[] edges = new ArrayList<edge>()[10]; 然后编译器报错如下: Cannot create a generic array of ArrayList<edge>; 在C++里可以这样比较方便的定义:  vector<edge> a[100]; 在Java里报错,查询了一下,见已经有人在sta

Swift快速入门之延迟调用与范型

defer defer 就像当于其它语言中的final代码块,它在函数中所有其它代码之后,函数返回之前运行.不论函数有没有抛出错误,它的代码都能运行.如下: var fridgeIsOpen = false let fridgeContent = ["milk", "eggs", "leftovers"] func fridgeContains(itemName: String ) -> Bool { fridgeIsOpen = true

JAVA范型-基础

JAVA范型-基础 一.泛型的概念 1.实现了参数化类型 2.用于编写可应用于多种类型的代码,即所编写的代码可应用于许多许多的类型. 3.范型容器.范型接口.范型方法都是经典的用法. 二.泛型与多态 1.多态是一种泛化机制.在使用类型说明的地方,使用多态确实具备一定的灵活性.但,多态是受限制的:只能接受基类或其子类(拘泥于单继承体系,使程序受限太多).而且,在实际编写代码时,只能使用已存在的基类或接口.同时,一旦指明了接口,程序就会要求你的代码必须使用特定接口(方法). 2.泛型:使我们能够编写

七周七语言:理解多种编程范型pdf

下载地址:网盘下载 作者简介  · · · · · · 作者简介: Bruce A. Tate RapidRed公司总裁,该公司主要为Ruby轻量级开发提供咨询.他曾任职于IBM公司,并担任过多家公司的客户解决方案总监和CTO.著作有十余本,包括荣获Jolt大奖的Better, Faster, Lighter Java. 译者简介: 戴玮 80后宅男,中科院自动化所在读博士,热爱机器学习与计算机视觉.编程喜C#.Ruby.Haskell而厌Java. 白明 Neusoft某开发部技术总监,拥有多

使用预处理命令实现C的范型编程

一.引言 Generic programming is a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. 范型编程在1973年的ML语言中首次提出,是在C语言之后诞生的,你可以轻

利用传入的Type类型来调用范型方法的解决方案

起因:自定义一个GridView控件,其数据源来源于一个通用方法Get<T>(),根据你传入的T到数据库中得到相应的数据,问题是定义GridView控件时没法在界面端设置使用泛型,只能在每个使用这个GridView控件时绑定数据.如果你没看懂这个起因也没关系,我们用一段代码来描述一下问题:我希望使用的是从外边传过来的类型tt来调用test1范型方法 class Program     {         static void Main(string[] args)         {    

分布式计算(一)基本范型

对于分布式的概念既模糊又有些理解,总是无法很好的把握,似乎与我们工作无关,但又天天在用,  究竟什么是分布式, 它能做什么?从那下手,网上可以查到很多,都使我越来越糊涂,大概是分布式计算.分布式存储.分布式缓冲等.日日梳理这些 模糊的东西,希望能有一天,能从这里走出一条光明的路. 1. 根据分布式计算抽象层次和封装层次的不同,可以将分布式的计算范型分为以下几类: 2.消息传递范型 消息传递(Message Passing) 范型利用网络API,完成将用户请求进行打包.传送和解包的工作,必要时还要

《七周七语言:理解多种编程范型》のIo课后习题答案

哎,因为上周忙着写OAuth2.0服务端框架草稿 ,耽误了一周学习Io Language了. 本篇习题解答是接着 <七周七语言:理解多种编程范型>のRuby课后习题答案 Io是原型语言,类似于JavaScript,并不区别类和对象,所有的东东都是对象,对象的数据结构就是由键值表来维护的(在Io中就是所谓的槽),通过各种消息传递给对象来实现打印输出,复制对象等功能.因为语法非常简单(也木有语法糖),所以你可以尽情构建自己的库和功能. 第一天: 1. 对1+1求值,然后对1+"one&q

java范型集合中的成员排序

范型集合中的类是JsonObject,不是自定义类,如果是自定义类就直接取要比较的字段值. 1 ArrayList<JSONObject> TList = new ArrayList<JSONObject>(); 2 3 for(int i=0;i<10000;i++) 4 { 5 JSONObject object=new JSONObject(); 6 Random rand = new Random(); 7 int randNum = rand.nextInt(200