文章目录
数组
数组是一种数据结构, 用来存储同一类型值的集合。通过一个整型下标可以访问数组中 的每一个值。例如, 如果 a 是一个整型数组, a[i] 就是数组中下标为 i 的整数。 在声明数组变量时, 需要指出数组类型 (数据元素类型紧跟 []) 和数组变量的名字。下 面声明了整型数组 a:
int[] a;
不过, 这条语句只声明了变量 a, 并没有将 a 初始化为一个真正的数组。应该使用 new 运算 符创建数组。 int[ ] a = new int[100];
这条语句创建了一个可以存储 100 个整数的数组。数组长度不要求是常量: newint[n] 会创建 一个长度为 n 的数组。
注释: 可以使用下面两种形式声明数组
int[] a;
或
int a[]
;
大多数 Java 应用程序员喜欢使用第一种风格, 因为它将类型 int[] (整型数组)与变 量名分开了。
这个数组的下标从 0 ~ 99 (不是 1 ~ 100 )。一旦创建了数组,就可以给数组元素赋值。 例如,使用一个循环:
intp a = new int[100];
for (int i = 0; i < 100; i++)
a[i] = i; // fills the array with numbers 0 to 99
创建一个数字数组时, 所有元素都初始化为 0。boolean 数组的元素会初始化为 false。 对象数组的元素则初始化为一个特殊值 null, 这表示这些元素(还)未存放任何对象。初学者 对此可能有些不解。例如
String[] names = new String[10];
会创建一个包含 10 个字符串的数组, 所有字符串都为null。如果希望这个数组包含空 串, 可以为元素指定空串:
for (int i = 0; i < 10; i++) names[i] = " ";
警告: 如果创建了一个 100 个元素的数组, 并且试图访问元素 a[100] (或任何在 0 ~ 99 之外的下标), 程序就会引发“ array index out of bounds” 异常而终止执行。
要想获得数组中的元素个数,可以使用`array.length`。例如,
for (int i = 0; i < a.length; i++)
System.out.println(a[i]);
一旦创建了数组, 就不能再改变它的大小(尽管可以改变每一个数组元素) 0 如果经常需 要在运行过程中扩展数组的大小,就应该使用另一种数据结构— —数组列表(array list) 有关数组列表的详细内容请参看第 5 章。
3.10.1 for each 循环
Java 有一种功能很强的循环结构, 可以用来依次处理数组中的每个元素(其他类型的元 素集合亦可)而不必为指定下标值而分心。
这种增强的 for 循环的语句格式为:
for (variable : collection) statement
定义一个变量用于暂存集合中的每一个元素, 并执行相应的语句(当然,也可以是语句块)。 collection 这一集合表达式必须是一个数组或者是一个实现了 Iterable 接口的类对象(例如 ArrayList)。有关数组列表的内容将在第 5 章中讨论, 有关 Iterable 接口的内容将在第 9 章中 讨论。 例如,
for (int element : a)
System.out.println(element):
打印数组 a 的每一个元素,一个元素占一行。
这个循环应该读作“ 循环 a中的每一个元素”(for each element in a)。Java语言的设计者 认为应该使用诸如 foreach、 in 这样的关键字,但这种循环语句并不是最初就包含在 Java 语 言中的,而是后来添加进去的, 并且没有人打算废除已经包含同名(例如 System.ii ) 方法或 变量的旧代码。
当然,使用传统的 for 循环也可以获得同样的效果:
for (int i = 0; i < a.length; i++)
System,out.println(a[i]);
但是,for each 循环语句显得更加简洁、更不易出错(不必为下标的起始值和终止值而操心)。
注释:foreach 循环语句的循环变量将会遍历数组中的每个元素, 而不需要使用下标值。
如果需要处理一个集合中的所有元素, for each 循环语句对传统循环语句所进行的改进 更是叫人称赞不已。然而, 在很多场合下, 还是需要使用传统的 for 循环。例如,如果不希望遍历集合中的每个元素, 或者在循环内部需要使用下标值等。
提示:有个更加简单的方式打印数组中的所有值, 即利用 Arrays 类的 toString 方法。 调用 Arrays.toString(a), 返回一个包含数组元素的字符串,这些元素被放置在括号内, 并用逗号分隔, 例如,“[2,3,5,7,11,13]”、
要想打印数组, 可以调用
System.out.println(Arrays.toString(a));
3.10.2 数组初始化以及匿名数组
在Java中,提供了一种创建数组对象并同时赋予初始值的简化书写形式。下面是一 例子:
int[] smallPrimes = { 2, 3, 5, 7, 11, 13 };
请注意, 在使用这种语句时,不需要调用 new。
甚至还可以初始化一个匿名的数组:
new intD { 17, 19, 23, 29, 31, 37}
这种表示法将创建一个新数组并利用括号中提供的值进行初始化,数组的大小就是初始值的 个数。使用这种语法形式可以在不创建新变量的情况下重新初始化一个数组。例如:
smallPrimes = new int[] { 17, 19, 23, 29, 31, 37};
这是下列语句的简写形式:
int[] anonymous = { 17, 19, 23, 29, 31, 37 };
smallPrimes = anonymous;
注释: 在 Java 中, 允许数组长度为 0。在编写一个结果为数组的方法时, 如果碰巧结果 为空, 则这种语法形式就显得非常有用。此时可以创建一个长度为 0 的数组: new elementType[0]
注意, 数组长度为 0 与 null 不同。
3.10.3 数组拷贝
在Java中,允许将一个数组变量拷贝给 另一个数组变量。这时, 两个变量将引用同 一个数组:
int[] luckyNumbers = smallPrimes;
luckyNumbers[5] = 12; // now smallPrimes[5] is also 12
图 3-14 显示了拷贝的结果。 如果希望将 一个数组的所有值拷贝到一个新的数组中去, 就要使用 Arrays 类的 copyOf方法:
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);
第 2 个参数是新数组的长度。这个方法通常用来增加数组的大小:
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);
如果数组元素是数值型,那么多余的元素将被赋值为 0 ; 如果数组元素是布尔型,则将赋值 为 false。相反,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
C++ 注释:Java 数组与 C++ 数组在堆栈上有很大不同, 但基本上与分配在堆(heap) 上 的数组指针一样。
也就是说, int[] a = new int[100]; // Java
不同于 int a[100]; // C++
而等同于 int* a = new int[100]; // C++
Java 中的 [ ]运算符被预定义为检查数组边界,而且没有指针运算, 即不能通过 a 加 1 得到数组的下一个元素。
3.10.4 命令行参数
前面已经看到多个使用 Java 数组的示例。 每一个 Java应用程序都有一个带 String arg[]
参数的 main方法。这个参数表明 main方法将接收一个字符串数组, 也就是命令行参数 3 例如, 看一看下面这个程序:
public class Message { public static void main(String[] args) {
if (args.length = 0 11 args[0].equals("-h"))
System.out.print("Hello,");
else if (args[0].equa1s("-g"))
System.out.print("Goodbye,");
// print the other command-line arguments
for (int i = 1 ; i < args.length; i++)
System.out.print(" " + args[i]);
System•out.println("!");
}
}
如果使用下面这种形式运行这个程序:
java Message -g cruel world
args
数组将包含下列内容:
args[0]
args[l]
args[2]
"cruel"
"world"
这个程序将显示下列信息:
C++ 注释: 在 Java 应用程序的 main 方法中, 程序名并没有存储在 args 数组中 u 例如, 当使用下列命令运行程序时Goodbye, cruel world!
java Message -h world
args[0] 是“ -h”, 而不是“ Message” 或“ java”。
3.10.5 数组排序
要想对数值型数组进行排序, 可以使用 Arrays类中的 sort 方法:
int[] a = new int[10000];
...
Arrays.sort(a)
这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较 高的。Arrays 类还提供了几个使用很便捷的方法, 在稍后的 API 注释中将介绍它们。
程序清单 3-7中的程序用到了数组,它产生一个抽彩游戏中的随机数值组合。 假如抽彩 是从 49 个数值中抽取 6 个,那么程序可能的输出结果为:
Bet the following combination. It'll make you rich!
4
7
8
19
30
44
要想选择这样一个随机的数值集合,就要首先将数值 1, 2, …,n存人数组 numbers 中:
int[] numbers = new int[n];
for (inti = 0;i < numbers.length;i++)
numbers[i] = i + 1;
而用第二个数组存放抽取出来的数值:
int[] result = new int[k];
现在,就可以开始抽取 k 个数值了。Math.random方法将返回一个 0 到 1 之间(包含 0、 不包含 1 ) 的随机浮点数。用 n 乘以这个浮点数, 就可以得到从 0 到 n - 1 之间的一个随机数。
int r = (int) (Math.random0 * n);
下面将 result 的第 i 个元素设置为munbers[r] 存放的数值, 最初是 r+1。但正如所看到 的,numbers 数组的内容在每一抽取之后都会发生变化。
result[i] = numberst[r];
现在,必须确保不会再次抽取到那个数值,因为所有抽彩的数值必须不相同。因此,这 里用数组中的最后一个数值改写 mimberM,并将 n 减 1。
numbers[r] = numbers[n - 1];
n--;
关键在于每次抽取的都是下标, 而不是实际的值。下标指向包含尚未抽取过的数组元素。 在抽取了 k 个数值之后, 就可以对 result 数组进行排序了,这样可以让输出效果更加清晰:
Arrays.sort(result);
for(int r : result)
System.out.println(r);
程序清单 3-7 LotteryDrawing/LotteryDrawing.java
import java.util.*;
/**
* This program demonstrates array manipulation,
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class LotteryDrawing {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System,out.print("How many numbers do you need to draw? ");
int k = in.nextInt();
System.out.print("What is the highest number you can draw? ");
int n = in.nextInt();
// fill an array with numbers 1 2 3 . . . n
int[] numbers = new int[n];
for (int i = 0; i < numbers.length; i++)
numbers[i] = i + 1 ;
// draw k numbers and put them into a second array
int[] result = new int[k];
for (int i = 0; i < result.length; i++) {
// make a random index between 0 and n - 1
int r = (int) (Hath.random() * n);
// pick the element at the random location
result[i] = numbers[r];
// move the last element into the random location
numbers[r] = numbers[n - 1 ];
n--;
}
// print the sorted array
Arrays.sort(result);
System.out.println("Bet the following combination. It'll make you rich!");
for (int r : result)
System.out.println(r);
API java,util.Arrays 1.2
-
static String toString(type[]a) 5.0
返回包含 a 中数据元素的字符串, 这些数据元素被放在括号内, 并用逗号分隔。 参数: a 类型为 int、long、short、char、 byte、boolean、float 或 double 的数组。 -
static type copyOf(type[]a, int length)
-
static type copyOfRange(type[]a, int start, int end)
返回与 a 类型相同的一个数组, 其长度为 length 或者 end-start, 数组元素为 a 的值。
参数:a 类型为 int、 long、short、char、byte、boolean、float 或 double 的数组。
start 起始下标(包含这个值)
end 终止下标(不包含这个值)。这个值可能大于 a.length。在这种情况 下,结果为 0 或 false。
length 拷贝的数据元素长度。 如果 length 值大于 a.length, 结果为 0 或 false ; 否则, 数组中只有前面 length 个数据元素的拷W值。 參 -
static void sort(type[ ] a)
采用优化的快速排序算法对数组进行排序。
参数:a 类型为 int、long、short、char、byte、boolean、float 或 double 的数组。 -
static int binarySearch(type[] a, type v)
-
static int binarySearch(type[]a, int start, int end, type v)
采用二分搜索算法查找值 v。如果查找成功, 则返回相应的下标值; 否则, 返回一个 负数值 r。-r-1 是为保持 a 有序 v 应插入的位置。
参数:a 类型为 int、 long、short、 char、 byte、boolean、float 或 double 的有 序数组。
start 起始下标(包含这个值) 。
end 终止下标(不包含这个值)。
v 同 a 的数据元素类型相同的值。- static void fill(type[]a, type v) 将数组的所有数据元素值设置为 v。
参数:a 类型为 int、long、short、char、byte、boolean、float 或 double 的数组。
v 与 a 数据元素类型相同的一个值。 - static boolean equals(type[]a, type[]b) 如果两个数组大小相同, 并且下标相同的元素都对应相等, 返回 true。
- 参数:a、 b 类型为 int、long、short、char、byte、boolean、float 或 double 的两个数组。
- static void fill(type[]a, type v) 将数组的所有数据元素值设置为 v。
3.10.6 多维数组
多维数组将使用多个下标访问数组元素, 它适用于表示表格或更加复杂的排列形式。这 一节的内容可以先跳过, 等到需要使用这种存储机制时再返四来学习。 假设需要建立一个数值表, 用来显示在不同利率下投资 $10,000 会增长多少, 利息每年 兑现, 而且又被用于投资(见表 3-8 )。 表 3-8 不同利率下的投资增长情况
可以使用一个二维数组(也称为矩阵)存储这些信息。这个数组被命名为 balances。
在 Java中, 声明一个二维数组相当简单。例如:
double[][] balances;
与一维数组一样, 在调用 new 对多维数组进行初始化之前不能使用它。在这里可以这样 初始化:
balances = new double[NYEARS][NRATES];
另外, 如果知道数组元素, 就可以不调用 new, 而直接使用简化的书写形式对多维数组 进行初始化。例如:
int[][] magicSquare = {
{16, 3, 2, 13},
{5, 10, 11, 8},
(9, 6, 7, 12},
{4, 15, 14, 1}
};
一旦数组被初始化, 就可以利用两个方括号访问每个元素, 例如,balances[i][j]
。
在示例程序中用到了一个存储利率的一维数组 interest 与一个存储余额的二维数组 balances。一维用于表示年, 另一维用于表示利率, 最初使用初始余额来初始化这个数组的 第一行:
for (int j = 0; j < balances[0].length; j++)
balances[0][j] = 10000;
//然后, 按照下列方式计算其他行:
for (int i = 1 ; i < balances.length; i++) {
for (int j = 0; j < balances[i].length; j++) {
double oldBalance = balances[i - 1 ][j];
double interest = ...;
balances[i][j] = oldBalance + interest;
}
}
程序清单 3-8 给出了完整的程序。
注释: for each 循环语句不能自动处理二维数组的每一个元素。它是按照行, 也就是一维 教组处理的要想访问二维教组 a 的所有元素, 需要使用两个嵌套的循环, 如下所示:
for (double row : a)
for (double value : row)
dosomething with value
提示: 要想快速地打印一个二维数组的数据元素列表, 可以调用: System.out.println(Arrays.deepToString(a)); 输出格式为: [[16, B, 2, 13], [5, 10, 11, 8], [9, 6, 7, 12], [4, 15, 14, 1 ]]
程序清单 3-8 CompoundInterest/CompoundInterest.java
1 /** 2 * This program shows how to store tabular data in a 2D array. 3 * ©version 1.40 2004-02-10 4 * ©author Cay Horstmann 5 */
public class Compoundlnterest {
public static void main(String[] args) {
final double STARTRATE = 10;
final int NRATES = 6;
final int NYEARS = 10;
//set interest rates to 10 . . . 15%
doublet[] interestRate = new double[NRATES];
for (int j = 0; j < interestRate,length; j++)
interestRate[j] = (STARTRATE + j) / 100.0;
doublet[][] balances = new double[NYEARS][NRATES];
// set initial balances to 10000
for(int j = 0; j < balances[0].length; j++)
balances[0][j] = 10000;
// compute interest for future years
for (int i = 1; i < balances.length; i++){
for (int j = 0; j < balances[i].length; j++) {
// get last year's balances from previous row
double oldBalance = balances[i - 1][j];
// compute interest
double interest = oldBalance * interestRate[j];
// compute this year's balances
balances[i][j] = oldBalance + interest;
// print one row of interest rates
for (int j = 0; j < interestRate.length; j++)
System.out.printf("%9.0f%%", 100 * interestRate[][j]);
System,out.println();
// print balance table
for (doublet] row : balances) {
// print table row
for (double b : row)
System.out.printf("%10.2f", b);
System,out.println();
}
}
}
3.10.7 不规则数组
到目前为止,读者所看到的数组与其他程序设计语言中提供的数组没有多大区别。但实 际存在着一些细微的差异, 而这正是 Java 的优势所在:Java 实际上没有多维数组,只有一维 数组。多维数组被解释为“ 数组的数组。 ”
例如, 在前面的示例中, balances 数组实际上是一个包含 10 个元素的数组,而每个元素 又是一个由 6 个浮点数组成的数组(请参看图 3-15 )。
表达式
balancesf[i]
引用第 i 个子数组, 也就是二维表的第 i 行。它本身也是一个数组, balances[i][j] 引用这个数组的第j 项。
由于可以单独地存取数组的某一行, 所以可以让两行交换。
double[] temp = balances[i];
balances[i] = balances[i + 1 ];
balances[i + 1 ] = temp;
还可以方便地构造一个“ 不规则” 数组, 即数组的每一行有不同的长度。下面是一个典 型的示例。在这个示例中,创建一个数组, 第 i 行第j 列将存放“ 从 i 个数值中抽取j 个数值” 产生的结果.
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
由于 j 不可能大于i, 所以矩阵是三角形的。第i行有 i + 1 个元素(允许抽取 0 个元素, 也是一种选择)。要想创建一个不规则的数组, 首先需要分配一个具有所含行数的数组。
int[][] odds = new int[NMAX + 1 ][];
接下来, 分配这些行。
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
在分配了数组之后, 假定没有超出边界, 就可以采用通常的方式访问其中的元素了。
for (int n = 0; n < for(int n = 0; n < odds.length; n++)
for (int k = 0; k < odds[n].length; k++) {
// compute lotteryOdds
odds[n][k] = lotteryOdds;
}
程序清单 3-9 给出了完整的程序。
C++ 注释: 在 C++ 中, Java 声明
doublet][] balances = new double[10][6]; // Java
不同于 double balances[10][6]; // C++
也不同于 double (*balances)[6] = new double[10][6]; // C++
而是分配了一个包含 10 个指针的数组:
double** balances = new double*[10]; // C++
然后, 指针数组的每一个元素被填充了一个包含 6 个数字的数组:
for (i = 0; i < 10; i++)
balances[i] = new double[6];
庆幸的是, 当创建 new double[10][6] 时, 这个循环将自动地执行。 当需要不规则的数组时, 只能单独地创建行数组。
程序清单 3-9 LotteryArray/LotteryArray.java
/**
* This program demonstrates a triangular array.
* @version 1.20 2004-02-10
* @author Cay Horstmann
*/
public class LotteryArray {
public static void main(String[] args) {
final int NMAX = 10;
// allocate triangular array
int[][] odds = new int[NMAX + 1 ][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1 ];
// fill triangular array
for (int n = 0; n < odds.length; n++)
for (int k = 0; k < odds[n].length; k++) {
/*
* compute binomial coefficient n*(n-l )*(n-2)*...*(n-k+l )/(l *2*3*...*k)
*/
int lotteryOdds = 1 ;
for (int i = 1 ;i <= k; i++)
lotteryOdds = lotteryOdds * (n -i + 1 ) / i;
odds[n][k] = lotteryOdds;
}
// print triangular array
for (int[] row : odds) {
for (int odd : row)
System.out.printf("%4d", odd);
System,out.println();
}
}
}
现在, 已经看到了 Java 语言的基本程序结构, 下一章将介绍 Java 中的面向对象的程序设计。