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

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 

来源:http://t.cn/RrhXJNL

一、问题产生

今天在学习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,两个类的很多方法实现方式也不一样。

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



如果你对 Dubbo / Netty 等等源码与原理感兴趣,欢迎加入我的知识星球一起交流。长按下方二维码噢

640?

目前在知识星球更新了《Dubbo 源码解析》目录如下:

01. 调试环境搭建
02. 项目结构一览
03. 配置 Configuration
04. 核心流程一览

05. 拓展机制 SPI

06. 线程池

07. 服务暴露 Export

08. 服务引用 Refer

09. 注册中心 Registry

10. 动态编译 Compile

11. 动态代理 Proxy

12. 服务调用 Invoke

13. 调用特性 

14. 过滤器 Filter

15. NIO 服务器

16. P2P 服务器

17. HTTP 服务器

18. 序列化 Serialization

19. 集群容错 Cluster

20. 优雅停机

21. 日志适配

22. 状态检查

23. 监控中心 Monitor

24. 管理中心 Admin

25. 运维命令 QOS

26. 链路追踪 Tracing

... 一共 69+ 篇

目前在知识星球更新了《Netty 源码解析》目录如下:

01. 调试环境搭建
02. NIO 基础
03. Netty 简介
04. 启动 Bootstrap

05. 事件轮询 EventLoop

06. 通道管道 ChannelPipeline

07. 通道 Channel

08. 字节缓冲区 ByteBuf

09. 通道处理器 ChannelHandler

10. 编解码 Codec

11. 工具类 Util

... 一共 61+ 篇


目前在知识星球更新了《数据库实体设计》目录如下:


01. 商品模块
02. 交易模块
03. 营销模块
04. 公用模块

... 一共 17+ 篇

源码不易↓↓↓

点赞支持老艿艿↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值