ArrayList 其实也有双胞胎,但区别还是挺大的!

一、问题产生

今天在学习ArrayList源码的时候发现了这么一句注释,即:

c.toArray might (incorrectly) not return Object[] (see 6260652)

https://bugs.java.com/view_bug.do?bug_id=6260652

这句话的意思是Collection集合类型的toArray()方法虽然声明返回值类型是Object[],但是具体调用时还真不一定就返回Onject[]类型,也有可能是其他的类型,这还要取决于你c的实际类型,使用不当还会抛出异常。这样讲可能会很懵比,下面我将会详细讲解到底为什么,现在我们先来看看Collection中的toArray()声明,让你对这个方法先有个大概的印象。

public Object[] toArray(); // 声明返回值类型为Object[]

那么什么情况会出现上面的bug呢?我们先来看看下面两个例子:

1、没有抛异常的情况

// 声明一个ArrayList集合,泛型为String类型
List<String> list = new ArrayList<>();
// 添加一个元素
list.add("list");
// 将上面的集合转换为对象数组
Object[] listArray = list.toArray(); ................ 1
// 输出listArray的类型,输出class [Ljava.lang.Object;
System.out.println(listArray.getClass());
// 往listArray赋值一个Onject类型的对象
listArray[0] = new Object();

2、抛异常的情况

// 同一创建一个列表,但是现在是通过Arrays工具类来创建,创建的列表类型为Arrays的内部类ArrayList类型
List<String> asList = Arrays.asList("string");
// 转换为对象数组
Object[] asListArray = asList.toArray();.............. 2
// 输出转换后元素类型,将输出class [Ljava.lang.String;
System.out.println(asListArray.getClass());
// 往对象数组中添加Object类型对象,会报错java.lang.ArrayStoreException
asListArray[0] = new Object();

上面第一种情况是通过new ArrayList()方式创建的java.util.ArrayList类型,第二种方式是使用Arrays.asList()方式创建的java.util.Arrays$ArrayList的类型,两个类型名都是ArrayList,但实现方式确实不同的。那为什么会报错呢?归根到底就是toArray()这个方法的实现方式不同导致的。我们分别先看下java.util.ArrayList类的toArray()java.util.Arrays$ArrayListtoArray()的实现方式:

java.util.ArrayList
public Object[] toArray() {
    // 调用Arrays工具类进行数组拷贝
    return Arrays.copyOf(elementData, size);...............1
}

Arrays.copyOf()
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());.................2
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    // 在创建新数组对象之前会先对传入的数据类型进行判定
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

下面是java.util.Arrays$ArrayList的实现

private final E[] a;
@Override
public Object[] toArray() {
    return a.clone();
}

从上面可以看出,在java.util.ArrayList中将会调用ArrayscopyOf()方法,传入一个newType进行类型判断,newType的值为original.getClass(),由于original.getClass()返回的类型为Object[](具体看ArrayList的源码可知),所以调用toArray()之后将返回一个Object[]类型数组,所以往listArray变量里边丢一个Object类型的对象当然不会报错。

再看看java.util.Arrays$ArrayList的实现,可以看出数据类型定义为泛型EE的具体类型将根据你传入的实际类型决定,由于你传入了"string",所以实际类型就是String[],当然调用a.clone()之后还是一样返回String[]类型,只不过是这里做了一个向上转型,将String[]类型转为Object[]类型返回罢了,但是注意,虽然返回的引用Object[],但实际的类型还是String[],当你往一个引用类型和实际类型不匹配的对象中添加元素时,就是报错。不服?下面举个栗子:

// 数组strings为String[]类型
String[] strings = { "a", "b" };
// 向上转型为Object[]类型,那么这个objects就属于引用类型为Object[],而实际类型为String[]
Object[] objects = strings;
// 添加一个Object类型变量,就报错啦!
objects[0] = new Object();//! java.lang.ArrayStoreException

为了加深理解,我们来总结下java中的向上转型和向下转型的区别。我们都知道我们可以通过注入Father fa = new Son()的方式进行声明,仅为Father类型为Son类型的父类,即发生向上转型,向上转型在java中是自动完成的,不需要进行强制转换,不会抛出异常。向下转型分为两种情况,下面结合代码演示:

// 向上转型
Father fa = new Son();

Father fafa = new Father();

// 向下转型(不会报错)
Son son = (Son) fa;.................1

// 向下转型,报错了java.lang.ClassCastException
Son sonson = (Son) fafa;.......................2

可以发现1处不会报错,2处却报错了,因为1fa变量的实际类型是Son,引用类型为Father,向下转换取决于实际类型而不取决于引用类型,比如fafa这个变量的实际类型就是其本身Father,在java中,父类默认是不能强制转换为子类的。

二、总结

首先最重要有以下几点:

  • 1、Java中数组集合向上转型之后,不能往数组集合中添加引用类型(即父类型)的对象,而应该添加实际类型的对象,比如说`Father[] father = son[],你就不能往father中添加Father类型了,而应该是Son
  • 2、Java中向上转型是默认允许的,但是向下转型可能会抛出错误,得小心使用!
  • 3、要小心采用Arrays.asList()创建的集合类型不是java.util.ArrayList,而是java.util.Arrays$ArrayList,两个类的很多方法实现方式也不一样。

谢谢阅读,欢迎评论区交流!

原文发布于微信公众号 - 芋道源码(YunaiV)

原文发表时间:2018-09-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏精讲JAVA

Java知识点总结之Java泛型

1122
来自专栏闻道于事

Java之集合初探(一)

一、集合概述、区别 集合是一种容器,数组也是一种容器 在Java编程中,装各种各样的对象(引用类型)的叫做容器。 为什么出现集合类? 面向对象语言对事物的体现都...

2557
来自专栏Hongten

python开发_类型转换convert

E | hongtenzone@foxmail.com  B | http://www.cnblogs.com/hongten

1692
来自专栏一个会写诗的程序员的博客

第7章 集合类第7章 集合类

在 Java 类库中有一套相当完整的容器集合类来持有对象。Kotlin没有去重复造轮子(Scala则是自己实现了一套集合类框架),而是在Java 类库的基础上进...

812
来自专栏java学习

Java基础总结大全(2)

四、集合框架 1:String类:字符串(重点) (1)多个字符组成的一个序列,叫字符串。 生活中很多数据的描述都采用的是字符串的。而且我们还会对其进...

3599
来自专栏郭耀华‘s Blog

剑指offer第五天

28.数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2...

2755
来自专栏desperate633

LintCode 二进制求和题目分析代码

743
来自专栏java学习

Java每日一练(2017/8/17)

每日一句 学的到东西的事情是锻炼,学不到的是磨练。 查看以前的所有练习题目以及答案:https://mp.weixin.qq.com/mp/homepage?_...

2929
来自专栏Java技术分享圈

杨老师课堂之ArrayList集合解析

​ 在前面我们学习了数组,数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为数组的长度不可变。例如,要保存一...

733
来自专栏Bingo的深度学习杂货店

Q7 Reverse Integer

Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 1...

2964

扫码关注云+社区

领取腾讯云代金券