Java进阶——数组与内存控制

下面将会深入探讨Java数组的静态特征。使用Java数组前必须对数组进行初始化,初始化的过程即给数组的所有元素都分配合适的内存空间,并指定初始值的过程。数组初始化以后将不能重新改变数组对象在内存中的位置和大小。从用法角度看,数组元素相当于普通变量,程序既可以吧数组的值赋给普通变量,也可以把普通变量的值赋给数组元素。但其实质是对内存中数组元素对象的操作。

1、数组初始化

Java语言是典型的静态语言,所以Java的数组也是静态的。Java中的数组必须经初始化后才可以使用,初始化即为数组对象的所有元素分配一个连续的内存空间,并为每个元素指定初始值。数组的初始化有两种方式:

>静态初始化:由程序员显式地指定每个数组元素的初始值,由系统决定数组的长度;

>动态初始化:程序员只指定数组长度,由系统为数组元素分配初始值。

下面是两种初始化方式的实例:

public class test01 {

       public static void main(String[] args) {

              //静态初始化数组

              String[] str0 = new String[]{"小武灵灵" , "41009160"};

              //静态初始化的简化形式

              String[] str1 = {"小武灵灵" , "41009160"};

              //动态初始化

              String[] str2 = new String[2];

       }

}

Java语言的数组变量是引用类型的变量,上面的三个数组变量按照声明的前后顺序(注意这里不是初始化的先后顺序)存储在main栈区中,其引用的数组是按照数组元素的顺序分别在堆内存中分配到长度为2的连续空间。

注意,虽然这里str0str1变量引用的数组内容相同,但是绝不是同一个数组,也就是说内存中除了str2外,还有两个长度内容都相同的数组。

注意,鼎泰初始化和动态初始化不能同时使用。

而执行动态初始化时,程序员指定了数组的长度,即为每个数组元素指定所需的内存空间,系统负责为这些数组元素分配初始值。系统将按照如下规则分配到初始值:

>基本类型:

       >整数类型(byte , short , int , long):数组元素的初始值为0

       >浮点类型(float , double)0.0

       >字符类型(char , string)’\u000’

       >布尔型(boolean)false

>引用类型(类、接口和数组)null

1)、数组变量和数组对象

Java的数组变量是一种引用类型的变量,通常情况下被存放在栈内存中,它并不是数组本身,它只是指向堆内存中的数组对象(C语言里的指针差不多)

数组对象是保存在堆内存中的连续内存空间。

理解了这两个概念之后,我们可以对数组的初始化有一个更深的认识,即对数组执行初始化,其实并不是对数组变量执行初始化,而是要对数组对象执行初始化(即为该数组对象分配一块连续的内存空间)

认识到这一点之后我们可以解释这样一种假象,即认为数组的长度是可变的,例如下面的实例:

public class test01 {

       public static void main(String[] args) {

              String[] str0 = new String[]{"小武灵灵" , "41009160" , "陕西师范大学"};

              String[] str1 = new String[2];

              str1 = str0;

              System.out.println("str1的长度为:" + str0.length);

       }

}

相信大家可以自行解释这种假象,这里我就不解释啦!

       2)、基本类型数组的初始化

基本类型数组的初始化过程为:程序直接为数组分配内存空间,再将数组元素的值存入对应的内存里。例如下面的代码块:

String[] str;

str = new String[]{“小武灵灵” , 41009160, “地信1”};

注意,所有局部变量都是放在栈内存里保存的,不管其是基本类型的变量,还是引用类型的变量,都是存储在各自的方法栈区中;但引用类型变量所引用的变量(包括数组、普通Java对象)则总是存储在对内存中。

注意,引用变量本质上只是一个指针,只要程序通过引用变量访问属性,或通过引用变量来调用方法,该引用变量将会由它所引用的对象代替。

       3)、引用类型数组的初始化

引用类型数组的数组元素依然是引用类型的,因此数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了该引用变量所引用的对象。例如:

class Student {

       public String name;

       public int number;

       public void output() {

              System.out.println(name + “,”+number);

}

}

public class Main{

       public static void main(String[] args) {

              Student[] stu = new Student[2];

              System.out.println(“数组长度:” + stu.length);

       Student xiaowu = new Student();

       xiaowu.name = “小武”;

       xiaowu.number = “41009160”;

       stu[0] = xiaowu;

       //下面两行代码所输出的结果都是一样的

       stu[0].output();

       xiaowu.output();

}

}

在上面的程序中,内存是这样分配的:首先引用数组变量stu[]初始化完成后,内存中开辟出了长度为二的空间用来存储student对象;对引用变量xiaowu初始化完成后,内存中开辟了出了能够存储一个student对象长度的空间,用来存储student对象;将xiaowu赋给stu[0],则在内存中数组stu[0]指向对象xiaowu

2、数组的使用

       当数组引用变量指向一个有效的数组对象之后,程序就可以通过该数组引用变量来访问数组对象。Java语言不允许直接访问对内存中的数据,因此无法直接访问堆内存中的数组对象,程序将通过数组引用变量来访问数据。

       要对数组有一个更深刻的认识,需要理解两个关系:数组元素与变量间的关系、多维数组与变量间的关系。

       1)、数组元素与变量间的关系

              只要在已有数据类型之后增加括号,就会产生一个新的数组类型,例如:

       >int à int[]:在int类型后增加[]即变为int[]数组类型

       >Stirng à String[]:在String类型后增加[]即变为String[]数组类型

       当程序需要多个类型相同的变量来保存程序状态时,可以考虑使用数组来保存这些变量。当一个数组初始化完成,就相当于定义了多个类型相同的变量。

       无论哪种类型的数组,其数组元素其实相当于一个普通变量,把数组类型之后的括号去掉之后得到的类型就是该数组元素的类型。

       从这个角度讲,数组元素就是变量。

       注意,这里的数组元素并不是指数组对象中的数组元素,而是指数组变量的数组变量元素。

       注意,main方法声明的变量都属于局部变量,因此它们都被保存在main方法栈中;但数组元素则作为数组对象的一部分,总是保存在堆内存中,不管它们是基本类型的数组元素,还是引用类型的数组元素。

2)、多维数组与变量间的关系

       理解了数组元素与变量之间关系之后,如果已有的类型是int,在其后增加[]后得到一个数组类型;以int[]类型为已有类型,在其后增加[]后得到int[][]依然是数组类型;

int[][]类型为已有类型,在其后增加[]后得到int[][][]依然是数组类型。

       从上面可以看出,所谓多维数组,其实就是数组元素依然是数组的1维数组,2维数组是数组元素是1维数组的数组,3维数组是数组元素是2维数组的数组,4维数组是数组元素是3维数组的数组……N维数组是数组元素是N-1维数组的数组。

       所以,实际上我们可以这样认为:没有多维数组,只有基本数据类型和引用数据类型。

 

综上,数组的初始化有静态初始化和动态初始化两种;数组对象和数组变量不是同一个概念对于数组变量而言;数组元素的类型分为基本类型的数组和引用类型的数组;所有局部变量都是存放在栈内存里保存的,但引用类型变量所引用的对象则总是存储在堆内存中;一定要区分它何时只是数组变量,何时代表数组对象本身;数组元素就是变量,没有多维数组。