javaSE 基础笔记之数组和枚举
第五章数组和枚举
学习目标:
² 掌握数组的声明和创建
² 掌握数组初始化
² 掌握数组元素的访问
² 掌握多维数组
² 掌握数组的复制
² 掌握基本的排序算法和数组的排序
² 理解枚举类型的基本概念
² 掌握枚举类型的基本使用
² 理解枚举类型的基本特点
一: 数组的声明和创建
1:数组是什么
数组是由相同类型的若干项数据组成的一个数据集合。 也就是说数组是用来集合相同类
型的对象并通过一个名称来引用这个集合,数组是引用类型。
2:数组的声明
你可以声明任何类型的数组──原始类型或类类型:
char s[];
Point p[]; // 这里Point是一个类
在 Java 编程语言中,即使数组是由原始类型构成,甚或带有其它类类型,数组也是一
个对象。声明不能创建对象本身,而创建的是一个引用,该引用可被用来引用数组。数组元
素使用的实际内存可由new语句或数组初始化软件动态分配。在后面,你将看到如何创建和
初始化实际数组。
上述这种将方括号置于变量名之后的声明数组的格式,是用于C、C++和 Java编程语言
的标准格式。这种格式会使声明的格式复杂难懂,因而,Java 编程语言允许一种替代的格
式,该格式中的方括号位于变量名的左边:
char[] s;
Point[] p;
这样的结果是,你可以认为类型部分在左,而变量名在右。上述两种格式并存,你可选
择一种你习惯的方式。声明不指出数组的实际大小。
注意----当数组声明的方括号在左边时,该方括号可应用于所有位于其右的变量
3:数组的创建
可以象创建对象一样,使用关键字 new 创建一个数组。创建的时候要指明数组的长度。
s = new char [20];
p = new Point [100];
第一行创建了一个 20个char值的数组, 第二行创建了一个 100个类型Point的变量。
然而,它并不创建100 个Point 对象;创建 100个对象的工作必须分别完成如下:
p[0] = new Point();
p[1] = new Point();
用来指示单个数组元素的下标必须总是从 0 开始,并保持在合法范围之内--大于 0
或等于 0 并小于数组长度。任何访问在上述界限之外的数组元素的企图都会引起运行时出
错。
数组的下标也称为数组的索引,必须是整数或者整数表达式,如下:
int i[] = new int[(9-2)*3];//这是合法的
其实,声明和创建可以定义到一行,而不用分开写。
二: 数组的初始化
当创建一个数组时,每个元素都被自动使用默认值进行初始化。在上述 char 数组 s 的
例子中,每个值都被初始化为0 (\u0000-null)字符;在数组 p的例子中, 每个值都被初始
化为 null,表明它还未引用一个 Point 对象。在经过赋值 p[0] = new Point()之后,数组
的第一个元素引用为实际Point 对象。
注意--所有变量的初始化(包括数组元素)是保证系统安全的基础, 变量绝不能在未初
始化状态使用。
Java编程语言允许使用下列形式快速创建数组,直接定义并初始化:
1 String names [] = { 2 3 “Georgianna”, 4 5 “Jen”, 6 7 “Simon”, 8 9 };
其结果与下列代码等同:
1 String names [] ; 2 3 names = new String [3]; 4 5 names [0] = “Georgianna”; 6 7 names [1] = “Jen”; 8 9 names [2] = “Simon”;
这种”速记”法可用在任何元素类型。例如:
1 Myclass array [] = { 2 3 new Myclass (), 4 5 new Myclass (), 6 7 new Myclass () 8 9 };
适当的类类型的常数值也可被使用:
1 import java.awt.Color; 2 3 Color palette [] = { 4 5 Color.blue, 6 7 Color.red, 8 9 Color.white 10 11 };
1:数组的内存分配
数组一旦被创建,在内存里面占用连续的内存地址。
数组还具有一个非常重要的特点——数组的静态性:数组一旦被创建,就不能更改数组
的长度。
比如,定义数组如下: Point[] p = new Point [3];
其中 p是数组名,数组长度是3,数组在被创建的时候,内存示意图如下:

三: 数组元素的访问
在Java编程语言中,所有数组的下标都从 0 开始。 一个数组中元素的数量被作为具有
length 属性的部分数组对象而存储; 这个值被用来检查所有运行时访问的界限。如果发生
了一个越出界限的访问,那么运行时的报错也就出现了。
使用length属性的例子如下:
1 int list [] = new int [10]; 2 3 for (int i= 0; i< list.length; i++){ 4 5 System.out.println(list[i]); 6 7 }
使用length属性使得程序的维护变得更简单。
所有元素的访问就通过数组的下标来访问,如上例的list[i],随着 i 的值发生变化,
就依次访问list[0]、list[1]、list[2]…
如果想要给某个数组元素赋值,如下方式: list[0]=5; list[1]=6;…
示例:假如定义一个数组:int c [] = new int[12];
……//进行赋值的语句
对数组进行赋值后,内存示意图如下:

然后就可以根据数组名[下标]来取值了。
如:int a = c[3];
结果就是:从数组中取出下标为3的元素的值“-123”,然后赋值给 a。
3.1:更优化的 for循环语句
在访问数组的时候,经常使用 for 循环语句。从 JDK5.0 开始,提供了一个更好的 for
循环语句的写法,示例如下:
1 public class Test { 2 3 public static void main(String args[]) { 4 5 int a[] = new int[3]; 6 7 //旧的写法,赋值 8 9 for(int i=0;i<a.length;i++){ 10 11 a[i] = i; 12 13 } 14 15 //新的写法,取值 16 17 for(int i : a){ 18 19 System.out.println(i); 20 21 } 22 23 } 24 25 }
显然JDK5.0版本的写法比以前是大大简化了。
四: 多维数组
1:多维数组的基础知识
Java 编程语言没有象其它语言那样提供多维数组。因为一个数组可被声明为具有任何
基础类型,所以你可以创建数组的数组(和数组的数组的数组,等等)。一个二维数组如下例
所示:
int twoDim [][] = new int [4][];
twoDim[0] = new int[5];
twoDim[1] = new int[5];
首次调用new而创建的对象是一个数组, 它包含4个元素, 每个元素对类型array of int
的元素都是一个null引用并且必须将数组的每个点分别初始化。
因为这种对每个元素的分别初始化,所以有可能创建非矩形数组的数组。也就是说,
twoDim的元素可按如下方式初始化:
twoDim[0] = new int [2]
twoDim[1] = new int [4];
twoDim[2] = new int [6];
twoDim[3] = new int [8];
由于此种初始化的方法烦琐乏味,而且矩形数组的数组是最通用的形式,因而产生了一
种”速记”方法来创建二维数组。例如:
int twoDim [][] = new int [3][4];
可被用来创建一个每个数组有4个整数类型的 3个数组的数组。
对二维数组 int [][] a = new int[3][4];
可以理解成为如下图所示:

注意-尽管声明的格式允许方括号在变量名左边或者右边,但此种灵活性不适用于数组句法
的其它方面。例如: new int [][4]是非法的。
2:示例
1 class FillArray 2 3 { 4 5 public static void main (String args[]) 6 7 { 8 9 int[ ][ ] matrix = new int[4][5]; //二维数组的声明和创建 10 11 for (int row=0; row < 4; row++) 12 13 { 14 15 for (int col=0; col < 5; col++) 16 17 { 18 19 matrix[row][col] = row + col; //二维数组的访问,为元素赋值 20 21 } 22 23 } 24 25 } 26 27 }
当然也可以直接定义并赋值,如下:
1 double[ ][ ] c = 2 3 { 4 5 {1.0, 2.0, 3.0, 4.0}, 6 7 {0.0, 1.0, 0.0, 0.0}, 8 9 {0.0, 0.0, 1.0, 0.0} 10 11 };
从上面可以看得很清楚,二维数组其实就是一维的一维数组。
3:多维数组的本质
N维数组就是一维的N-1维数组,比如:三维数组就是一维的二维数组。
三维以至多维数组都是一个思路,一维数组——〉二维数组——〉三维数组的实例:
1 class Fill3DArray 2 3 { 4 5 public static void main (String args[]) 6 7 { 8 9 int[ ][ ][ ] M = new int[4][5][3]; 10 11 for (int row=0; row < 4; row++) 12 13 { 14 15 for (int col=0; col < 5; col++) 16 17 { 18 19 for (int ver=0; ver < 3; ver++) 20 21 { 22 23 M[row][col][ver] = row + col + ver; 24 25 } 26 27 } 28 29 } 30 31 } 32 33 }
五: 数组的复制
数组一旦创建后,其大小不可调整。然而,你可使用相同的引用变量来引用一个全新的
数组:
int myArray [] = new int [6];
myArray = new int [10];
在这种情况下,第一个数组被有效地丢失,除非对它的其它引用保留在其它地方。
Java 编程语言在 System 类中提供了一种特殊方法拷贝数组,该方法被称作
arraycopy()。例如,araycopy可作如下使用:
//原始数组
int myArray[] = { 1, 2, 3, 4, 5, 6 };
//新的数组,比原始数组大
int hold[] = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
//把原始数组的值拷贝到新的数组
System.arraycopy(myArray, 0, hold, 0, myArray.length);
拷贝完成后,数组hold有如下内容:1,2,3,4,5,6,4,3,2,1。
注意—在处理对象数组时,System.arraycopy()拷贝的是引用,而不是对象。对象本身不改
变。
六: 数组的排序
在讨论数组排序之前,我们先来看看一些基本的排序方法:
1:冒泡排序
对几个无序的数字进行排序,比较常用的方法是冒泡排序法。冒泡法排序是一个比较简
单的排序方法,在待排序的数列基本有序的情况下排序速度较快。
基本思路:对未排序的各元素从头到尾依次比较相邻的两个元素是否逆序(与欲排顺序
相反) ,若逆序就交换这两元素,经过第一轮比较排序后便可把最大(或最小)的元素排好,
然后再用同样的方法把剩下的元素逐个进行比较,就得到了你所要的顺序。
可以看出如果有N个元素, 那么一共要进行N-1轮比较, 第 I轮要进行 N-I次比较。 (如:
有5个元素,则要进行5-1轮比较。第 3 轮则要进行5-3次比较)
示例如下:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 //需要排序的数组,目前是按照升序排列的 6 7 int a[] = new int[5]; 8 9 a[0] = 3; 10 11 a[1] = 4; 12 13 a[2] = 1; 14 15 a[3] = 5; 16 17 a[4] = 2; 18 19 //冒泡排序 20 21 for(int i=0;i<a.length;i++){ 22 23 for(int j=i+1;j<a.length;j++){//注意j的开始值是i+1,因为按照排序规则,比a[i]大的值都应该在它后面 24 25 if(a[i] > a[j]){ 26 27 int temp = a[j]; 28 29 a[j] = a[i]; 30 31 a[i] = temp; 32 33 } 34 35 } 36 37 } 38 39 //检测一下排序的结果 40 41 for(int i : a){ 42 43 System.out.println("i="+i); 44 45 } 46 47 } 48 49 }
运行结果:
i=1
i=2
i=3
i=4
i=5
如果你想要按照降序排列,很简单, 只需把: if(a[i] > a[j])改成: if(a[i] < a[j])
就可以了。
2:选择排序
基本思路:从所有元素中选择一个最小元素 a[i]放在a[0](即让最小元素 a[i]与 a[0]
交换) , 作为第一轮; 第二轮是从a[1]开始到最后的各个元素中选择一个最小元素, 放在 a[1]
中;……依次类推。n个数要进行(n-1)轮。比较的次数与冒泡法一样多,但是在每一轮
中只进行一次交换,比冒泡法的交换次数少,相对于冒泡法效率高。
示例如下:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 //需要排序的数组,目前是按照升序排列的 6 7 int a[] = new int[5]; 8 9 a[0] = 3; 10 11 a[1] = 4; 12 13 a[2] = 1; 14 15 a[3] = 5; 16 17 a[4] = 2; 18 19 20 21 //选择法排序 22 23 int temp; 24 25 for (int i = 0; i<a.length; i++) { 26 27 int lowIndex = i; 28 29 //找出最小的一个的索引 30 31 for (int j=i+1;j<a.length;j++) { 32 33 if (a[j] < a[lowIndex]) { 34 35 lowIndex = j; 36 37 } 38 39 } 40 41 //交换 42 43 temp=a[i]; 44 45 a[i]=a[lowIndex]; 46 47 a[lowIndex]=temp; 48 49 } 50 51 //检测一下排序的结果 52 53 for(int i : a){ 54 55 System.out.println("i="+i); 56 57 } 58 59 } 60 61 }
运行结果:
i=1
i=2
i=3
i=4
i=5
如果你想要按照降序排列,很简单,只需要把: if (a[j] < a[lowIndex])这句话
修改成:if (a[j] > a[lowIndex])就可以了。
3:插入法排序
基本思路:每拿到一个元素,都要将这个元素与所有它之前的元素遍历比较一遍,让符
合排序顺序的元素挨个移动到当前范围内它最应该出现的位置。
举个例子来说,就用前面的数组,我们要对一个有5个元素的数组进行升序排列,假设
第一个元素的值被假定为已排好序了,那么我们就将第2 个元素与数组中的部分进行比较,
如果第2个元素的值较小,则将它插入到第 1 个元素的前面,现在就有两个元素排好序了,
我们再将没有排序的元素与排好序的元素列表进行比较,同样,如果小于第一个元素,就将
它插入到第一个元素前面,但是,如果大于第一个元素的话,我们就将它再与第 2 个元素的
值进行比较,小于的话就排在第2个元素前面,大于的话,就排在第 2 个元素的后面。以此
类推,直到最后一个元素排好序。
示例如下:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 // 需要排序的数组,目前是按照升序排列的 6 7 int a[] = new int[5]; 8 9 a[0] = 3; 10 11 a[1] = 4; 12 13 a[2] = 1; 14 15 a[3] = 5; 16 17 a[4] = 2; 18 19 // 插入法排序 20 21 int temp; 22 23 for (int i = 1; i < a.length; i++) {// i=1开始,因为第一个元素认为 24 25 是已经排好序了的 26 27 for (int j = i; (j > 0) && (a[j] < a[j - 1]); j--) { 28 29 //交换 30 31 temp = a[j]; 32 33 a[j] = a[j - 1]; 34 35 a[j - 1] = temp; 36 37 } 38 39 } 40 41 // 检测一下排序的结果 42 43 for (int i : a) { 44 45 System.out.println("i=" + i); 46 47 } 48 49 } 50 51 }
运行结果:
i=1
i=2
i=3
i=4
i=5
如果你想要按照降序排列,很简单,只需要把: a[j] < a[j - 1]这句话修改成:a[j]
> a[j - 1]就可以了。
4:希尔(Shell)法排序
从前面介绍的冒泡排序法,选择排序法,插入排序法可以发现,如果数据已经大致排好
序的时候,其交换数据位置的动作将会减少。例如在插入排序法过程中,如果某一整数 d[i]
不是较小时, 则其往前比较和交换的次数会更少。如何用简单的方式让某些数据有一定的大
小次序呢?Donald Shell(Shell排序的创始人)提出了希尔法排序。
基本思路:先将数据按照固定的间隔分组,例如每隔4 个分成一组,然后排序各分组的
数据,形成以分组来看数据已经排序,从全部数据来看,较小值已经在前面,较大值已经在
后面。将初步处理了的分组再用插入排序来排序,那么数据交换和移动的次数会减少。可以
得到比插入排序法更高的效率。
示例如下:
1 public class Test { 2 3 public static void main(String[] args) { 4 5 // 需要排序的数组,目前是按照升序排列的 6 7 int a[] = new int[5]; 8 9 a[0] = 3; 10 11 a[1] = 4; 12 13 a[2] = 1; 14 15 a[3] = 5; 16 17 a[4] = 2; 18 19 // shell法排序 20 21 int j = 0; 22 23 int temp = 0; 24 25 //分组 26 27 for (int increment = a.length / 2; increment > 0; increment /= 2) 28 29 { 30 31 //每个组内排序 32 33 for (int i = increment; i < a.length; i++) { 34 35 temp = a[i]; 36 37 for (j = i; j >= increment; j -= increment) { 38 39 if (temp < a[j - increment]){ 40 41 a[j] = a[j - increment]; 42 43 }else{ 44 45 break; 46 47 } 48 49 } 50 51 a[j] = temp; 52 53 } 54 55 } 56 57 // 检测一下排序的结果 58 59 for (int i2 : a) { 60 61 System.out.println("i=" + i2); 62 63 } 64 65 } 66 67 }
运行结果:
i=1
i=2
i=3
i=4
i=5
如果你想要按照降序排列,很简单,只需要把:if (temp < a[j - increment])
这句话修改成:if (temp > a[j - increment])就可以了。
5:数组排序
事实上,数组的排序不用那么麻烦,上面只是想让大家对一些基本的排序算法有所了解
而已。在 java.util.Arrays 类中有一个静态方法 sort,可以用这个类的 sort 方法来对数组进行
排序。
示例如下:
public class Test {
public static void main(String[] args) {
// 需要排序的数组,目前是按照升序排列的
int a[] = new int[5];
a[0] = 3;
a[1] = 4;
a[2] = 1;
a[3] = 5;
a[4] = 2;
//数组排序
java.util.Arrays.sort(a);
// 检测一下排序的结果
for (int i2 : a) {
System.out.println("i=" + i2);
}
}
}
注意:现在的 sort 方法都是升序的,要想实现降序的,还需要 Comparator 的知识,这
个在后面会学到。
七: 枚举类型
1:枚举类型是什么
枚举类型enum是一种新的类型,在JDK5.0加入,允许用常量来表示特定的数据片断,
这些数据是分配时预先定义的值的集合,而且全部都以类型安全的形式来表示。
在枚举类型没有加入到Java 前,我们要想表达常量的集合,通常采用如下的方式:
1 public class Test { 2 3 public static final int A = 1; 4 5 public static final int B = 2; 6 7 public static final int C = 3; 8 9 public static final int D = 4; 10 11 public static final int E = 5; 12 13 }
那么我们在使用的时候就采用如下代码:
Test.A 或者 Test.B 之类的代码。
但是在这样做的时候,我们需要记住这类常量是Java中 int类型的常量,这意味着该方
法可以接受任何 int 类型的值,即使它和 Test 中定义的所有级别都不对应。因此需要检测
上界和下界,在出现无效值的时候,可能还要包含一个 IllegalArgumentException。而且,
如果后来又添加另外一个级别(例如 TEST.F,那么必须改变所有代码中的上界,才能接受
这个新值。
换句话说, 在使用这类带有整型常量的类时, 该解决方案也许可行,但并不是非常有效。
枚举就为处理上述问题提供了更好的方法。
把上面的例子改成用枚举的方式:
1 public class Test { 2 3 public enum StudentGrade{ 4 5 A,B,C,D,E,F 6 7 }; 8 9 }
可以采用如下的方式进行使用
1 public class Test { 2 3 public enum StudentGrade{ 4 5 A,B,C,D,E,F 6 7 }; 8 9 10 11 public static void main(String[] args) { 12 13 System.out.println("学生的平均成绩为=="+StudentGrade.B); 14 15 } 16 17 }
迄今为止,您所看到的示例都相当简单,但是枚举类型提供的东西远不止这些。您可以
逐个遍历枚举值,也可以在 switch 语句中使用枚举值,枚举是非常有价值的。
2:遍历枚举类型
示例如下:
1 public class Test { 2 3 public enum StudentGrade{ 4 5 A,B,C,D,E,F 6 7 }; 8 9 public static void main(String[] args) { 10 11 for(StudentGrade score : StudentGrade.values()){ 12 13 System.out.println("学生成绩取值可以为=="+score); 14 15 } 16 17 } 18 19 }
运行结果:
学生成绩取值可以为==A
学生成绩取值可以为==B
学生成绩取值可以为==C
学生成绩取值可以为==D
学生成绩取值可以为==E
学生成绩取值可以为==F
values() 方法返回了一个由独立的 StudentGrade实例构成的数组。
还有一个常用的方法:valueOf(String) :功能是以字符串的形式返回某一个具体枚举元
素的值,示例如下:
1 public class Test { 2 3 public enum StudentGrade{ 4 5 A,B,C,D,E,F 6 7 }; 8 9 public static void main(String[] args) { 10 11 Test t = new Test(); 12 13 StudentGrade score = StudentGrade.valueOf("A"); 14 15 System.out.println("你的成绩是:"+score); 16 17 } 18 19 }
运行结果:
你的成绩是:A
3:在 switch中使用枚举类型
示例如下:
1 public enum StudentGrade{ 2 3 A,B,C,D,E,F 4 5 }; 6 7 public static void main(String[] args) { 8 9 Test t = new Test(); 10 11 StudentGrade score = StudentGrade.C; 12 13 switch(score){ 14 15 case A: 16 17 System.out.println("你的成绩是优秀"); 18 19 break; 20 21 case B: 22 23 System.out.println("你的成绩是好"); 24 25 break; 26 27 case C: 28 29 System.out.println("你的成绩是良"); 30 31 break; 32 33 case D: 34 35 System.out.println("你的成绩是及格"); 36 37 break; 38 39 default: 40 41 System.out.println("你的成绩是不及格"); 42 43 break; 44 45 } 46 47 } 48 49 }
运行结果:
你的成绩是良
在这里,枚举值被传递到 switch 语句中,而每个 case 子句将处理一个特定的值。该值在
提供时没有枚举前缀,这意味着不用将代码写成 case StudentGrade.A,只需将其写成 case
A 即可,编译器不会接受有前缀的值。
4:枚举类型的特点
从上面的示例中可以看出,枚举类型大概有如下特点:
(1) :类型安全
(2) :紧凑有效的枚举数值定义
(3) :运行的高效率
4.1:类型安全
枚举的申明创建了一个新的类型。它不同于其它的已有类型,包括原始类型(整数,浮
点数等等)和当前作用域(Scope)内的其它的枚举类型。当你对方法的参数进行赋值操作
的时候,整数类型和枚举类型是不能互换的(除非是你进行显式的类型转换) ,编译器将强
制这一点。比如说,用上面申明的枚举定义这样一个方法:
public void foo(Day);
如果你用整数来调用这个函数,编译器会给出错误的。必须用这个类型的值进行调用。
foo(4); // compilation error
4.2:紧凑有效的枚举数值定义
比较前面写的例子,你看看是枚举定义写得紧凑,还是直接使用 static final紧凑呢。答
案是不言而喻的。
4.3:运行的高效率
枚举的运行效率和原始类型的整数类型基本上一样高。 在运行时不会由于使用了枚举而
导致性能有所下降。
作业
1:写一个方法,在方法内部定义一个一维的 int 数组,然后为这个数组赋上初始值,最后
再循环取值并打印出来
2:下面的数组定义那些是正确的
A: int a[][] = new int[3,3];
B: int a[3][3] = new int[][];
C: int a[][] = new int[3][3];
D: int []a[] = new int[3][3];
E: int [][]a = new int[3][3];
3:定义一个长度为10的一维字符串数组,在每一个元素存放一个单词;然后运行时从命令
行输入一个单词,程序判断数组是否包含有这个单词,包含这个单词就打印出“Yes” ,不包
含就打印出“No”
4:请在下面语句中找出一个正确的。
A. int arr1[2][3];
B. int[][] a2 = new int[2][];
C. int[][] arr2=new int [][4];
D. int arr3[][4]= new int [3][4];
5:用二重循环求出二维数组b所有元素的和:
int[][] b={{11},{21,22},{31,32,33}}
6:编写一个方法实现将班级同学的名单存放在数组中,并利用随机数(Math.random())随机
输出一位同学的姓名。
7:生成一百个随机数,放到数组中,然后排序输出。
8:统计字符串中英文字母、空格、数字和其它字符的个数。

浙公网安备 33010602011771号