Java和许多高级语言都提供了一种称作数组的数据结构,可以用它来存储一个元素个数固定且元素类型相同的有序集。
单个的数组变量可以引用一个大的数据集合。
数组是用来存储数据的集合,通常可以把数组看作一个存储具有相同类型的变量集合。
一旦数组被创建,它的大小是固定的。使用一个数组引用变量,通过下标来访问数组中的元素。
为了在程序中使用数组,必须声明一个引用数组的变量,并指明数组的元素类型。
声明数组变量的语法:
elementType[] arrayRefVar;(元素类型[] 数组引用变量;)
elementType可以是任意数据类型,但是数组中所有的元素都必须具有相同的数据类型。
注意: 也可以用 elementType arratRefVar[] 声明数组变量。
不同于基本数据类型的声明,声明一个数组变量时并不在内存中给数组分配任何空间。它只是创建一个对数组的引用的存储位置。如果变量不包含对数组的引用,那么这个变量的值为null。除非数组已经被创建,否则不能给它分配任何元素。
声明数组变量后,可以使用下面的语法用new操作符创建数组:
arratRefVar = new elementType[arratSize];
声明一个数组变量、创建数组、然后将数组引用赋值给变量这三个步骤可以合并为一个步骤:
elementType[] arrayRefVar = new elementType[arraySize];
或
elementType arrayRefVar[] = new elementType[arraySize];
注意: 一个数组变量看起来似乎是存储了一个数组,但实际上它存储的是指向数组的引用。
当给数组分配空间时必须指定该数组能够存储的元素个数,从而确定数组大小。创建数组之后,就不能再修改它的大小。可以使用arrayRefVar.length得到数组的大小。
当创建数组后,它的元素被赋予默认值,数值型基本数据类型的默认值为0,char型的默认值为‘\u0000’,boolean型的默认值为false。
数组元素可以用过下标访问。数组下标是基于0的,即其范围从0开始到arrayRefVar.length - 1结束。
数组中的每个元素都可以使用下面的语法表示,称为下标变量:
arrayRefVar[index];
注意: 一些语言使用圆括号引用数组元素,例如arrayRefVar(9)。而Java语言使用方括号,例如arrayRefVar[9]。
创建数组后,下标变量与正常变量的使用方法相同。例如:下面代码将 myList[0] 和 myList[1] 的值相加赋给 myList[2]。
myList[2] = myList[0] + myList[1];
Java有一个简捷的标记,称作数组初始化语法,它使用下面的语法将声明数组、创建数组和初始化数组结合到一条语句中:
elementType[] arratRefVar = {value0, value1, value2, ..., valuek};
注意: 数组初始化语法中不使用操作符new。使用数组初始化语法时,必须将声明、创建和初始化数组都放在一条语句中。将它们分开会产生语法错误。因此,下面语句是错误的:
double[] myList;
myList = {1.9, 2.9, 3, 4};
处理数组时,经常会用到for循环,理由如下:
① 数组中所有元素都是同一类型的。可以使用循环以同样的方式反复处理这些元素。
②由于数组的大小是已知的,所以很自然地就使用for循环。
假设创建如下数组:
double[] myList = new double[10];
下面是一些处理数组的例子:
① 使用输入值初始化数组
Scanner input = new Scanner(System.in);
System.out.println("Enter " + myList.length + " value: ");
for (int i = 0; i < myList.length; i++)
myList[i] = input.nextDouble();
② 使用随机数初始化数组
for(int i = 0; i < myList.length; i++) {
myList[i] = Math.random() * 100;
}
③ 显示数组
for(int i = 0; i < myList.length; i++) {
System.out.prin(myList[i] + " ");
}
注意: 对于char[]类型的数组,可以使用一条打印语句打印。例如下面的代码显示Dallas:
char[] city = {'D', 'a', 'l', 'l', 'a', 's'};
System.out.println(city);
④ 对所有元素求和
double total = 0;
for(int i = 0; i < myList.length; i++) {
total += myList[i];
}
⑤ 找出最大元素
double max = myList[0];
for(int i = 0; i < myList.length; i++) {
if(myList[i] > max) max = myList[i];
}
⑥ 找出最大元素的最小下标值
double max = myList[0];
int indexOfMax = 0;
for(int i = 1; i < myList.length; i++) {
if(myList[i] > max) {
max = myList[i];
indexOfMax = i;
}
}
⑦ 随机打乱
for(int i = myList.length - 1; i > 0; i--) {
int j = (int)(Math.random() * (i + 1));
double temp = myList[i];
myList[i] = myList[j];
myList[j] = temp;
}
⑧ 移动元素
double temp = myList[0];
for(int i = 1; i < myList.length; i++) {
myList[i - 1] = myList[i];
}
myList[myList.length - 1] = temp;
⑨ 简化编码
String[] months = {"January", "February", ..., "December"};
System.out.print("Enter a month number(1 to 12): ");
int monthNumber = input.nextInt();
System.out.println("The month is " + months[monthNumber - 1]);
如果不使用 month 数组,则需要使用一个很长的多分支if-else语句来确定月份名称:
if(monthNumber == 1)
System.out.println("The month is January");
else if(monthNumber == 2)
System.out.println("The month is February");
...
else
System.out.println("The month is December");
Java支持一个简便的for循环,称为foreach循环,即不使用下标变量就可以顺序地遍历整个数组。例如:
for (double e : myList) {
System.out.println(e);
}
此代码可读作“对myList中每个元素e进行以下操作”。
注意: 变量e必须声明为与myList中元素相同的数据类型。
通常,foreach循环的语法为:
for(elementType element : arrayRefVar) {
//执行语句
}
注意: 当需要以其他顺序遍历数组或改变数组中的元素时,还是需要使用下标变量。
注意: 越界访问数组是经常出现的程序设计错误,它会抛出一个运行错误ArrayIndexOutOfBoundException。为了避免错误发生,使用时应确保所使用的下标不超过arrayRefVar.length - 1。
经常会出现错误地使用下标1引用数组的第一个元素,但实际上第一个元素的下标应该是0。这称为下标过1错误。它是循环中该使用 < 的地方误用 <= 时发生的错误。例如:
for(int i = 0; i <= list.length; i++)
System.out.println(list[i]);
应该把 <= 替换为 < 。
编写一个程序,找到大于所有项平均值的那些项。
public class AnalyzeNumbers {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("Enter the number of items: ");
int n = input.nextInt();
double[] numbers = new double[n];
double sum = 0;
//输入数字
System.out.print("Enter the number: ");
for(int i = 0; i < n; i++) {
numbers[i] = input.nextDouble();
sum += numbers[i];
}
//计算平均值
doubel average = sum / n;
//记录大于平均值的数的个数
int count = 0;
for(int i = 0; i < n; i++) {
if(number[i] > average) {
count++;
}
}
System.out.println("Average is " + average);
System.out.println("Number of elements above the average is " + count);
}
}
编写一个程序,从一副扑克中随机选出4张牌
public class DeckOfCards {
public static void main(String[] args) {
int[] deck = new int[52];
String[] suits = {"Spades", "Hearts", "Diamonds", "Clubs"};
String[] ranks = {"Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King"};
//初始化deck,为deck的元素赋值:0到51
for(int i = 0; i < deck.length; i++)
deck[i] = i;
//洗牌
for(int i = 0; i < deck.length; i++) {
int index = (int)(Math.random() * deck.length);
int temp = deck[i];
deck[i] = deck[index];
deck[index] = temp;
}
//显示前四张扑克
for(int i = 0; i < 4; i++) {
String suit = suits[deck[i] / 13];
String rank = ranks[deck[i] % 13];
System.out.println("Card number " + deck[i] + ": " + rank + " of " + suit);
}
}
}
//输出:
//Card number 6: 7 of Spades
//Card number 48: 10 of Clubs
//Card number 11: Queen of Spades
//Card number 24: Queen of Hearts
要将一个数组中的类容复制到另一个数组中,需要将数组的米格元素复制到另外一个数组中。
在程序中经常需要复制一个数组或数组的一部分。这种情况下可以选择使用赋值语句(=):
list2 = list1;
这条语句并不是把 list1 引用的数组内容复制给 list2, 而是将 list1 的引用赋值给 list2。这条语句之后,list1 和 list2 都指向同一个数组。
在Java中,赋值语句可以复制基本数据类型的变量,但不能复制数组。将一个数组变量赋值给另一个数组变量,实际上是将一个数组的引用复制给另一个变量,使两个变量都指向相同内存地址。
复制数组的三个方法:
① 使用循环语句,逐个元素进行复制操作。
② 使用 System 类中的静态方法 arraycopy。
③ 使用 clone 方法复制数组。
可以使用循环将源数组中的每个元素复制到目标数组中的对应元素。例如:
int[] sourceArray = {2, 3, 4, 5};
int[] targetArray = new int[sourceArray.length];
for(int i = 0; i < sourceArray.length; i++) {
targetArray[i] = sourceArray[i];
}
另一种方法是使用 java.lang.System 类的 arraycopy 方法复制数组,而不是使用循环。arraycopy 语法如下:
arraycopy(sourceArray, srcPos, targetArray, tarPos, length);
其中,参数 srcPos 和 tarPos 分别表示在源数组 sourceArray 和目标数组 targetArray 中的起始位置。从 sourceArray 复制到 targetArray 中的元素个数由参数 length 指定。所以,可以改写上述循环:
System.arraycopy(sourceArray, 0, targetArray, 0, sourceArray.length);
arraycopy 方法没有给目标数组分配内存空间。复制前必须创建目标数组以及为它分配内存空间。复制完成后,sourceArray 和 targetArray 具有相同内容,但占有独立的内存空间。
注意: arraycopy 方法违反了 Java 命名习惯。根据命名习惯,该方法应该命名为 arrayCopy。
当将一个数组传递给方法时,数组的引用被传递给方法。
正如给方法传递基本数据类型的值一样,可以给方法传递数组。例:
public static void printArray(int[] array) {
for(int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
可以通过传递一个数组调用上面的方法。例如:
printArray(new int[]{3, 1, 2, 6, 4, 2});
注意: 前面的语句使用下述语法创建数组:
new elementType[] {value0, value1, ..., valuek};
该数组没有显式地引用变量,这样的数组被称为匿名数组。
Java使用按值传递的方式将实参传递给方法。传递基本数据类型变量的值与传递数组值有很大的不同。
1. 对于剧本数据类型参数,传递的是实参的值
2. 对于数组类型参数,参数值是数组的引用,给方法传递的是这个引用。从语义上来讲,最好的描述就是参数传递的是共享信息,即方法中的数组和传递的数组是一样的。所以,如果改变方法中的数组,将会看到方法外的数组也发生了变化。
public class Test {
public static void main(String[] args) {
int x = 1;
int[] y = new int[10];
m[x, y];
System.out.println("x is " + x);
System.out.println("y[0] is " + y[0]);
}
public static void m(int number, int[] numbers) {
number = 1001;
numbers[0] = 5555;
}
}
//输出
//x is 1
//y[0] is 5555
注意: 数组在Java中是对象。JVM将对象存储在一个称作堆的内存区域中,堆用于动态内存分配。
当从方法中返回一个数组时,数组的引用被返回。
可以在调用方法时向方法传递一个数组。方法也可以返回一个数组。例:
public static int[] revers(int[] list) {
int[] result = new int[list.length];
for(int i = 0, j = result.length - 1; i < list.length; i++, j--) {
result[j] = list[i];
}
return result;
}
具有同样类型的可变长度的参数可以传递给方法,并将作为数组对待。可以把类型相同但个数可变的参数传递给方法。方法中的声明如下:
typeName... parameterName
在方法声明中,指定类型后紧跟着省略号(…)。只能给方法中指定一个可变长参数,同时该参数必须是最后一个参数。任何常规参数必须在它之前。
Java 将可变长参数作为数组对待。可以将一个数组或数目可变的参数传递给可变长参数。
public class VarArgsDemo {
public static void main(String[] args) {
printMax(34, 3, 3, 2, 56);
printMax(new double[]{1, 2, 3, 4});
}
public static void printMax(double... numbers) {
if(numbers.length == 0) {
System.out.println("No argument passed");
return;
}
double resule = numbers[0];
for(int i = 1; i < numbers.length; i++)
if(numbers[i] > result)
result = numbers[i];
System.out.println("The max value is " + result);
}
}
查找是在数组中寻找特定元素的过程,查找是计算机程序设计中经常要完成的任务。有很多用于查找的算法和数据结构,经常使用的有:线性查找和二分查找。
线性查找法将要查找的关键字 key 与数组中的元素逐个进行比对。这个过程持续到在列表中找到与关键字匹配的元素,或者查完列表也没有找到关键字为止。
public class LinearSeach {
public static int linearSearch(int[] list, int key) {
for(int i = 0; i < list.length; i++) {
if(key == list[i])
return i;
}
}
}
线性查找法把关键字和数组中的每个元素进行比较。数组中的元素可以按任意顺序排列。如果关键字存在,那么在找到关键字之前,这种算法必须与数组中一半的元素进行比较。由于线性查找法的执行时间随着数组元素个数的增长而线性增长,所以对于大数组而言,线性查找法的效率不高。
二分查找法是另一种常见的对数值列表的查找方法。使用二分查找法的前提条件是数组中的元素必须已经排序完成。
二分查找法首先将关键字与数组的中间元素进行比较,考虑三种情况:
① 如果关键字小于中间元素,只需要在数组的前一半元素中继续查找。
② 如果关键字和中间元素相等,则匹配成功,查找结束。
③ 如果关键字大于中间元素,只需要在数组的后一半元素中继续查找关键字。
显然,二分法在每次比较之后就排除一半的数组元素。因此,对于一个有 1024(2^10) 个元素的数组,在最坏情况下,二分法只需要比较11次,而线性查找则需要1023次。
public class BinarySearch {
public static int binarySearch(int[] list, int key) {
int low = 0;
int high = list.length - 1;
while (high >= low) {
int mid = (low + high) / 2;
if(key < list[mid])
high = mid - 1;
else if(key == list[mid])
return mid;
else
low = mid + 1;
}
return -low - 1;
}
}
注意: 线性查找法适用于在较小的数组或没有排序的数组中查找,但是对大数组而言效率不高。二分查找法的效率较高,但它要求数组已经排好序。
如同查找一样,排序是计算机编程中非常普遍的一个任务。对于排序已经开发出很多不同的算法。
假设要按升序排列一个数组。选择排序法先找到数列中最小的数,然后将它和第一个元素交换。接下来,在剩下的数中找到最小数,将它和第二个元素交换,直到数列剩下一个数为止。
public class SelectionSort {
public static void selectionSort(double[] list) {
for (int i = 0; i < list.length - 1; i++) {
double currentMin = list[i];
int currentMinIndex = i;
for(int j = i + 1; j < list.length; j++) {
if(currentMin > list[j]) {
currentMin = list[j];
currentMinIndex = j;
}
}
if(currentMinIndex != i) {
list[currentMinIndex] = list[i];
list[i] = currentMin;
}
}
}
}