一.数组也是一种类型
- 数组是编程语言中最常见的一种数据结构,java中可通过数组元素的索引来访问数组元素,包括为数组元素赋值和取值。
- 一旦数组初始化完成,数组在内存总所占的空间将被固定起来,因此数组长度不可变,即使数组中没有存放数据元素,但他的空间任然被保留下来,仍然属于数组。
- java的数组既可以存储基本数据类型,也可以存储引用数据类型,它本身也是一种引用数据类型。例如, int 是一个基本数据类型,但 int[] 就是一中引用类型了。
二.定义数组
java语言支持两种语法格式来定义数组
type[] arrayName;
type arrayName[];
通常建议使用第一种方式来定义数组。很容易理解这是一个变量,其中变量名是arrayName,而变量类型是type[]。
注意:定义数组时不能指定数组的长度
三.数组的初始化
java语言中数组必须先初始化,然后才可以使用。所谓初始化,就是为数组的数组元素分配内存空间,并为每个数组袁术赋初值。
数组初始化的方式有两种
- 静态初始化:初始化时由程序员显式指定每个数组元素的初值,并由系统决定数组的长度。
- 动态初始化:初始化时程序员只能指定数组的长度,由系统为数组元素分配初值。
3.1 静态初始化
语法格式:
arrayName = new type[] {element1,element2,element3...};
但在实际的开发过程中,可以使用简化的方法:
int[] a = {3,5,4,6};
3.2动态初始化
动态初始化只指定数组的长度,由系统为每个数组元素指定初值,其语法格式如下:
arrayName = new type[length];
动态初始化时,系统将按以下规则初始化初值
- 当数组元素是基本类型中的整数类型(byte,short,int和long) ,则数组元素的值为0;
- 当数组元素是基本类型中的浮点类型(float,double) ,则数组元素的值为0.0;
- 当数组元素是基本类型中的字符类型(char) ,则数组元素的值为'\u0000';
- 当数组元素是基本类型中的布尔类型(boolean) ,则数组元素的值为false;
- 当数组元素是引用类型(类,借口和数组) ,则数组元素的值为null;
四.foreach循环
这种循环遍历数组和集合。使用foreach遍历数组时,无法获得数组的长度,无法根据索引来访问数组,foreach会自动遍历数组中的元素。
其格式如下:
for(type variableName : array|collection){
//variableName自动迭代访问每个元素。
}
public class ForEachTest
{
public static void main(String[] args)
{
String[] books = {"轻量级java教程",
"疯狂java讲义",
"疯狂Android讲义"};
//使用foreach来遍历数组,其中book将自动迭代每个数组元素
for(String book : books)
{
System.out.println(book);
}
}
}
五.深入数组
数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存中是分开存放的。
5.1内存中的数组
数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用内存指向有效内存时,才可通过该数组来访问数组元素。
实际的数组对象被存储在堆(heap)内存中,其数组引用变量被存储在栈(stack)中。
数组引用变量是访问堆内存中数组元素的根本方式
如果堆内存中的数组不再有任何引用变量指向自己,则这个数组将会成为垃圾,该数组所占内存将被系统回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为null,切断数组引用变量和实际数组之间的关系。
只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组的长度可变的错觉。
public class ArrayInRam
{
public static void main(String[] args){
//定义初始化数组,使用静态初始化
int[] a = {5,7,20};
//定义初始化数组,使用动态初始化
int[] b = new int[4];
//输出b数组的长度
System.out.println("b数组的长度为:" + b.length);
//循环输出a数组的元素
for(int i=0;i<a.length;i++)
{
System.out.println(a[i]);
}
//循环输出b数组的元素
for(int i=0;i<b.length;i++)
{
System.out.println(b[i]);
}
//因为a是int[] 类型,b也是int[]类型,所以可以将a的值赋给b,也就是让b引用指向a引用指向的数组
b=a;
//再次输出b数组的长度
System.out.println("b数组的长度:" + b.length);
}
}
对于引用类型变量,系统的引用变量存储中的都是地址。在上面例子中,当执行b=a;时系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。
5.2基本类型数组的初始化
对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。
下面程序定义了一个int[] 类型的数组变量,采用动态初始化的方式初始化了该数组,并显式为每个数组元素赋值。
public class PrimitiveArrayTest
{
public static void main(String[] args)
{
//定义一个int[]类型的数组变量
int[] iArr;
//动态初始化该数组,数组长度为5
iArr = new int[5];
//采用循环方式为每个数组元素赋值
for(int i=0;i<iArr.length;i++){
iArr[i]=i+10;
}
}
}
上面代码执行第一行代码 int[] iArr;时,仅定义一个数组变量,此时内存中的存储示意图如图4.5所示。
执行了int[] iArr;后,仅在栈内存中定义了一个空引用。这个引用并未指向任何有效的内存,当然也无法指定数组的长度。
当执行了iArr=new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的初识值:所有元素都被赋值为0,此时内存中的存储示意图如图4.6所示。
5.3引用类型数组的初始化
引用类型数组的数组元素是引用,因此情况更加复杂。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。
class Person
{
public int age;
public double height;
public void info(){
System.out.println("我的年龄是" + age+"我的身高是"+height);
}
}
public class ReferenceArrayTest
{
public static void main(String[] args){
//定义一个Person[] 类型的名为students的数组变量
Person[] students;
//执行动态初始化
students=new Person[2];
//创建一个Person实例,并将这个Person实例赋给zhang变量
Person zhang=new Person();
//为zhang所引用的Person对象的age,height赋值。
zhang.age=15;
zhang.height=158;
//创建一个Person实例,并将这个Person实例赋值给lee变量
Person lee = new Person();
lee.age=16;
lee.height=161;
//将zhang变量的值赋给第一个数组元素
students[0]=zhang;
//将lee变量的值赋给第二个数组元素
students[1]=lee;
//下面两行代码的结果完全一样,因为lee和students[1]指向的是同一个Person实例
lee.info();
students[1].info();
}
}
上面代码的执行过程代表了引用类型数组初始化的典型过程。
执行Person[] students;代码时,这行代码仅在栈内存中定义了一个引用变量,也就是一个指针,这个指针并未指向任何有效的内存区,此时的内存中存储示意图如图4.8所示.
直到执行初始化,本程序对students数组执行动态初始化,动态初始化由系统为每个数组元素分配默认的初识值:null.执行动态初始化以后内存的存储示意图如图4.9所示.
接着代码定义了zhang和lee两个Person实例,定义这两个实例实际上分配了4块内存,在栈内存中存储了zhang和lee两个引用变量,还在堆内存中存储了两个Person实例.此时内存示例图如图4.10所示.
直到程序依次将zhang和lee分别赋给students的两个元素,students数组的两个数组元素将会指向有效的内存区,此时的内存存储示意图如图4.11所示.
此时,zhang和students[0]指向同一个内存区,而且他们都是引用类型变量,因此通过zhang和students[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改students[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的是同一个内存区,所以必然相互影响.同理,lee和students[1]是一样的.
5.4没有多维数组
java语言中提供了支持多维数组的语法,但是如果从java的数组的底层的运行机制上来说,没有多维数组.
java语言里的数组类型是引用类型,因此数组变量实际上是一个引用,这个引用指向真实的数组内存.数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组.
回到前面定义数组类型的语法:type[] arrName;,这是典型的一维数组的定义语法,如果希望数组元素也是一个引用,而且是指向int数组的引用,则可以把type改为int[] ,那么上面定义数组的语法就是int[][] arrName.
如果把int这个类型1扩大到Java的所有类型(不包括数组类型),则出现了定义二维数组的语法:
type[][] arrayName;
java采用上面的语法格式来定义二维数组,但他的本质还是一维数组,只是其数组元素也是引用,数组元素里保存的引用指向一维数组.
接着对这个"二维数组"执行初始化,同样可以把这个数组当成一维数组来初始化,把这个"二维数组"当成一个一维数组,其元素类型是type[] 类型,则可以采用如下语法来进行初始化:
arrName = new type[length][];
上面的初始化语法相当于初始化了一个一维数组,这个一维数组的长度是length.同样,因为这个一维数组的的数组元素是引用类型的,所以系统为每个数组元素都分配了初识值:null.
使用new type[length][]初始化这个数组后,相当于定义了length个type[]类型的变量,当然,这些type[]类型的变量都是数组类型,因此必须再次初始化这些数组.
下面程序示范了如何把二维数组当成一维数组来处理.
public class TwoDimensionTest
{
public static void main(String[] args)
{
// 定义一个二维数组
int[][] a;
// 把a当成一维数组进行初始化,初始化a是一个长度为4的数组
// a数组的数组元素又是引用类型
a = new int[4][];
// 把a数组当成一维数组,遍历a数组的每个数组元素
for (int i = 0 , len = a.length; i < len ; i++ )
{
System.out.println(a[i]);
}
// 初始化a数组的第一个元素
a[0] = new int[2];
// 访问a数组的第一个元素所指数组的第二个元素
a[0][1] = 6;
// a数组的第一个元素是一个一维数组,遍历这个一维数组
for (int i = 0 , len = a[0].length ; i < len ; i ++ )
{
System.out.println(a[0][i]);
}
// 同时初始化二维数组的2个维数
int[][] b = new int[3][4];
// 使用静态初始化的语法来初始化一个二维数组
String[][] str1 = new String[][]{new String[3]
, new String[]{"hello"}};
// 使用简化的静态初始化语法来初始化二维数组
String[][] str2 = {new String[3]
, new String[]{"hello"}};
System.out.println(str1[1][0]);
System.out.println(str2[1][0]);
}
}
由上可知,二维数组是一维数组,其数组元素是一维数组;三维数组也是一维数组,其数组元素是二维数组.....从这个角度来看,java语言里没有多维数组.