孤尽大佬也中招:Java toArray方法的正确打开方式
共 3624字,需浏览 8分钟
·
2024-12-03 17:41
你知道的越多,不知道的就越多,业余的像一棵小草!
你关注,我们一起精进!你星标,我们便有了更多故事!
编辑:业余草
来源:juejin.cn/post/6844904034076377095
推荐:https://t.zsxq.com/le3MZ
前言
今天群里有人截图了一个资料,无意间看到是华山版《Java开发手册》独家讲解https://developer.aliyun.com/live/1201
当时的直播视频。
视频是很早之前直播的,当时Java开发手册发行到 1.5 这个版本的时候,对孤尽大佬的一个采访。视频中也有孤尽对几道易错题的讲解,有兴趣的同学可以点击上面的链接去看一看。
孤尽你不知道的话,你一定听过阿里巴巴Java开发手册(现在已经改为《Java开发手册》),孤尽就是推广创作这本手册的人。
正文
今天说的坑就来源于上文提到的采访视频,孤尽提到了关于集合的 toArray 方法,以前的使用方式是“采用传入一个与集合数量大小一致的数组”,也就是如下这种写法。
List<String> list = ....
String[] array = list.toArray(new String[list.size()]);
诶,看到这笔者心里有一波小慌,这不就是我平常用的方法吗,用了这么长时间,难道是错的???
视频中孤尽说到,有一些同学手册中的这条进行了反馈,手册也进行了修改,大概意思是“传入空数组的性能会更好”,写法如下:
List<String> list = ....
// 下面的数组内初始大小是0
String[] array = list.toArray(new String[0]);
平时写顺手的一个方法,居然不是最优的,看到这里笔者决定调查一下。
分析Java开发手册中该内容
首先既然视频中提到了对java开发手册的修改,那么就先去看看关于这个方法,手册中修改了那些内容。
-
下图为1.4版本的阿里巴巴java开发手册关于toArray的截图
-
下图为1.5版本的阿里巴巴java开发手册关于toArray的截图
首先说明了不要使用无参的toArray(),这个好理解。众所周知无参的toArray返回的是Object[],泛型丢了,强转还报错,这个没有什么争议。
但是关于传入数组的地方修改了,新版本手册的确是更新为传入的是类型完全一 致、长度为 0 的空数组。
下面也是增加了4条说明,说明了其实数组大小不一致并不会报错,但是234情况都有负面影响。
看到说明,如果同为踩过这个坑的同学可能恍然大悟,不过可能还有同学没反应过来,笔者把toArray(T[] a)的源码粘贴过来,你可能就明白了。
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a‘s runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
配合源码,再看手册更新的四条说明就非常清晰了:如果你传 0 或者小于实际大小的数字时,都是直接进了第一个if判断,copyOf一个新的数组,和你传入的大小就没有关系了。
-
假设集合大小是3,你传入的是new String[2],效果和new String[0]是一样的,却白白浪费了2个空间,对应手册中增加GC负担的说法。 -
假设集合大小是3,你传入的是new String[list.size()],在并发场景下,String[3]这个数组已经创建成功,但是list又增加了元素,效果和上一条一样,浪费了空间。
其实说到这里,笔者要说的这个坑已经很清晰了,以前的写法虽说不是bug,但是并不是最优写法。
严谨一点不是坏事,我们看看其他人怎么说。
StackOverflow中相关问题的回答
笔者也查到了一个评论很精彩的 StackOverflow 中相关问题https://stackoverflow.com/questions/174093/toarraynew-myclass0-or-toarraynew-myclassmylist-size
,我截取了采纳回答(但是不是票数最高)中的部分截图
图中的答主也贴出了测试代码(详见链接),对两种方式进行了测试,结论和最新版手册是一样的。大致意思是:
preSize是老写法,也就是传集合长度方式, resize是传长度为0的数组的方式。
两种写法在不同数据量(n)的场合下,得分越拉越大,得分(score)是运行时长,肯定是越小越好。有兴趣的同学可以去链接中,将该答主的测试用例粘贴过来试一试。
如果不了解JMH基准测试的盆友,可查阅一下相关资料。
关于Idea的提示
其实有些同学知道我们常用的ide,也就是idea中就有关于这个问题的提示文字https://github.com/JetBrains/intellij-community/blob/master/plugins/InspectionGadgets/src/inspectionDescriptions/ToArrayCallWithZeroLengthArrayArgument.html
,笔者测试的时候,idea就把它提示了出来,是一个性能的警告提示⚠️。
笔者直接翻译过来粘贴一下:
❝有两种写法可以将集合转换为数组:使用预设大小数组(例如
c.toArray(new String [c.size())])
)或使用空数组(例如c.toArray(new String [0])
)。
❝在较早的Java版本中,建议使用预设大小的数组,因为创建适当大小的数组所需的反射调用非常慢。但是,由于OpenJDK 6的更新较晚,因此此调用变得很有趣,与空数组版本相比,空数组版本的性能相同,有时甚至更好。同样,传递预大小的数组对于并发或同步收集也是危险的,因为在size和toArray调用之间可能会发生数据争用,如果在操作期间同时缩小了集合,则在数组末尾可能会导致额外的null。
❝此检查允许遵循统一的样式:使用空数组(在现代Java中建议使用)或使用预定大小的数组(在较早的Java版本或基于非HotSpot的JVM中可能更快)。
总结
经过今天的调查,以后集合转数组大家是不是都会用传入空数组的写法了呢😁
同样,再优秀的文献都有可能存在失误的情况,我们保持敬佩之心阅读的时候,又不能丢失严谨!