如果你发现在一个接口使用有如下定义方法:

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,并且它也可能给类型安全和性能造成短板.

Java基础学习总结(67)——Java接口API中使用数组的缺陷的更多相关文章

  1. Java基础学习笔记八 Java基础语法之接口和多态

    接口 接口概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的”类”.接口只描述所应该具备的方法,并没有具体实现,具体的实现由接口的实现类(相当于接口的子类)来完成.这样将功能的定义 ...

  2. Java基础学习(四)-- 接口、集合框架、Collection、泛型详解

    接口 一.接口的基本概念 关键字为:Interface,在JAVA编程语言中是一个抽象类型,是抽象方法的集合.也是使用.java文件编写.   二.接口声明 命名规范:与类名的命名规范相同,通常情况下 ...

  3. Java基础学习笔记十四 常用API之基本类型包装类

    基本类型包装类 Java中有8种基本的数据类型,可是这些数据是基本数据,想对其进行复杂操作,变的很难.怎么办呢?在实际程序使用中,程序界面上用户输入的数据都是以字符串类型进行存储的.而程序开发中,我们 ...

  4. java基础学习总结一(java语言发展历史、jdk的下载安装以及配置环境变量)

    最近一段时间计划复习一下java基础知识,使用的视频课程是尚学堂高淇老师的,上课过程中的心得体会直接总结一下,方便以后复习. 一:计算机语言的发展 1:机器语言,最原始的语言,主要有“01”构成,最早 ...

  5. Java基础学习笔记二 Java基础语法

    注释 注释用来解释和说明程序的文字,注释是不会被执行的. 单行注释 //这是一条单行注释 public int i; 多行注释 /* 这是 * 一段注释, * 它跨越了多个行 */ public vo ...

  6. Java基础学习总结(50)——Java事务处理总结

    一.什么是Java事务 通常的观念认为,事务仅与数据库相关. 事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性(isol ...

  7. java基础学习03(java基础程序设计)

    java基础程序设计 一.完成的目标 1. 掌握java中的数据类型划分 2. 8种基本数据类型的使用及数据类型转换 3. 位运算.运算符.表达式 4. 判断.循环语句的使用 5. break和con ...

  8. Java基础学习笔记一 Java介绍

    java语言概述 Java是sun公司开发的一门编程语言,目前被Oracle公司收购,编程语言就是用来编写软件的. Java的应用 开发QQ.迅雷程序(桌面应用软件) 淘宝.京东(互联网应用软件) 安 ...

  9. Java基础学习笔记六 Java基础语法之类和ArrayList

    引用数据类型 引用数据类型分类,提到引用数据类型(类),其实我们对它并不陌生,如使用过的Scanner类.Random类.我们可以把类的类型为两种: 第一种,Java为我们提供好的类,如Scanner ...

随机推荐

  1. ExtJs 下拉单联动,次级下拉框查询模式

    queryMode : 'local' 如果下拉框的值是本地数据,最好设定queryMode为local,这样可以提高用户的响应速度

  2. Codeforces Round #332 (Div. 2)C. Day at the Beach 树状数组

    C. Day at the Beach   One day Squidward, Spongebob and Patrick decided to go to the beach. Unfortuna ...

  3. luogu2770 航空路线问题 网络流

    题目大意: 给定一张航空图,图中顶点代表城市,边代表 2 城市间的直通航线.现要求找出一条满足下述限制条件的且途经城市最多的旅行路线.(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然 ...

  4. map集合遍历的五种方法

    package com.jackey.topic; import java.util.ArrayList;import java.util.HashMap;import java.util.Itera ...

  5. AAC帧格式及编码介绍

    参考资料: AAC以adts格式封装的分析:http://wenku.baidu.com/view/45c755fd910ef12d2af9e74c.html aac编码介绍:http://wenku ...

  6. KNN in c++

    Pseudo Code of KNN We can implement a KNN model by following the below steps: Load the data Initiali ...

  7. PCB SQL MS 将多行有序数据转为一行数据(一列转一行)

    一.原数据:多行有序 SELECT CC.techname FROM PPEflow BB LEFT JOIN pubgyxxb CC ON BB.techno = CC.techno ORDER B ...

  8. Python 40 数据库-外键约束 、多对一与多对多的处理

    mysql提供了 foreign key,专门用于为表和表之间 建立物理关联 思考 表里存储的是一条条的记录,两个表之间能产生的关系有哪些? 现有 A B两张表 1.多对一         2.一对一 ...

  9. 剑指offer笔记

    1.在定义类的赋值描述符成员函数时,有以下几点要注意: 1)判断是否是自己赋值给自己 2)返回值是const类的引用(为了连续赋值) 3)参数是const类的引用 4)如果数据成员中有指针,注意要深拷 ...

  10. 利用set特性判断list是否存在重复的值

    List<String>  list2=new ArrayList();//存放很多值的list //根据set不能存储相同的值该特性来判断list2中的值是否重复 HashSet set ...