加入收藏 | 设为首页 | 会员中心 | 我要投稿 东莞站长网 (https://www.0769zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

防止在 Java 接口中使用数组的 3 个理由

发布时间:2021-11-20 16:25:12 所属栏目:教程 来源:互联网
导读:如果你发现在一个接口使用有如下定义方法: public String[] getParameters(); 那么你应该认真反思。数组不仅仅老式,而且我们有合理的理由避免暴露它们。在这篇文章中,我将试图总结在Java API中使用数组的缺陷。首先从最出人意料的一个例子开始。 数组导致

如果你发现在一个接口使用有如下定义方法:
 
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>中的.
 
toArray 3 Good Reasons to Avoid Arrays in Java Interfaces
 
这个图标展示了一个在Java 7上n=5的参考标准. 不过,更多的数据项或者是另外一个VM情况系啊,这幅图片并不会改变太多. CPU的开销可能并不会太剧烈,但是会有增长. 机会有一个数组的使用者应该将其转换到一个集合中去,以便利用它做任何事情, 然后将结果转换回一个数组,来送进另外一个接口的方法中,诸如此类做法.
 
使用一个简单的数组列表,而不是可以提高性能的一个数组,无需过多的奔波。数组列表对包装数组增加了一个持续的32字节开销。例如,十个对象的一个数组,需要104个字节,一个数组列表有136字节。使用集合,你甚至可以决定返回内部列表一个不可修改的版本:
 
public list<string>getnames()
    return collections.unmodifiablelist(namelist);
}
 
在常数时间内完成这个操作,因此它比任何上述(黄色条)要快很多。这与备用副本不一样。当你的内部数据库变更时,可以改变一个不可修改的集合。如果发生这种情况,客户可以运行ConcurrentModificationException.当迭代此项目时,它可能被认为是一个在运行时,接口提供了一个抛出Unsupportoperationexception的方法的不友好界面。不过,至少会在内部使用,这个方法对于一个备用副本来说,可能是一个高性能的解决方案-使用数组,这是不可能的
 
数组定义一个结构,而不是一个接口
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,并且它也可能给类型安全和性能造成短板.

(编辑:东莞站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读