》》数组为什么特殊
@@ 数组与其他种类的容器之间的区别有三方面:效率 、 类型 和 保存基本类型的能力。
@@ 在 Java 中,数组是一种效率最高的存储和随机访问对象引用序列的方式。
@@ 数组就是一个简单的线性序列,这使得元素访问非常快速。 但是为这种速度所付出的
代价是数组对象的大小被固定,并且在其生命周期中不可改变。
@@ 你可能会建议使用 ArrayList ,它可以通过创建一个新实例,然后把旧实例中所有的引用
移到新实例中,从而实现更多空间的自动分配。
尽管通常应该首选 ArrayList 而不是数组,但是这种弹性需要开销,因此,ArrayList 的
效率比数组低很多。
@@ 数组和容器都可以保证你不能滥用它们。无论你是使用数组还是容器,如果越界,都会得到一个
表示程序员错误的 RuntimeException 异常 。
@@ 数组可以持有基本类型,而泛型之前的容器则不能。
但是有了泛型,容器就可以指定并检查它们所持有对象的类型,并且有了自动包装机制,容器
看起来还能够持有基本类型。
@@ 数组之所以优于泛型之前的容器,就是因为你可以创建一个数组去持有某种基本类型。这意味着
你可以通过编译期检查,来防止插入错误类型和抽取不当类型。
@@ 数组使用 [ ] 来访问元素,而 List 使用的是 add( ) 和 get( ) 方法。
@@ 数组和 ArrayList 之间的相似性是有意设计的,这使得从概念上讲,这两者之间的切换是很
容易的。
@@ 随着自动包装机制的出现,容器已经可以与数组几乎一样方便地用于基本类型中了。数组硕果
仅存的优点就是效率。
》》数组是第一级对象
@@ 无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,
这个(数组)对象用以保存指向其他对象的引用。
@@ 可以作为数组初始化语法的一部分隐式地创建数组对象,或者用 new 表达式显式地创建。
@@ 只读成员 length 是数组对象的一部分(事实上,这是唯一一个可以访问的字段或方法),表示
此数组对象可以存储多少元素。
@@ “ [ ] ” 语法是访问数组对象唯一的方式。
@@ 对象数组和基本类型数组在使用上几乎是相同的;唯一的区别就是对象数组保存的是引用,
基本类型数组直接保存基本类型的值。
@@ length 是数组的大小,而不是实际保存的元素个数。新生成一个对象数组对象时,其中所有的
引用都被自动初始化为 null , 所以检查其中的引用是否为 null , 即可知道数组的某个对象是否
存有对象。
同样的,基本类型的数组如果是数值型的,就被自动初始化为 0 ; 如果是字符型(char)的,
就被自动初始化为 (char)0 ; 如果是布尔型的,就被自动初始化为 false 。
@@ 数组的 “ 聚集初始化 ” 创建语法
例如:
Bery [] b = { new Bery( ) , new Bery( ) , new Bery( ) } ;
》》返回一个数组
@@ 在 Java 中,你写一个方法时可以直接 “ 返回一个数组 ” ,而无需担心要为数组负责-------只要
你需要它,它就会一直存在,当你使用完后,垃圾回收器会清理掉它。
@@ 返回一个数组和返回任何其他对象(实质上是返回引用)没什么区别。
》》多维数组
@@ 对于基本类型的多维数组,可以通过使用花括号将每个向量分隔开。
例如:
int [ ] [ ] a = { { 1 , 2 , 3 } , { 4 , 5 , 6 } } ;
每对花括号括起来的集合都会把你带到下一级数组。
@@ 使用 Java SE5 的 Arrays.deepToString( ) 方法,可以将多维数组转换为多个 String 。
-------- Arrays.deepToString( ) 方法对基本类型数组和对象数组都起作用。
@@ 自动包装机制对数组初始化器也起作用。
》》数组与泛型
@@ 通常,数组与泛型不能很好地结合。你不能实例化具有参数化类型的数组:
Peel<Banana> [ ] peels = new Peel<Banana> [10]; // illlegal
擦除会移除参数类型信息,而数组必须知道它们所持有的确切类型,以强制保证类型安全。
但是,你可以参数化数组本身的类型:
class ClassParameter<T>{
public T[ ] f ( T[ ] arg ) {
return arg ;
}
}
补充:使用参数化方法而不使用参数化类的方便之处在于:你不必为需要应用的每种不同的
类型都使用一个参数去实例化这个类,并且你可以将其定义为静态的。
@@ 尽管你不能创建实际的持有泛型的数组对象,但是你可以创建非泛型的数组,然后将其
转型。如下:
public static void main(String[] args){
List<String>[] ls ; // 这条语句可以顺利地通过编译器而不报任何错误
List[ ] la = new List[10];
ls = ( List<String>[] ) la ; // 转型
ls[0] = new ArrayList<String>( ) ;
}
补充:一旦拥有了对 List<String> 的引用,你就会看到你将得到某些编译器检查。问题是
数组是协变类型的,因此 List<String> 也是一个 Object[ ] 。
@@ 一般而言,你会发现泛型在类或方法的边界处很有效,而在类或方法的内部,擦除通常
会使泛型变得不适用。
@@ 如果我创建一个 String[ ] ,Java 在编译期和运行时都会强制要求我只能将 String 对象
置于该数组中。但是,如果创建的是 Object[ ] ,那么我就可以将除基本类型之外的任何对象
置于该数组中。
》》创建测试数据
@@ 通常,在试验数组和程序时,能够很方便地生成填充了测试数据的数组,将会很有帮助。
## Arrays.fill( )
@@ Java 标准类库 Arrays 有一个十分有限的 fill( ) 方法:只能用同一个值填充各个位置,而
针对对象而言,就是复制同一个引用进行填充。
@@ 使用 Arrays.fill( ) 可以填充整个数组,只是数组里面都是同一个值。
## 数据生成器
@@ 为了以灵活的方式创建更有意义的数组,引入了 Generator 的概念。
@@ 如果某个工具使用了 Generator , 那么你就可以通过选择 Generator 的类型来创建任何
类型的数据(这是策略设计模式的一个实例--------每个不同的 Generator 都表示一个不同的
策略)。
## 从 Generator 创建数组
@@ 为了接收 Generator 并产生数组,我们需要两个转换工具。
-----第一个工具:使用任意的 Generator 来产生 Object 子类型的数组。
-----第二个工具:(为了处理基本类型)接收任意基本类型的包装器类型数组,并产生相应的
基本类型数组。
@@ 所有的 Collection 子类型都拥有 toArray( ) 方法,该方法将使用 Collection 中的元素来
填充参数数组。
@@ 泛型不能用于基本类型,而我们确实想用生成器来填充基本类型数组。为了解决这个问题,
我们创建了一个转换器,它可以接收任意的包装器对象数组,并将其转换为相应的基本类型
数组。
》》Arrays 实用功能
@@ 在 java.util 类库中可以找到 Arrays 类,它有一套用于数组的 static 实用方法,其中有
六个基本方法:
-------- equals( ) 用于比较两个数组是否相等(deepEquals( ) 用于多维数组 )
-------- fill( ) 用于向数组中填充值,但是被填充的数组中的值都一样的
-------- sort( ) 用于对数组排序
--------- binarySearch( ) 用于在已经排序的数组中查找元素
--------- toString( ) 用于产生数组的 String 表示
--------- hashCode( ) 用于产生数组的散列吗
补充:
---------- 上面的所有方法对各种基本类型和 Object 类而重载过。
---------- Arrays.asList( ) 接受任意的序列或数组作为其参数,并将其转变为 List 容器。
## 复制数组
@@ Java 标准类库提供有 static 方法 System.arraycopy( ) ,用它复制数组比用 for
循环要快很多。 System.arraycopy( ) 针对所有类型做了重载。
------- arraycopy( ) 需要的参数有:参数一:源数组
参数二: 表示从源数组中的什么位置开始复制的
偏移量
参数三:表示从目标数组的什么位置开始复制的
偏移量
参数四:需要复制的元素个数
--------- 对数组的任何越界操作都会导致异常。
--------- System.arraycopy( ) 对基本类型数组与对象数组都可以复制。然而,如果复制
对象数组,那么只是复制了对象的引用----而不是对象本身的拷贝。这被称为
浅复制。
---------System.arraycopy( ) 不会执行自动包装与自动拆包,两个数组必须具有相同的
确切类型。
## 数组的比较
@@ Arrays 类提供了重载后的 equals ( ) ,用来比较整个数组。同样,此方法针对所有
基本类型与 Object 都做了重载。数组相等的条件是元素个数必须相等,并且对应位置的
元素也相等,这可以通过对每一个元素使用 equals( ) 作比较来判断。(对应基本类型,
需要使用基本类型的包装器类的 equals( ) 方法,例如,对于 int 类型使用 Integer.equals()
作比较 )
## 数组元素的比较
@@ 排序必须根据对象的实际类型执行比较操作。
@@ 程序设计的基本目标是:将保持不变的事物与会发生改变的事物相分离。而这里,
不变的是通用的排序算法,变化的是各种对象相互比较的方式。
因此,不是将进行比较的代码编写成不同的子程序,而是使用策略设计模式。通常使
用策略,可以将 “ 会发生变化的代码 ” 封装在单独的类中(策略对象),你可以将策略对象
传递给总是相同的代码,这些代码将使用策略来完成其算法。
通常上面的策略方式,你能够用不同的对象来表示不同的比较方式,然后将它们传递给
相同的排序代码。
@@ Java 有两种方式来提供比较功能:
第一种: 实现 java.lang.Comparable 接口 ,使你的类具有 “天生” 的比较能力。此接口
很简单,只有 compareTo( ) 一个方法。此方法接受另一个 Object 为参数,
如果当前对象小于参数则返回负值,如果相等则返回零,如果当前对象大于参数
则返回正值。
第二种:继承自 Object 的 equals( ) 方法
《
补充:如果有特殊的性能的需要,那么可以实现 从Object 继承来的 equals ()
方法
》
@@ Collections 类包含一个 reverseOrder ( ) 方法,该方法可以产生一个 Comparator ,它
可以反转自然的排序顺序。
## 数组排序
@@ 使用内置的排序方法,就可以对任意的基本类型数组排序;也可以对任意的对象数组进行
排序,只要该对象实现了 Comparable 接口或具有相关联的 Comparator 。
@@ String 排序算法依据词典编排顺序,所以大写字母开头的词都放在前面输出,然后才是小
写字母开头的词。(电话薄通常是这样的。)如果想要忽略大小写字母将单词都放在一起排序,
要使用 CASE_INSENSITIVE_ORDER 。
@@ Java 标准类库中的排序算法针对正排序的特殊类型进行了优化------针对基本类型设计的
“ 快速排序 ”(Quicksort),以及针对对象设计的 “ 稳定归并排序 ” 。所以无需担心排序的性能,
除非你可以证明排序部分的确是程序效率的瓶颈。
## 在已排序的数组中查找
@@ 如果数组已经排好序了,就可以使用 Arrays.binarySearch( ) 执行快速查找。如果要对未
排序的数组使用 binarySearch( ) ,那么将产生不可预料的结果。
@@ 如果 Arrays.binarySearch( ) 找到了目标,产生的返回值等于或大于 0 。否则,它产生
负返回值。
@@ 如果想要对没有重复元素的数组排序,可以使用 TreeSet(保持排序顺序)或者
LinkedHashSet(保持插入顺序),这两个类会自动处理所有的细节。除非细节成为程序性能
的瓶颈,否则不需要自己维护数组。
@@ 如果使用 Comparator 排序了某个对象数组(基本类型数组无法使用 Comparator 进行排序),
在使用 binarySearch( ) 时必须提供同样的 Comparator (使用 binarySearch( ) 方法的重载版本)
》》总结
@@ 当你使用最近的 Java 版本编程时,应该 “ 优选容器而不是数组 ” 。只有在已证明性能成为
问题(并且切换到数组对性能提高有所帮助)时,你才应该将程序重构为使用数组。