点击上方“芋道源码”,选择“置顶公众号”
技术文章第一时间送达!
源码精品专栏
精尽 Dubbo 原理与源码 69 篇
精尽 Netty 原理与源码 61 篇
来源: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$ArrayList
的toArray()
的实现方式:
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
中将会调用Arrays
的copyOf()
方法,传入一个newType
进行类型判断,newType
的值为original.getClass()
,由于original.getClass()
返回的类型为Object[]
(具体看ArrayList
的源码可知),所以调用toArray()
之后将返回一个Object[]
类型数组,所以往listArray
变量里边丢一个Object
类型的对象当然不会报错。
再看看java.util.Arrays$ArrayList
的实现,可以看出数据类型定义为泛型E
,E
的具体类型将根据你传入的实际类型决定,由于你传入了"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
处却报错了,因为1
处fa
变量的实际类型是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 等等源码与原理感兴趣,欢迎加入我的知识星球一起交流。长按下方二维码噢:
目前在知识星球更新了《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+ 篇
源码不易↓↓↓↓↓
点赞支持老艿艿↓↓