第4章 数组
数组是一种最简单的复合数据类型。数组是一组同类型有序数据的集合,数组中的一个数据成员称为数组元素,数组元素可以用一个统一的数组名和下标(序号)来唯一确定。根据数组下标是一个还是多个,数组分为一维数组和多维数组。
4.1 一维数组
一维数组中的各个元素排成一行,通过数组名和一个下标就能访问一维数组中的元素。
4.1.1 一维数组的定义
数组的定义包括数组声明和为数组分配空间、初始化(创建数组)等内容,必要时,还要为数组元素分配空间或初始化。
1.一维数组的声明
声明一个一维数组的一般形式为:
类型 数组名[];
或
类型[] 数组名;
其中,类型可以是Java中任意的基本数据类型或引用类型,数组名是一个合法的标识符,[]指明该变量是一个数组变量。
例如:
int ia[]; (或 int[] ia;) // 声明一个整型数组 double da[]; (或 double[] da;) // 声明一个双精度实型数组 String sa[]; (或 String[] sa;) // 声明一个字符串数组 Button btn[];(或 Button[] btn;) // 声明一个按钮数组
一个数组声明语句,可同时声明多个数组变量。此时,后一种声明格式写起来简单些。例如:
int[] a,b,c;
相当于:
int a[],b[],c[];
与其他高级语言不同,Java在数组声明时并不为数组分配存储空间,因此,在声明的[]中不能指出数组中元素的个数(数组长度),而且对于如上声明的数组是不能访问它的任何元素的,必须经过初始化、分配存储空间创建数组后,才能访问数组的元素。当仅有数组声明,而未分配存储空间时,数组变量中只是一个值为null的空引用(指针)。
2.一维数组的空间分配
为数组分配空间有两种方法:数组初始化和使用new运算符。为数组分配空间后,数组变量中存储为数组存储空间的引用地址。
(1)数组初始化
数组初始化是指在声明数组的同时指定数组元素的初始值。一维数组初始化的形式如下:
类型 数组名[] = {初值1[,初值2…]}
基本类型和字符串类型数组等可以用这种方式创建数组空间。例如:
int ia[] = {1,2,3,4,5}; double da[] = {1.1,2.2,3.3}; String sa[] = {"Java","BASIC","FORTRAN"};
从上述例子可以看到,一维数组的初始化即在前面数组声明的基础上在大括号中给出数组各元素的初值,系统将自动按照所给初值的个数计算出数组的长度并分配相应的存储空间。
(2)使用new运算符
通过使用new运算符可为数组分配存储空间和指定初值。若数组已经声明,为已声明数组分配空间的一般形式如下:
数组名 = new类型[数组长度];
其中,数组名是已声明的数组变量,类型是数组元素的类型,数组长度可以为整型常量或变量。通过运算符new按数组长度和类型为数组分配空间。
若数组未声明,则可在数组声明的同时用new运算符为数组分配空间:
类型 数组名[] = new类型[数组长度];
例如:
int a[]; a = new int[10]; // 给数组a分配10个整型数据空间 double b[] = new double[5]; // 给数组b分配5个双精度实型数据空间 String s[] = new String[2]; // 给数组s分配2个元素的引用空间
一旦数组初始化或用new分配空间以后,数组的长度即固定下来,不能变化,除非用new运算符重新分配空间。
在Java语言中,用new运算符为数组分配空间是动态的,即可根据需要随时用new为已分配空间的数组再重新分配空间。但需注意,对一个数组再次动态分配空间时,若该数组的存储空间的引用没有另外的存储,则该数组的数据将会丢失。例如:
int a[]={1,2,3}; a = new int[5]; // 为a数组重新分配空间,原a数组的值1,2,3将丢失
用new进行数组的动态空间分配时,若未指定初值,则使用各类数据的默认初值。即对数值类型,其默认初值是相应类型的0;对字符型,默认初值为\u0000;对布尔型,默认初值为false;对复合数据类型,默认初值为null。
经过上述操作,就完成了基本类型的数组和已经初始化的字符串数组的定义,接着就可以访问数组或数组元素了。但对复合类型的数组,还需要对数组元素分配空间、初始化。
3.复合类型数组元素的动态空间分配和初始化
一般情况下,复合类型的数组需要进一步对数组元素用new运算符分配空间或进行初始化。设已声明一个复合类型的数组:
类型 数组名[]; // type是一个复合数据类型
对复合数组的动态空间分配步骤如下。
(1)为数组分配每个元素的引用空间。
数组名 = new类型 [数组长度];
(2)为每个数组元素分配空间。
数组名[0] = new类型(参数表); … 数组名[数组长度-1] = new类型(参数表);
其中“参数表”用于数组元素初值的指定。
例如,下面是一个图形界面应用程序中所用按钮数组的定义:
Button btn[]; // 声明一个Button按钮类型的数组btn btn = new Button[2]; // 给数组btn分配2个元素的引用空间 btn[0] = new Button("确定"); // 为btn[0]分配空间并赋显示文本"确定" btn[1] = new Button("退出"); // 为btn[1]分配空间并赋显示文本"退出"
当然,在比较简单的情况下,上述操作可简化为:
Button btn[] = {new Button("确定"),new Button("退出")};
4.1.2 一维数组的引用
一维数组的引用分为数组的引用和数组元素的引用,大部分时候都是数组元素的引用。一维数组元素的引用方式为:
数组名[下标]
其中:下标是int类型的,也可以是byte、short、char等类型,但不允许为long类型。下标的取值从0开始,直到数组的长度减1。一维数组元素的访问与同类型的变量访问的方法相同,每一个数组元素都可以用在同类变量被使用的地方。对前面建立的数组变量ia,有5个数组元素,通过使用不同的下标来引用不同的数组元素:ia[0]、ia[1]、…、ia[4]。
Java对引用数组元素的下标要进行越界检查以保证安全性。若数组元素下标小于0、大于或等于数组长度将产生ArrayⅠndexOutOfBoundsException异常。
Java语言对于每个数组都有一个指明数组长度的属性length,它与数组的类型无关。例如,a.length指明数组a的长度。
对一维数组元素的逐个处理,一般用循环结构的程序。
【例4.1】设数组a中存有10个学生某门课程的成绩,输出这10个学生的成绩与平均成绩的差(低于平均成绩用负数表示)。
public class Score{ public static void main(String args[]){ int a[] = {90,87,67,83,88,94,76,98,95,72},sum = 0; double ave; for(int i = 0;i < a.length;i ++) sum += a[i]; ave = sum/10.0; System.out.println("Average = " + ave); for(int s:a)System.out.printf("%4d",s); // 注意for_each语句的用法 System.out.println(); for(int s:a)System.out.printf("%4d", (s-(int)ave)); }
}
程序运行结果如下:
Average = 85.0 90 87 67 83 88 94 76 98 95 72 5 2-18 -2 3 9 -9 13 10-13
在本程序中,对数组的元素逐个读取处理使用了for-each格式的循环语句。观察加注释的语句,其中,变量s应与数组的类型相同,它将按照数组a的元素的值和个数来决定取哪些值。需注意的是:在循环体中,s代表数组元素,而不是数组元素的下标。for-each语句是Java5.0版新增的语句,不仅可以应用在数组的访问中,也可以应用在多元素的其他集合类型中。
【例4.2】输入5个整数,按输入相反的顺序输出它们。
import java.util.Scanner; public class NumberInput { public static void main(String args[]){ Scanner sc = new Scanner(System.in); int i,n,a[] = new int [5]; for(i=0;i<a.length;i++){ System.out.print("请输入第 " + (i+1)+ " 个数: "); a[i] = sc.nextInt(); } for(i = a.length -1;i >= 0;i --) System.out.println("反序 " + (a.length - i) + ":" + a[i]); } }
【例4.3】将Fibonacci数列的前15个数存入一维数组a中,并顺序输出数组的后5个数。
public class FibArray{ public static void main(String args[]){ int i,x = 1,y = 1,z,a[] = new int [15]; for(i = 0;i < 15;i ++){ a[i] = x; z = x + y; x = y; y = z; } for(i = 10;i < 15;i ++) System.out.printf("%2d : %4d\n",i + 1,a[i]); } }
程序运行结果如下:
11 : 89 12 : 144 13 : 233 14 : 377 15 : 610
输出的数据是Fibonacci数列的第11至15个数。
有时候需要将不同基本类型的数据组织到一个数组中,在Java这种面向对象的语言中,实现起来是很容易的。在第2 章中已经介绍过与基本类型相应的包装类,在Java语言中,所有的类都是类Object的子类,因此可以定义类Object的数组,也就可以将不同基本类型的数据存入到一个数组。
【例4.4】类Object的数组。
class ObjectArray { public static void main(String args[]) { Object a[] = new Object[5]; a[0] = new Integer(3); a[1] = new String("张三"); a[2] = new Boolean("true"); a[3] = new Character('F'); a[4] = new Double(1345.68); System.out.println("编号 姓名 已婚 性别 工资"); for(Object s:a)System.out.print(" " + s + " "); System.out.println(); } }
程序运行结果如下:
编号 姓名 已婚 性别 工资 3 张三 true F 1345.68
当数组创建后,数组名中就存储为数组存储区的首地址(即一种引用),可以将此地址赋值给另一同类的数组,即数组的引用。
【例4.5】数组的引用。
public class RefDemo { public static void main(String[] args){ int[] a = { 1,2,3 },b; b = a; for (int s:b)System.out.print(s + " "); System.out.println(); } }
由int a[]={1,2,3},int b[];可知数组a已创建,数组变量存储为数组存储区的引用,数组b未创建,数组变量中为null值。若有b = a;则使得数组b得到数组a的存储区引用。此时,对数组a元素的访问,可用数组b来进行,即b[0],b[1],b[2]的值分别为1,2,3。若将数组a和b的赋值反过来:a = b;则将得到一个编译错误:
variable b might not have been initialized(变量b还未初始化)。
【例4.6】用递归调用打印数组元素。
public class Recursion1 { public static void main(String args[]) { int values[] = new int[5]; for(int i = 0; i < values.length; i ++) values[i] = i; printArray(5,values);
} static void printArray(int i,int a[]) { if(i == 0) return; else printArray(i -1,a); System.out.println("a[" + (i -1) + "] " + a[i -1]); } }
程序运行结果如下:
a[0] 0 a[1] 1 a[2] 2 a[3] 3 a[4] 4
在本程序中,方法printArray的参数int a[]是一数组,在方法中按照数组的使用方法来调用。