Java接口中尽量避免使用数组
作者:goldensun 发布时间:2022-07-06 23:24:14
如果你发现在一个接口使用有如下定义方法:
public String[] getParameters();
那么你应该认真反思。数组不仅仅老式,而且我们有合理的理由避免暴露它们。在这篇文章中,我将试图总结在Java API中使用数组的缺陷。首先从最出人意料的一个例子开始。
数组导致性能不佳
你可能认为使用数组是最快速的,因为数组是大多数collection实现的底层数据结构。使用一个纯数组怎么会比使用一个包含数组的对象性能更低?
让我们先从这个看起来很熟悉的普遍的习惯用法开始:
public String[] getNames() {
return namesList.toArray( new String[ namesList.size() ] );
}
这个方法从一个用来在其内部保存数据的可变集合处创建了一个数据. 它通过提供一个确切大小的数组来尝试优化数组的创建. 有趣的是,这一“优化”使得其比下面的更简单的版本速度还要慢(请看图表中绿色VS橘色条):
public String[] getNames() {
return namesList.toArray( new String[ 0 ] );
}
不过,如果方法返回的是一个List, 创建防御式的副本又更加的快了 (红条):
public List<String> getNames() {
return new ArrayList( namesList );
}
不同之处在于一个ArrayList将它的数据项放在一个Object[]数组中,并且使用的是无类型的toArray方法,其比有类型的方法要快很多(蓝条). 这是类型安全的,因为无类型的数组时封装在由编译器检查的泛型类型ArrayList<T>中的.
这个图标展示了一个在Java 7上n=5的参考标准. 不过,更多的数据项或者是另外一个VM情况系啊,这幅图片并不会改变太多. CPU的开销可能并不会太剧烈,但是会有增长. 机会有一个数组的使用者应该将其转换到一个集合中去,以便利用它做任何事情, 然后将结果转换回一个数组,来送进另外一个接口的方法中,诸如此类做法.
是用一个简单的ArrayList,而不是一个数组来提升性能,无需再动太多的手脚. ArrayList 为封装的数组增加了32字节的恒定开销. 例如,一个有十个对象的数组需要104字节,一个ArrayList 136字节.
使用 集合,你甚至可能决定返回内部列表的一个不可修改的版本:
public List<String> getNames() {
return Collections.unmodifiableList( namesList );
}
此操作会在固定的市价运行,因此他比任何上述其它的方法都要快很多(黄条). 其同一个防御式的拷贝不同。一个不可修改的集合将会在你的内部数据变化时跟着变化。如果变化发生了,客户端会在迭代数据项时运行到一个ConcurrentModificationException中. 可以认为它是一个糟糕的设计,接口提供了一个在运行时抛出一个UnsupportedOperationException. 不过,至少对于内部的使用,这个方法对于一个防御式的拷贝而言,会是一个高性能的选择 - 一些不可能使用数组实现的东西.
数组定义一个结构,而不是一个接口
Java 是一门面向对象的语言。面向对象的核心概念就是提供一些方法来访问和操作它们的数据,而不是直接对数据域进行操作. 这些方法创建一个接口来描述你可以在对象上面做的事情.
由于java已经对性能做了设计,原生类型和数组已经被融合进了类型系统之中. 对象可以使用数组来在内容高效地存储数据. 然而,即使通过数组来呈现一个可变集合的元素,它们也不会提供任何方法来访问和操作这些元素. 事实上,除了直接访问的替换元素之外,在数组上你没有多少其它事情可以做. 数组甚至连toString 和 equals 都没有一个有意义的实现, 而集合却有:
String[] array = { "foo", "bar" };
List<String> list = Arrays.asList( array );
System.out.println( list );
// -> [foo, bar]
System.out.println( array );
// -> [Ljava.lang.String;@6f548414
list.equals( Arrays.asList( "foo", "bar" ) )
// -> true
array.equals( new String[] { "foo", "bar" } )
// -> false
不同于数组,集合的 API 提供了许多有用的方法来访问元素. 用户可以检查包含的元素,提取子列表或者计算交集. 集合可以向数据层添加特定的特性, 诸如线程安全,同时将实现原理保持在内部可见.
通过使用一个数据,你定义了数据被保存在内存中的哪个地方. 通过使用一个集合,你定义了用户可以在数据上做的操作.
数组不是类型安全的
如果你依赖于编译器检查的类型安全,小心对象数组. 下面的代码会在运行时奔溃,但是编译器找不出问题所在:
Number[] numbers = new Integer[10];
numbers[0] = Long.valueOf( 0 ); // throws ArrayStoreException
原因是数组是“协变式”的, 比如,如果 T 是S 的一个子类型, 那么 T[] 就会是 S[] 的一个子类型. Joshua Bloch 在其著作 Effective Java 涵盖了所有的理论, 每一个Java开发者必读.
归因于这个行为,暴露数组类型的接口允许返回声明数组类型的一个子类型, 导致了一个怪异的运行时异常.
Bloch 同时也解释说,数组与泛型类型不兼容. 因为数组会在运行时强制要求有类型信息,而泛型则会在编译时被检查,泛型类型不能被放到数组中.
一般而言,数组和泛型不能很好的融合。如果你发现自己在融合它们而得到了一个编译时错误或者警告,那你的第一反应应该是用list去替换数组.
- Joshua Bloch, Effective Java (第二版), 第29条
总结
数组底层的语言构造、它们会被用在实现中,但是它们不应该想其它的类暴露. 在一个接口方法中使用数组违背了面向对象的原则,它会导致违和的API,并且它也可能给类型安全和性能造成短板.


猜你喜欢
- 本文实例讲述了Android开发之DialogFragment用法。分享给大家供大家参考,具体如下:背景Android 官方推荐使用 Dia
- 递归算法设计的基本思想是:对于一个复杂的问题,把原问题分解为若干个相对简单类同的子问题,继续下去直到子问题简单到能够直接求解,也就是说到了递
- 好久没有写文章了,年前公司新开了一个项目,是和usb转串口通信相关的,需求是用安卓平板通过usb转接后与好几个外设进行通信,一直忙到最近,才
- Java 线程对比Thread,Runnable,Callablejava 使用 Thread 类代表线程,所有现场对象都必须是 Threa
- mybatis resulttype 返回值异常在使用mybatis时。resulttype返回自定义的类时,可能返回的类中字段数据存在缺失
- 目录设置应用的名称,包名、应用图标和启动替换应用图标替换启动页设置访问权限配置版本发布参数生成应用签名修改配置文件打包注意事项设置应用的名称
- 本文实例讲述了C#实现简单获取及设置Session类。分享给大家供大家参考。具体分析如下:这是一个简单的C#获取Session、设置Sess
- 加坐标可以使用https://mvnrepository.com/来查找先加以下坐标:使用的数据库介绍:配置连接数据库:spring: &n
- 解析得到的代码能通过XHTML 1.0 STRICT验证;包含了标题,链接,字体,对齐,图片,引用,列表等方面的功能.&
- 一、读线圈状态/// <summary> /// 读线圈状态测试 &nbs
- 一、概括1.通常创建一个对象的方法如图:通过 Student tom = new Student(); 创建tom对象,这种创建
- 需求分析:我们在做winform开发的时候,有时候需要让程序休眠几秒钟,但是如果我们直接使用 Thread.Sleep()函数的话,页面UI
- 在Java中,线程有5中不同状态,分别是:新建(New)、就绪(Runable)、运行(Running)、阻塞(Blocked)和死亡(De
- 通过主菜单对各级子菜单进行控制,并实现添加记录,查找记录,删除记录,修改记录,排序记录,以及退出系统功能的实现。一共六部分的功能模块。 上面
- 本文实例展示了C#中this指针的用法,对于初学者进一步牢固掌握C#有很大帮助,具体内容如下:一、this指针是什么:这里有一些面向对象编程
- java弱口令检测机制1. 设计要求应具备检测口令的长度和是否在指定字符集合内的能力。应具备检测口令字符逻辑相邻的能力,如aBc,abC等。
- 如下所示: /** * 判断某个界面是否在前台 * * @param context
- 在很多时候我们在生成C#exe文件时,如果在工程里调用了dll文件时,那么如果不加以处理的话在生成的exe文件运行时需要连同这个dll一起转
- 代码从windows下visual studio到andriod平台迁移实现步骤:前言前言也是迁言,从windows的visual stud
- 本文为大家分享了Java多线程实现Runnable方式的具体方法,供大家参考,具体内容如下(一)步骤 1.定义实现Runnable