数组概述
数组实际上是一种容器,是若干具有相同数据类型的元素构成的有序数据集。数组中的元素可以是基本类型,也可以是引用类型,但是一个数组中的数据类型必须是相同的,或者可以发生自动类型转换的。当一个数组初始化完成之后,就相当于定义了多个类型相同的变量。
数组的分类
数组中的元素可以通过下标进行访问,能够通过一个下标定位到一个具体元素的数组称为 一维数组 。同理,还有二维数组和三维数组以及更高维的数组。但是由于现实世界中很难有什么模型需要使用超过三维的数组进行抽象,所以一般不需要使用三维以上的数组。
如果没有特殊说明,本文中所描述的数组都是针对一维数组的。
数组的声明和初始化
一维数组的声明
// 类型[] 数组名;
// 类型 数组名[];
int[] arrName;
int arrName[];
说明:
- 数组声明中的类型指的是数组中存储的元素的类型,而不是数组的类型。
- 无论数组中的元素是什么类型,数组本身是一种对象类型(引用类型)。
- 在声明数组时,不能在方括号内指定数组的长度,而是要在创建数组的时候指定长度。
一维数组的创建/初始化
仅仅声明数组。数组元素并未分配内存单元,因此声明数组之后要进行创建操作,为数组元素分配内存单元,否则无法访问该数组。
// 数组名 = new 类型[数组长度];
arrStr = new String[5]; // 创建一个长度为5的用来存储字符串的数组
说明:
- new 关键字右边的类型必须与数组声明时的类型一致,如果是引用类型,必须能够兼容。例如:
String[] arr; arr = new String[5]; Object[] array; array = new String[5];
- 创建数组时不能省略长度,长度的表示可以是任何值为 int 型的表达式(但是运算结果不能是负数)。如果长度是 byte|short|char 类型的值,会自动转为 int 型。
变量必须提供维度表达式或数组初始化器.Variable must provide either dimension expressions or an array initializer
- 可以在声明数组的同时创建数组,也可以先声明再创建。
String[] arr = new String[5]; // 等价于 String[] arr; arr = new String[5];
- 创建数组之后,数组中各元素的值均为其对应类型的默认值(0 或者 null)
声明数组的同时为数组各元素赋初值
// 类型[] 数组名 = {初值1, 初值2, …};
int[] arr = {1, 2, 3};
说明:
- 多个初值之间使用逗号隔开,初值的个数决定了数组的长度。花括号中可以没有任何初始值(
int[] arr = {};
),此时数组的长度为零。 - 系统先根据初值的个数创建出数组,然后将各初始值按顺序赋值给数组的各个元素。
- 各初值的类型要与声明时数组元素的类型一致。如果数据类型不一致,系统首先试着将元素类型转换为声明的类型,如果转换失败,则发生语法错误。
- 不允许先声明数组,再单独赋以初值.
int[] arr; arr = {1, 2, 3}; // 操作非法
数组的创建和在声明的同时赋初值都称为数组的初始化
- 静态初始化:初始化时由程序员显式指定每个数组元素的初始值,由系统决定数组的长度。静态初始化又有两种书写形式,成为静态标准形式和简写形式。
String[] books = new String[] {"A", "B", "C"}; String[] books = {"A", "B", "C"};
- 动态初始化:程序员指定数组的长度,由系统为数组元素分配默认的初始值。(即上文的数组的创建)
注意:
初始化数组时,不要同时使用静态初始化和动态初始化,即不要在初始化数组时既指定长度又为每一个元素分配初始值。
String[] arr = new String[5] {};
Cannot define dimension expressions when an array initializer is provided.(当提供数组初始化器时,无法定义维度表达式。)
数组初始化完成的标志是数组中的所有元素都被分配了内存并完成初始值的指定。
内存分析
从内存的角度分析,数组名实际上代表着数组对象的引用,被分配到栈中,而数组中的元素则被分配在堆中。(Java中的数组占据连续的内存单元,因此具有随机存取(Random Access)的特点)
Java数组是静态的,即初始化完成之后,程序不能再改变数组对象在内存中的位置和大小,只能改变存储元素的值。实际上,由于Java数组变量是一个引用类型的变量,所以,可以通过改变一个数组变量所指向的实际数组,造成改变数组长度的假象。
对于数组变量本身来说,它并不需要进行所谓的初始化,只要数组变量指向一个有效的数组对象,程序即可正常使用该数组变量。可以说,Java程序中的所有引用变量,他们都不需要进行所谓的初始化操作,需要进行初始化的是该引用变量所引用的对象。
数组元素的值直接存储在对应的数组元素中。程序中所有局部变量都是放在栈内存里保存的,不管是基本类型还是引用类型的变量,都是存储在各自的方法栈区中。
引用类型变量引用的对象则总是存储在堆内存中。由于Java不允许直接访问堆内存中的数据,所以引用变量在本质上等同于一个指针,只要程序通过引用变量访问属性或者调用方法,该引用变量就会由它所引用的对象代替。
一维数组的访问
取数组长度
Java中的任何数组都有一个名为length
的属性用来表示数组的长度,即数组中元素的总个数。编程者可以通过arrarName.length
的形式动态获取数组的长度。
length是final常量,由系统自动赋值一次,所以编程者不能修改该属性。
访问数组元素
arrayName[index]
- 数组下标可以是任何值为 int 型的表达式。
- 下标从 0 开始。有时候为了方便编写及阅读程序,声明数组时可以将数组长度比预定长度增加 1,并约定下标为 0 的元素不用,以统一下标与自然计数。
- Java程序在运行时,系统会检查数组元素的下标是否越界(不在0至length-1之间)。如果越界,则抛出IndexOutOfBoundsException.(对下标越界的检测是在运行期而不是编译期)
- C/C++并不检测数组下标,当访问越界下标的时候。会返回一个不确定的值。
访问数组整体
Java允许通过数组名将数组作为整体进行访问。数组名其实就是对象的引用。
二维数组
二维数组的每一个元素都是一个一维数组,声明如下:
类型[][] 数组名;
创建二维数组
数组名 = new 类型[行数][列数];
也可以在声明的同时进行创建:
类型[][] 数组名 = new 类型[行数][列数];
二维数组经常用于表示数学上由若干行和若干列构成的矩阵,行数是它的一维长度,列数是它的一维元素数组中的数组长度。
与一维数组类似,也可以在声明数组的同时为各元素指定初值。其格式如下:
类型[][] 数组名 = {{第1行初值}, {第2行初值},…};
说明:
- 与C语言不同,Java中的二维数组中的内层花括号是必不可少的。内层花括号的对数确定了二维数组的行数。
- 每一对内层花括号中值的个数都可以不相同,即Java允许二维数组中的每一行具有不同的列数。
二维数组的存储结构
计算机中的内存空间总是一维结构,即由若干字节线性排列而成,因此,无论数组是几维的,都会被映射成一维结构。
对于二维数组,是由若干个一维数组组成的,例如如下一个二维数组:
int[][] a = {{1, 2}, {3, 4, 5, 6}, {7, 8, 9}};
将被存储为:
二位数组的实质仍是一维数组,只是元素不是基本类型,而是数组引用类型。
说明:
- 上图中灰色背景标识的是引用。可以将a[0],a[1],a[2]当作是一维数组的数组名。
- 二维数组中不同行的元素所占的内存单元可能是不连续的,但是每一个行内的元素是连续的。(上图中1和2是连续存储的,但是2和3未必,但是3、4、5、6又是连续的)
- 二维数组的各行是相对独立的,所以创建二维数组的时候可以省略列数(但是行数不能省略),然后单独创建每一行。
int[][] a = new int[2][]; a[0] = new int[2]; a[1] = new int[3];
- 二维以上的多维数组也能被视为一维数组,只是嵌套层次更深,它们的声明方式与二维数组类似。创建多维数组时,最高维长度不能省略,如果省略了最高维长度,则必须省略其后的所有低维长度,否则将发生语法错误。
访问二维数组
- 获取数组长度
int[][] a = new int[3][]; a[0] = new int[2]; a[1] = new int[4]; System.out.println(a.length); // 打印3(有3行) System.out.println(a[1].length); // 打印4(第2行有4列) System.out.println(a[2].length); // 因尚未创建第3行运行时将出错 int[][]b={{}}; // 1行0列 System.out.println(b.length); // 打印1 System.out.println(b[0].length); // 打印0
- 访问数组元素
访问二维数组的元素需要给出两个下标——行下标和列下标。array[1][3];