保存对象有很多种方式,数组在其中有什么特别之处呢?数组和其它容器之间的区别主要有两方面:效率和类型。第一,效率:在Java中,数组是一种效率最高的存储和随机访问对象引用序列的方式。**数组就是一个简单的线性序列。但当创建一个数组对象(将数组看作对象),数组的大小就被固定了,并且这个数组的生命周期也是不可改变的。通常是创建一个特定大小的数组,在空间不足的时候再创建一个新的数组,然后把旧数组中的所有引用移到新数组中。在C++中,容器类vector的确知道自己保存的对象是何类型,不过它有一个缺点:C++中vector的操作符[]不做边界检查,所以可能会越界操作。而在Java中,无论使用数组或容器,都有边界检查。如果越界操作就会得到一个RuntimeException异常。RuntimeException异常通常说明是程序员的错误,因此你不必自己做越界检查。多说一句,为了速度,C++的vector不会对每次存取做边界检查;而Java的数组和容器会因为时刻存在的边界检查带来固定的性能开销。第二,类型:只有数组拥有保存基本类型的能力。List、Set和Map容器不以具体的类型来处理对象。还句话说,它们将所有对象都按Object类型处理。从某个角度来说,这种方式很好:你只需要构建一个容器,任意的Java对象都可以放入其中。数组可以放入容器,作为使用Java基本包装类的常量,或者作为包装在你自己的类中的可变值。这正是数组比通用容器优越的第二点:当你创建一个数组时,它只能保存特定类型(数组可以保存基本类型,容器则不能)。这意味着会在编译时做类型检查,以防将错误的类型插入数组,或取出数据时弄错类型。当然,无论在编译时还是运行时,Java都会阻止你向对象发送不恰当的消息。所以,并不是说哪种方法更不安全,只是如果编译时就能够指出错误,那么程序可以运行得更快,也减少了程序的使用者被异常吓着的可能性。考虑到效率与类型检查,应该尽可能使用数组。然而,如果要解决更一般化的问题,数组就可能受到过多的限制。
数组是第一级对象无论使用哪种类型的数组,数组标识符其实只是一个引用,指向在堆中创建的一个真实对象,这个(数组)对象用以保存指向其他对象的引用。只读成员length是数组对象的一部分,事实上这是数组中唯一一个可以访问的字段和方法;“[]”语法是访问数组对象唯一的方式。下例说明初始化数组的各种方式,以及如何给指向数组的引用赋值,使之指向另一个数组对象。此例也说明,**对象数组和基本类型数组在使用上几乎是相同的;唯一的区别就是对象数组保存的是引用,基本类型数组直接保存基本类型的值。
/**
* Title:
* Description:
* Company: Augmentum Inc
* Copyright: 2008 (c) Thinking in Java
* @author: Forest He
* @version: 1.0
*/
package com.augmentum.foresthe;
class Weeble {} // A samll mythical creature
public class ArraySize {
public static void main(String[] args) {
/**
* Arrays of objects
*/
Weeble[] a;
Weeble[] b = new Weeble[5];
Weeble[] c = new Weeble[4];
Weeble[] d = { new Weeble(), new Weeble(), new Weeble() };
a = new Weeble[] { new Weeble(), new Weeble() };
for (int i = 0; i < c.length; i++)
if (c[i] == null) c[i] = new Weeble();
System.out.println("a.length = " + a.length);
System.out.println("b.length = " + b.length);
System.out.println("c.length = " + c.length);
System.out.println("d.length = " + d.length);
a = d;
System.out.println("a.length = " + a.length);
/**
* Arrays of primitives
*/
int[] e;
int[] f = new int[5];
int[] g = new int[4];
int[] h = { 11, 47, 93 };
System.out.println("f.length = " + f.length);
System.out.println("g.length = " + g.length);
System.out.println("h.length = " + h.length);
e = h;
System.out.println("e.length = " + e.length);
e = new int[] { 1, 2 };
System.out.println("e.length = " + e.length);
}
}
数组a是一个尚未初始化的局部变量,在你对它正确地初始化之前,编译器不允许用此引用做任何事情。**数组b初始化为指向一个Weeble引用的数组,但其实并没有Weeble对象置入数组中。然而,仍然可以访问数组的大小,因为b指向一个合法的对象。这样做有一个小缺点:你无法知道在此数组中确切地有多少元素,因为length只表示数组能够容纳多少元素。也就是说,length是数组的大小,而不是实际保存的元素个数。新生成一个数组对象时,其中所有的引用被自动初始化为null;所以检查其中的引用是否为null,即可知道数组的某个位置是否存有对象。
数组c表明,数组对象在创建之后,随即将数组的各个位置都赋值为Weeble对象。**数组d表明使用“聚集初始化”语法创建数组对象(隐式地使用new在堆中创建,就像数组c一样),并且以Weeble对象将其初始化的过程,这些操作只用了一条语句。数组d采用的聚集初始化操作必须在定义d的位置使用,但若使用下面的语法:“动态的聚集初始化”,则可以在任意位置创建和初始化数组对象。例如,假设方法hide()需要一个Weeble对象的数组作为输入参数。可以如此调用:hide(d); 但也可以动态地创建将要作为参数传递的数组:
hide(new Weeble[] { new Weeble(), new Weeble() }); 在很多情况下,此语法使得代码书写变得更方便。
表达式:a = d; 说明如何将指向某个数组对象的引用赋给另一个数组对象,这与其他类型的对象引用没有任何区别。
ps:容器类只能保存对象的引用。而数组可以直接保存基本类型,还可以保存对象的引用。在容器中可以使用“包装”类,例如Integer、Double等,以把基本类型的值放入容器中。但是相对于基本类型,包装器类使用起来很笨拙。此外,与包装基本类型的容器相比,创建与访问一个基本类型的数组效率更高。
返回一个数组
C和C++不能返回一个数组,而只能返回指向数组的指针。但Java允许直接返回一个数组。而且返回一个数组与返回任何其他对象(实质上是返回引用)没什么区别。
/**
* Title:
* Description:
* Company: Augmentum Inc
* Copyright: 2008 (c) Thinking in Java
* @author: Forest He
* @version: 1.0
*/
package com.augmentum.foresthe;
import java.util.Random;
public class IceCream {
private static Random rand = new Random();
public static final String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin", "Praline Cream", "Mud Pie"};
/**
* 返回数组的容量为n, 由传入方法的参数决定
*/
public static String[] flavorSet(int n) {
String[] results = new String[n];
Boolean[] picked = new Boolean[flavors.length];
for (int i = 0; i < flavors.length; i++) picked[i] = false;
for (int i = 0; i < n; i++) {
int t;
/**
* 保证不产生重复的随机数
*/
do
t = rand.nextInt(flavors.length);
while(picked[t]);
results[i] = flavors[t];
picked[t] = true;
}
return results;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println("flavorSet(" + i + ") = ");
String[] f1 = flavorSet(flavors.length);
for (int j = 0; j < f1.length; j++) System.out.println("/t" + f1[j]);
}
}
}