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

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. js简单函数封装

    //每index个字符插入一个str字符串 String.prototype.insertStrPerIndex =function(index,str){ if(this.length>ind ...

  2. C# SuperWebSocket服务端学习(二)

    首先需要下载DLL类库  地址详见:http://download.csdn.NET/detail/u011269801/9590935 1,打开VS2012,新建一个控制台应用程序,选择.NET4. ...

  3. Effective Java(二)—— 循环与 StringBuilder

    当需要为一个类编写 toString() 方法时,如果字符串操作比较简单,便可以信赖编译器,它会为你合理地构造最终的字符串结果(而不会不断创建冗余的中间变量). String mongo = &quo ...

  4. Help Me with the Game(模拟)

    http://poj.org/problem?id=2996 #include<stdio.h> #include<string.h> ][]; void find1(char ...

  5. php的self和this

    <?phpclass ss{ public static $code; public function __construct( $code ) { self::$code=$code; //这 ...

  6. layui日期输入框

    <div class="layui-form-item">                <label class="layui-form-label& ...

  7. C - Crazy Town

    Problem description Crazy Town is a plane on which there are n infinite line roads. Each road is def ...

  8. Asp.net MVC4 Step By Step(4)-使用Ajax

    Ajax技术就是利用Javascript和XML技术实现这样的效果, 可以向Web服务器发送异步请求, 返回更新部分页面的数据, 而不需要全部更新整个页面. Ajax请求两种类型的内容, 一种是服务端 ...

  9. background使用

    background-position 有两个参数,定义背景图片起始位置可选值有: center top left right bottom px % background-size 可以用 px % ...

  10. 使用curl 上传文件,multipart/form-data

    使用curl 上传文件,multipart/form-data 1. 不使用-F,curl内置multipart/form-data功能: 2. 文件内容与真实数据无关,用abc代替数据,依然可以上传 ...