第5章 数组、字符串和枚举 5.1 数 组 在Java语言中,数组(Array)本身是一种引用数据类型,它是一组具有相同数据类型的 数据的有序集合,该集合中的数据称为数组元素。数组元素可以由8种基本数据类型或引 用类型(对象)组成,数组中的每个元素都具有相同的数据类型。数组具备如下特点。 (1)数组元素的下标从0开始。 (2)数组元素占用连续的内存。 (3)数组一旦用new分配好内存之后,就不能更改其长度,即不能更改数组所能包含的 元素个数。 (4)可用一个统一的数组名和一个下标来唯一地确定其中的某个元素,例如,a[0]表示 数组a的第一个元素;a[1]表示数组a的第二个元素,以此类推。 5.1.1 一维数组 1.一维数组的声明 声明一维数组需要明确给出数组的名字、数组元素的数据类型,其格式可以是如下任意 一种: 数组元素类型 数组名字[]; 数组元素类型[] 数组名字; 例如: int[ ] d; String[ ] names; char c[ ]; String s[ ]; 例如,声明一个数据类型为Student类的数组: Student[] s; //等价于Student s[] 2.一维数组的实例化 声明数组仅仅是给出了数组名字和元素的数据类型,要想真正使用数组必须为它分配 内存空间,即数组实例化。在为数组实例化时必须指明数组的长度,即该数组包含的元素个 数。数组实例化使用关键词new来完成,格式如下: 数组名称=new 数组元素类型[数组长度] 139 例如: int[ ] d; //声明,不必指定数组的大小 d=new int[4]; //数组实例化,即分配内存 上面两条语句也可简化为如下一条语句: int[ ] d=new int[4]; 实例化时,数组d首先在堆内存中开辟16B的空间用于存放int数据,如图5-1(b)所 示,其中每4B构成一个单元,共4个单元,分别存储d[0]、d[1]、d[2]、d[3],这4个单元在 内存中对应的地址分别为0x7839E020、0x7839E024、0x7839E028、0x7839E02B(0x表示十 六进制)。然后在栈内存中分配一个单元用于存储第一个元素的地址,如图5-1(a)所示。因 此,数组首地址为d[0]的地址,即0x7839E020,也即数组d的地址。也就是说,数组名字d 就表示数组的首地址。 注意:由于元素类型为int,每个元素占4B,因此各元素地址之间相差为4。 图5-1 数组实例化时的内存分配 Java把内存分成两种:栈内存和堆内存。在方法中定义的基本数据类型的变量和对象 的引用变量都是在方法的栈内存中分配的,当在一段代码块中定义一个变量时,Java就在 栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量分配 的内存空间,该内存空间就可以立即被另做他用。 堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动 垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定一个特殊的变 量,让栈中的这个变量的取值等于数组或对象在堆内存的首地址,栈中的这个变量就成了数 组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对 象,引用变量就相当于是为数组或对象起的一个名称。引用变量是普通的变量,定义时在栈 中分配,引用变量在程序运行到其作用域之外后被释放,而数组和对象本身在堆中分配,即 使程序运行到使用new产生数组或者对象的语句所在的代码块之外,数组和对象本身占据 的内存不会被释放,数组和对象在没有引用变量指向它时,才变为垃圾,不能再被使用,但仍 140 然占据内存空间不放,在随后的一个不确定时间被垃圾回收器收走(释放掉)。这也是Java 比较占内存的原因。实际上,栈中的变量指向堆内存中的变量,这就是Java中的“指针”。 3.一维数组的初始化 数组实例化以后,在内存中实际存储的数据是什么呢? 在图5-1的实例中,由于数据类 型为int,因此实例化时各单元默认分配数据为0,即d[0]、d[1]、d[2]、d[3]都为0。对于8 种基本数据类型或引用类型而言,在数组实例化时,其默认值如表5-1所示。 表5-1 8种基本数据类型的数组实例化时的默认值 数组声明与实例化数组元素默认值 booleanb[]=newboolean[10]; false charc[]=newchar[10]; \' 0' (若用println输出,无显示) inti[]=newint[10]; 0 byteby[]=newbyte[10]; 0 shortsh[]=newshort[10]; 0 longl[]=newlong[10]; 0 floatf[]=newfloat[10]; 0.0 doubled[]=newdouble[10]; 0.0 引用类型null 除了默认值外,也可以在声明的同时进行初始化。 例如: int a[]={1, 2, 3, 4}; //注意没有new char[] c={'A', 'B', 'C', 'D'}; String[] str={"How", "are", "you"}; 其中,数组c初始化时的内存分配如图5-2所示。 图5-2 数组c初始化时的内存分配 141 注意:由于元素类型为char,每个元素占2B,因此各元素地址之间相差为2。 这种初始化方法仅限于8种基本数据类型和String型,若为引用类型,则需要采用new 关键词对每个对象进行实例化。 例如: Student st[]; st=new Student[3]; //或者把这两句合成一句: Student st[]=new Student[3]; st[0]=new Student(); st[1]=new Student(); st[2]=new Student(); 5.1.2 二维数组 1.二维数组的声明 二维数组的声明格式可以是如下任意一种: 数组元素类型 数组名字[][]; 数组元素类型[][] 数组名字; 例如: int[][] d; String[][] names; char c[][]; String s[][]; 例如,声明一个数据类型为Student类的数组: Student[][] s; //等价于Student s[][] 2.二维数组的实例化 二维数组的实例化仍然使用new来实现。 例如: int t[][]=new int[2][3]; 或者 int t[][]; t=new int[2][3]; 这样为二维数组t进行了实例化,分配了内存空间,它相当于定义了一个长度为2的一维数 组,而该一维数组的每个元素又是一个长度为3的一维数组。二维数组可理解成如下的表格。 t[0][0] t[0][1] t[0][2] t[1][0] t[1][1] t[1][2] 142 二维数组还可以动态实例化。 例如: int a[][]=new int[2][]; //分配两行,即两个一维数组 a[0]=new int[3]; //第一个一维数组有3 个元素 a[1]=new int[5]; //第二个一维数组有5 个元素 注意:Java语言中,二维数组被看作是数组的数组,数组空间不是连续分配的,所以不 要求二维数组每一维的大小相同。 二维数组t在实例化时,首先在堆内存中分配一维数组的空间,即t[0]、t[1],其对应的 内存地址分别为0x7839E020和0x7839E024;然后,在堆内存中开辟24B的内存空间用于 存放int数据,如图5-3(b)所示,其中每4B构成一个单元,共6个单元,分别存储t[0][0]、 t[0][1]、t[0][2]、t[1][0]、t[1][1]、t[1][2],这6个单元在内存中对应的地址分别为 0x8849F080、0x8849F084、0x8849F088、0x9458B050、0x9458B054、0x9458B058(0x表示十 六进制)(多维数组中维与维之间的内存空间分配是不连续的);其次,将t[0][0]的地址存 入t[0]单元中,即将0x8849F080存入0x7839E020表示的内存单元,将t[1][0]的地址存入 0x7839E024表示的内存单元;最后,在栈内存中分配一个单元用于存储t[0]元素的地址,如 图5-3(a)所示。因此,数组t的地址即0x7839E020。也就是说,数组名字t就表示数组的入 口地址。 图5-3 二维数组实例化时的内存分配 注意:由于元素类型为int,每个元素占4B,因此各元素地址之间相差为4。 3.二维数组的初始化 1)直接初始化 对于8种基本数据类型和String类型的数组,可进行直接初始化。 例如: int iArray[][]={{1, 2}, {3, 4}, {5, 6}}; //表示3 行2 列的二维数组,相当于一个表格 char cArray[][]={{'A', 'B'}, {'C'}, {'D', 'E', 'F'}}; String sArray[][]={{"Hello", "World"}, {"How", "Are", "You"}}; 143 在上例中,数组cArray有3行,而每一行的元素个数分别为2个、1个、3个。也就是 说,cArray由3个一维数组构成,即cArray[0]、cArray[1]、cArray[2],而这3个一维数组 的元素分别为{cArray[0][0],cArray[0][1]}、{cArray[1][0]}、{cArray[2][0],cArray[2][1], cArray[2][2]}。 2)动态初始化 对于引用类型的二维数组,必须首先为最高维分配引用空间,然后再顺次为低维分配空 间,而且必须为每个数组元素单独分配空间(否则将不能使用)。 例如: Student[][] s=new Student[2][]; //为最高维分配引用空间 s[0]=new Student[2]; //为低维分配引用空间 s[1]=new Student[2]; //为低维分配引用空间 s[0][0]=new Student(); //为每个数组元素单独分配引用空间 s[0][1]=new Student(); //为每个数组元素单独分配引用空间 s[1][0]=new Student(); //为每个数组元素单独分配引用空间 s[1][1]=new Student(); //为每个数组元素单独分配引用空间 5.1.3 数组的注意事项 (1)当通过循环遍历数组时,下标永远不要小于0,下标永远要比数组元素个数小。 (2)当数组下标出错(小于0、大于或等于数组元素个数),Java产生ArrayIndexOutOf- BoundsException异常。 (3)一旦创建数组后,不能调整数组大小,但可使用相同的引用变量来引用一个全新的 数组。例 如: int[]a=new int[6]; a=new int[10]; (4)数组是一种特殊的对象,其长度可以用“数组名.length”来引用,如c.length,对于二 维数组,如前述例子中的数组cArray,cArray.length返回最高维的长度(二维数组的行数), 值为3;cArray[2].length表示第三行低维数组的长度,值为3。 5.1.4 数组的应用 1.数组应用举例 【程序5-1】 AvgScores.java。 /* 已知3 位同学的高等数学、汇编语言以及Java 程序设计三门课程的成绩,计算每个同学的平均分 数并输出到屏幕。 */ import java.text.DecimalFormat; class AvgScores{ public static void main(String args[]){ 144 String names[]={"丁一","丁二","丁三"}; //三位同学的姓名 DecimalFormat df=new DecimalFormat("#"); //用于输出时的格式化 int[][] iScores={{89,98,87},{90,88,95},{86,92,93}}; //三门课程的成绩 int i, j; double avg; //临时变量,用于保存平均成绩 for(i=0;i