数组(array)用来存储一个元素个数固定且元素类型相同的有序集。
一旦数组被创建,它的大小是固定的。使用一个数组的引用变量,通过下标来访问数组中的元素。

一、一维数组

声明数组变量

elementType[] arrayRefVar; (元素类型[] 数组引用变量;)
或
elementType arrayRefVar[]; (元素类型 数组引用变量[];)

注:推荐使用elementType[] arrayRefVar;(元素类型[] 数组引用变量;)风格。
其中elementType可以是任意数据类型,但是数组中所有元素都必须具有相同的数据类型。例如:

double[] myList;

上面代码声明的myList,它引用一个具有double型元素的数组。

创建数组

不同与基本数据类型变量,声明一个数组变量时并不在内存中给数组分配任何空间,只是创建一个对数组的引用的存储位置。如果变量不包含对数组的引用,那么这个变量的值为null。除非数组已经被创建,否则不能给它分配任何元素。
声明数组之后,可以使用new操作符创建数组,并将它的引用赋给一个变量。

arrayRefVar = new elementType[arraySize];

上述语句做了两件事:1)使用new elementType[arraySize]创建了一个数组;2)把这个数组的引用赋值给变量arrayRefVar。
当然,也可以把声明数组变量、创建数组和将数组引用赋值给变量这三个步骤合在一条语句中。

elementType[] arrayRefVar = new elementType[arraySize];
(元素类型[] 数组引用变量 = new 元素类型[数组大小];)
或
elementType arrayRefVar[] = new elementType[arraySize];
(元素类型 数组引用变量[] = new 元素类型[数组大小];)

例如:

double[] myList = new double[10];

使用如下语句给元素赋值:

arrayRefVar[index] = value;

例如:

myList[0] = 7.7;
myList[1] = 8.8;
...

注:数组变量看起来似乎是存储了整个数组,但实际上它存储的是只想数组的引用。严格来讲,一个数组变量和一个数组是不同的,但多数情况下它们之间的差异是可以忽略不计的。

数组大小和默认值

给数组分配空间时,必须指定该数组能够存储的元素个数,从而确定数组的大小,且创建数组之后就不能修改其大小。可以使用arrayRefVar.length得到数组的大小。
当数组创建后,它的元素被赋予默认值,数值型基本数据类型的默认值为0,char型默认值为’\u0000’,boolean型默认为false。

访问数组元素

数组元素是通过下标访问的,数组下标是基于0的,即其范围为从0开始arrayRefVar.length - 1结束。
数组中每个元素都可以通过以下语法表示,称为下标变量。

arrayRefVar[index];(数组引用变量[下标];)

数组初始化

Java使用数组初始化语法,将数组的声明数组、创建数组和初始化数组结合到一条语句中:

elementType[] arrayRefVar = {value0, value1, value2, ..., valuek};(元素类型[] 数组引用变量 = {值0, 值1, 值2, ..., 值k};)

例如:

double[] myList = {1.0 ,2.0, 3.0, 4.0};
等价于
double myList = new double[4];
double[0] = 1.0;
double[1] = 2.0;
double[2] = 3.0;
double[3] = 4.0;

注:数组初始化语法语句中不使用new操作符。使用数组初始化语法时,必须将声明、创建和初始化都放在一个语句中。将它们分开会产生语法错误。例如以下语句是错误的:

// error
double[] myList;
myList = {1.0 ,2.0, 3.0, 4.0};

处理数组

处理数组元素的时候,经常使用for循环,理由如下:
1)数组中所有元素都是同一类型的,可以使用循环以同样的方式反复处理这些元素;
2)由于数组的大小时已知的,所以很自然地使用for循环。

Java支持一个简便的for循环,即foreach循环,即不使用下标变量就可以顺序的遍历整个数组。具体语法为:

for (elementType element : arrayRefVar) {
    // do something
}

例如:

for (double e : myList) {
    System.out.println(e);
}

即对数组myList中的每一个元素e进行相应操作。注意,变量e必须声明为与数组中元素相同的数据类型。
但是,当需要以其他顺序遍历数组或是改变数组中的元素时,还是必须用下标变量。

数组的复制

要将一个数组中的内容赋值到另一个数组中,需要将该数组中的每个元素复制到另外一个数组中。
当需要将要复制一个数组或数组一部分时,使用赋值语句(=),即list1 = list2;并不能将list1引用的数组内容复制给list2d,而只是将list1的引用复制给了list2。执行赋值语句后,list1和list2都会指向同一个数组。
在Java中,==可以使用赋值语句复制基本数据类型,但不能复制数组。==将数组变量赋值给另一个数组变量,实际是将一个数组的引用复制给另一个变量,使得两个变量都指向相同的内存地址。

复制数组的方法有三种:
1)使用循环语句逐个复制数组中的元素;
2)使用System类中的静态方法arraycopy
3)使用clone方法复制数组。

使用循环复制:

int[] sourceArray = {1, 2, 3};
int[] targetArray = new int[sourceArray.length];
for (int i = 0; i < sourceArray.length; i++) {
    targetArray[i] = sourceArray[i];
}

使用java.lang.System类中的arraycopy方法复制数组,语法如下:

System.arraycopy(sourceArray, srcPos, targetArray, tarPos, length)

srcPos和tarPos分别表示源数组sourceArray和目标数组targetArray中的起始位置,length指定复制到元素个数。
上述循环方法可以如此改写:

System.arraycopy(sourceArray, 0, targetArray, 0, sourceArray.length)

arraycopy方法没有给目标数组分配内存空间,因此复制前必须创建目标数组并为它分配内存空间。复制完成后两个数组内容相同,但占有独立的内存空间。

数组在方法中的使用

将数组传递给方法

当一个数组传递给方法时,实际是数组的引用被传递给方法。

int[] myList = {1, 2, 3};

public static void printArray(int array) {
    for (int i = 0; i < myList.length; i++) {
        System.out.println(array[i]);
    }
}
// 调用
printArray(myList);
printArray(new int[]{1, 2, 3})

上述代码printArray(new int[]{1, 2, 3})中没有显示的应用变量,这样的数组称为匿名数组。语法为:

new elementType[]{value0, value1, ..., valuek};

Java使用按值传递的方式将实参传递给方法。但传递基本数据类型变量和传递引用类型变量有很大的不同。

  • 对于基本数据类型参数,传递的是实参的值;
  • 对于数组类型参数,参数值是数组的引用,给方法传递的是这个引用。从语义上讲,就是参数传递时信息共享,即方法中的数组和传递的数组是一样的,所以如果方法中的数组改变了,方法外的数组相应的也变化了。例如:
public class Test1 {
   public static void main(String[] args) {
       int x = 1;
       int[] y = new int[3];
       m(x, y);
       System.out.println("x is:" + x);
       System.out.println("y[0] is:" + y[0]);
   }

   private static void m(int num, int[] array) {
       num = 666;
       array[0] = 777;
   }
}

/* output
x is:1
y[0] is:777
*/

可以看到x调用m方法后还是1,但是y[0]却变成了777,因为y和array尽管是两个独立的变量,但是它们指向的是同一个数组,当调用m方法后x和y的值分别传递给了num和array,因为y是数组的引用值,所以array也是具有相同的引用值。
注意:数组在Java中是对象,JVM将对象存储在一个称为堆的内存区域中,堆用于动态内存分配。

从方法中返回数组

当一个方法中返回一个数组时,数组的引用被返回

可变长参数列表

具有相同类型的可变长度的参数可以传递给方法,并作为数组对待。声明如下:

typeName... parameterName (类型名... 参数名)

在方法声明中,指定类型后紧跟省略号(…)。只能给方法中指定一个可变长参数,同时该参数必须时最后一个参数,任何常规参数必须在它之前。
Java将可变长参数当成数组对待,可以将一个数组或是数目可变的参数传递给可变长参数。当数目可变的参数调用方法时,Java会创建一个数组并把参数传给他。

public class Test2 {
   public static void main(String[] args) {
       printArray(1, 2, 3);
       printArray(new int[]{4, 5, 6});
   }

   private static void printArray(int... numbers) {
       for (int e : numbers) {
           System.out.print(e + " ");
       }
       System.out.println();
   }
}

/* output
1 2 3 
4 5 6 
*/

数组的查找

如果一个数组排好序了,对于查找数组中的一个元素,二分查找比线性查找更加高效。

Arrays类

java.util.Arrays类中包含一些实用的方法用于常见的数组操作,比如排序和查找。

排序

可以使用sort或者parallelSort对整个或部分数组排序。如果计算机有多个处理器,那么parallelSort将更加高效。

int[] numbers = {3, 1, 2};
java.util.Arrays.sort(numbers);
java.util.Arrays.parallelSort(numbers);

char chars = {'a', 'A', '4', 'F', 'D', 'P'};
java.util.Arrays.sort(numbers, 1, 3);
java.util.Arrays.parallelSort(numbers, 1, 3);

调用sort(numbers)对整个数组排序,调用sort(numbers, 1, 3)可对chars[0] 到 chars[3 - 1]的部分排序。

查找

二分查找法在数组中查找对应关键字,数组必须提前按升序排列。如果数组中不存在关键字,方法返回-(插入点下标 + 1),个人理解“插入点下标”即为如果该关键字存在那么它应该在数组中所处位置:

int[] list = {1, 2, 3, 4, 5, 6, 7};
System.out.println(java.util.Arrays.binarySearch(list, 7)); // 6
System.out.println(java.util.Arrays.binarySearch(list, 8)); // -8

其中,8如果在list中存在,那他按顺序应该在index为7的位置,因此返回-(7 + 1),即-8。

两个数组是否相等

可以采用equals检测两个数组是否相等。

int[] list1 = {1, 2};
int[] list2 = {1, 2};
int[] list3 = {1, 3};
java.util.Arrays.equals(list1 , list2); // true
java.util.Arrays.equals(list1 , list3); // false

填充数组

使用fill方法填充整个或部分数组。

int[] list1 = {1, 2};
int[] list2 = {1, 2, 3};
java.util.Arrays.fill(list1 , 7); // 用7填充整个数组 [7, 7]
java.util.Arrays.fill(list2 , 1, 2, 8); // 用8填充部分数组,下标含前不含后 [1, 8, 3]

数组转换为字符串

使用toString犯法将数组返回为字符串。

int[] list = {1, 2};
System.out.print(java.util.Arrays.toString(list)); // [1, 2]

命令行参数

main方法可以从命令行接收字符串参数或从文件中获取参数。

java Test arg0 arg1 ... argk
或者
java Test < testParas.txt

arg0 arg1 … argk都是字符串,但是在命令行中,不需要放在双引号内,这些字符串是用空格分隔,如果字符串包含空格,那就必须使用双引号括住。
当调用main方法时,Java解释器会创建一个数组存储命令行参数,然后将数组引用传递给args,如果具有n个参数,java解释器会创建一个如下数组:

args = new String[n];

注:如果运行时灭有传递字符串参数,那么使用new String[0];创建数组,这种情况下,该数组是长度为0的空数组。args是对整个空数组的引用,因此,args不是null,但是args.length为0.

二、多维数组

二维数组

在表格或是矩阵中的数据可以表二维数组。二维数组中的元素通过行和列的下标来访问。
二维数组的本质也是个数组,只是它的每个元素都是一个数组。因此大多数原理、方法与一维数组一致。

二维数组的声明和创建

声明:

数据类型[][] 数组名;
或者
数据类型 数组名[][]; // 不推荐

创建:

matrix = new int[5][5];

注:使用new操作符创建数组时,第一个下标必须指定。语法new int[][]是错误的。

数组长度:
二维数组也通过.length的方式获取数组长度。

数组的处理:
二维数组一般用嵌套for循环来处理。

锯齿数组

锯齿数组就是二维数组中的每一行的长度各不相同。

多维数组

多维数组与二维数组大致一致。