理解数组
数组(如:int[])也是一种数据类型,它本身是一种引用类型。Java 的数组要求所有数组元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,不能存储多种数据类型的数据。
因为 Java 语言是面向对象的语言,能很好地支持类与类之间的继承关系,这样可能产生一个数组里可以存放多种数据类型的假象:例如有一个水果数组,要求每个数组元素都是水果,实际上数组元素既可是苹果,也可以是香蕉,但这个数组的数组元素的类型还是唯一的,只能是水果类型。这里体现出的继承思想就是:可以说子类一定是父类(苹果一定是水果,香蕉一定是水果),却不能说父类一定是子类(水果一定是苹果,水果一定是香蕉)。
数组初始化
一旦为数组的每个数组元素分配了内存空间,每个内存空间里存储的内容就是该数组元素的值,即使这个内存空间存储的内容是空,这个空也是一个值(null)。不管以哪种方式来初始化数组,只要为数组元素分配了内存空间,数组元素就具有了初始值,初始值的获得由两种形式:一种有系统自动分配,一种有程序员指定。
一旦数组的初始化完成,数组在内存中所占的空间将被固定下来,因此数组的长度将不可改变。即使把某个数组元素的数据清空,但它所占的空间依然被保留,依然属于该数组,数组的长度依然不变。
静态初始化:程序员为数组元素分配初始值,系统决定数组长度。
动态初始化:程序员指定数组长度,系统为数组元素分配初始值。
静态初始化和动态初始化不要同时使用,也就是说不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值。
内存中的数组
定义数组时,在栈内存(stack)分配空间存储“数组的引用变量”
初始化数组时,在堆内存(heap)分配空间存储“数组本身”
基本数据类型数组
// 静态初始化
int[] values1 = { 1, 2, 3 };
// 动态初始化
int[] values2 = new int[3];
引用类型数组
Integer[] integers = new Integer[2];
integers[0] = new Integer(1);
integers[1] = new Integer(2);
当我们看一个数组的时候,一定要把数组看成两个部分:一个是数组引用,也就是代码中定义的数组引用变量(stack memory);还有一个是数组本身,这个部分是运行在系统内存(heap memory)里的,通常无法直接访问它,只能通过数组引用变量来访问它。
为什么有栈内存和堆内存之分?
方法执行时,都会建立自己的内存栈,方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;
当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),此时这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在合适的时候回收它。
没有多维数组
Java 语言提供了支持多维数组的语法。但是,如果从数组底层的运行机制上来看,没有多维数组。
Java 语言里的数组类型是引用类型,因此,数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。
// 定义并初始化具有三个int类型的数组
int[] one = new int[3];
// 定义并初始化具有三个int[]类型的数组
int[][] two = new int[3][];
定义一维数组的语法:type[] arrName;
这是典型的一维数组定义语法,其中 type 是数组元素的类型。
把 type 具体成 int[]( int[] 是一种数据类型,用法和普通类型并无区别),就出现了一个二维数组 int[][] arrName;
再把int这个类型扩展到 Java 的所有类型(不包括数组类型),就可以得出二维数组的定义语法;
定义二维数组的语法:type[][] arrName;
多维数组类推即可。
从这里就可以分析出多维数组的本质其实就是“在数组中存储数组”,用“树”结构实现“多维”的概念。