Java数组的解读

2 篇文章 0 订阅

参考资料

[1]. 疯狂Java讲义第三版,李刚

数组类型

Java数组的一些特点

  1. Java的数组要求所有的数组元素具有相同的数据类型。
  2. 因为类和类之间可以支持继承关系,这样可能产生一个数组里可以存放多种数据类型的假象。
  3. 数组一旦初始化完成,数组在内存中所占的空间就固定下来,因此数组的长度将不可改变,即使清空某个元素,但它所占的空间依然保留。

定义数组

定义数组的语法

// 常用的定义方法
type[] arrayName;
// 不常用的定义方法
type arrayName[];

数组是一种引用类型的变量,因为使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),这个引用变量还未指定任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。

数组的初始化

Java语言中数组必须先初始化,然后才可以使用,所谓初始化,就是就数组的数组元素分配内存空间,并为每个数组元素赋初始值。

静态初始化

初始化时由程序员显示指定每个数组元素的初始值,由系统决定数组长度。
语法格式如下:

// 标准版
type arrayName = new type[] {element1, element2, element3, element4 ...};
// 简化版
type arrayName = {element1, element2, element3, element4 ...};

使用示例:

// 定义一个int数组类型的变量,变量名为intArr
int[] intArr;
// 使用静态初始化,初始化数组时只指定数组元素的初始值,不指定数组长度。
intArr = new int[]{5, 6, 8, 20};
// 定义一个Object数组类型的变量,变量名为objArr
Object[] objArr;
// 使用静态初始化,初始化数组时数组元素的类型是
// 定义数组时所指定的数组元素类型的子类
objArr = new String[] {"a", "b"};
Object[] objArr2;
objArr2 = new Object[] {"c", "d"};
// 简化版
int[] ints1 = {1, 2, 8, 10};

子类实例是一种特殊的父类实例,在上面的程序中,String类型是Object类型的子类,即字符串是一种特殊的Object实例,所以可以声明变量类型的子类赋值到父类。

动态初始化

初始化时程序员只指定数组长度,由系统为数组元素分配初始值。
动态初始化的语法格式:

arrayName = new type[length];

语法示例:

// 数组的定义和初始化同时完成,使用动态初始化语法
int[] prices = new int[5];
// 数组的定义和初始化同时完成,初始化数组时元素的类型是定义数组时元素类型的子类
Object[] books = new String[4];

执行动态初始化时,程序员只需要指定数组的长度,即为每个数组元素指定所需的内存空间,系统将负责为这些元素分配初始值。指定初始值时,系统将按如下规则分配初始值。
1. 基本类型
1.1 整数类型(byte、short、int、long),则数组元素的值为0。
1.2 浮点类型(folat、double),则数组元素的值是0.0。
1.3 字符类型(char),则数组元素的值是’\u0000’。
1.4 布尔类型(boolean),则数组元素的值是false。
2. 引用类型(类、接口、数组),则数组元素的值是null

使用数组

使用索引值访问到数组以后,就可以把它当成一个普通的变量使用,类型就是数组在声明时的类型。

// 数组的定义和初始化同时完成,使用动态初始化语法
int[] prices = new int[5];
System.out.println(prices[1]);
// 为prices的第二个元素赋值为110
prices[1] = 110;
System.out.println(prices[1]);
// 试图访问的数组元素索引值超过数组长度
// 将引发ArrayIndexOutOfBoundsException异常
System.out.println(prices[100]);
// 利用数组的length属性,来遍历数组
for (int i = 0 ; i < prices.length; i++)
{
    System.out.println("prices: "+prices[i]);
}

foreach循环

foreach循环会自动迭代数组的每个元素,当每个元素都被迭代一次以后,foreach循环自动结束
foreach循环的语法格式如下:

for (type variableName : array | collection)
{
    // variableName 自动迭代访问每个元素...
}

foreach循环遍历所有元素的使用示例:

String[] Letters = {"a", "b", "c", "d"};
// 使用foreach循环来遍历数组元素
// Letter会自动迭代每个数组元素
for (String Letter: Letters
    ) {
    System.out.println(Letter);
}

当使用foreach循环来迭代输出数组元素或集合元素时,通常不要对循环变量赋值。

String[] Letters = {"a", "b", "c", "d"};
// 使用foreach循环来遍历数组元素
for (String Letter: Letters
    ) {
   Letter = "e";
   System.out.println("第一次:"+Letter);
}
// 再次遍历可以看到每个元素的值没有任何改变
// 很明显Letter只是一个临时变量,系统将元素的值赋给了它
// 所以不要再foreach循环中遍历
for(String Letter : Letters)
{
   System.out.println("第二次:"+Letter);
}

深入数组

内存中的数组

实际的数组对象本存储在堆(heap)内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。数组在内存中的存储示意图如下:
这里写图片描述

如果堆内存中数组不再有任何引用变量指向自己,则这个数组将成为垃圾,这个数组所占的内存将会被系统的垃圾回收机制回收,可以将该数组变量赋值null来达成资源回收的目的。

// 定义并初始化数组,使用静态初始化
int[] a = {1, 2, 20};
// 定义并初始化数组,使用动态初始化
int[] b = new int[5];

// 输出b数组的长度
System.out.println("a数组的长度是:" + a.length);
for (int i = 0; i < a.length; i++)
{
    System.out.println(a[i]);
}

// 输出b数组的长度
System.out.println("b数组的长度是:"+ b.length);
for (int i = 0; i < b.length; i++)
{
    System.out.println(b[i]);
}

// 将数组b的引用堆内存地址修改为a
b = a;
System.out.println("再次输出数组b的长度:" + b.length);

上面代码的运行原理如下:
这里写图片描述

这里写图片描述
通过上面的代码和图可以知道,变量放在栈内存中,数组的实体放在堆内存中,每次变动的只是栈内存中对堆内存中数组的引用。

基本类型数组的初始化

// 定义一个int[] 类型的数组变量
int[] iArr;
// 动态初始化数组,数组长度为5
iArr = new int[5];
// 采用循环方式为每个数组元素赋值
for (int i = 0; i < iArr.length; i++)
{
    iArr[i] = i+10;
}

下面用图来说明执行过程:
当执行int[] iArr;,仅在栈内存中定义了一个空引用,并未指向任何有效的内存,无法指定数组的长度。

这里写图片描述
当执行iArr = new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的初始值,所有的数组元素都被赋值为0
这里写图片描述
每个数组元素的值直接存储在对应的内存中,操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量。循环赋值以后如图:
这里写图片描述

引用类型数组的初始化

下面定义一个Person类(所有类都是引用类型)

public class Person {
    public int age;
    public double height;
    public void info()
    {
        System.out.println("我的年龄是:" + age + ",我的身高是:" + height);
    }
}

下面程序定义一个Person数组,接着动态初始化这个Person数组,并为这个数组的每个数组元素指定初始值,代码如下:

// 定义一个students数组变量,其类型是Person[]
Person[] students;
// 执行动态初始化
students = new Person[2];
// 创建一个Person实例,并将这个Person实例赋给zhang变量
Person zhang = new Person();
zhang.age = 15;
zhang.height = 158;

// 创建一个Person实例,并将这个Person实例赋给lee变量
Person lee = new Person();
// 为lee所引用的Person对象的age、height赋值
lee.age = 16;
lee.height = 161;

// 将张变量的值赋给第一个数组元素
students[0] = zhang;
// 将lee变量的值赋给第二个数组元素
students[1] = lee;

// 下面两行代码的结果完全一样,因为lee
// 和students[1]指向的是同一个Person实例
lee.info();
students[1].info();

执行Person[] students;代码时,只在栈内存中定义了一个引用变量。
这里写图片描述
执行动态初始化 students = new Person[2];
这里写图片描述
创建zhang和lee实例

// 创建一个Person实例,并将这个Person实例赋给zhang变量
Person zhang = new Person();
zhang.age = 15;
zhang.height = 158;

// 创建一个Person实例,并将这个Person实例赋给lee变量
Person lee = new Person();
// 为lee所引用的Person对象的age、height赋值
lee.age = 16;
lee.height = 161;

这里写图片描述
将变量引用给students变量

// 将张变量的值赋给第一个数组元素
students[0] = zhang;
// 将lee变量的值赋给第二个数组元素
students[1] = lee;

这里写图片描述
此时,zhangstudents[0]指向同一个内存区,而且他们都是引用类型变量,因此通过zhang和students[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改students[0]所指向的Person实例的实例变量,还是修改zhang变量所执行的Person实例的实例变量,所修改的是同一个内存区,所以必然互相影响。

没有多维数组

Java语言里的数组类型是引用类型,因此数组变量其实就是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去很像多维数组。
下面程序示范了如何把二维数组当成一维数组处理。

// 定义一个二维数组
int[][] a;
// 把a当成一维数组进行初始化,初始化a是一个长度为4的数组
// a数组的数组元素又是引用类型
a = new int[4][];
for (int i = 0; i < a.length; i++)
{
    System.out.println(a[i]);
}

// 初始化a数组的第一个元素
a[0] = new int[2];
// 访问a数组的第一个元素所指数组的第二个元素
a[0][1] = 6;
// a数组的第一个元素是一个一维数组,遍历这个一维数组
for (int i = 0; i < a[0].length; i++)
{
    System.out.println(a[0][i]);
}

当定义一个二维数组int[][] a;的时候,只是在栈区生成了一个a引用变量
这里写图片描述
程序对a数组执行初始化:a = new int[4][];,这行代码让a变量指向一块长度为4的数组内存,这个长度为4的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认的初始值null:
这里写图片描述

现在为a[0]执行动态初始化,这样,所有的元素都是0,然后为a[0][1]的元素赋值6

// 初始化a数组的第一个元素
a[0] = new int[2];
// 访问a数组的第一个元素所指数组的第二个元素
a[0][1] = 6;

这里写图片描述

如果一次指定每一维的大小,则代码如下:

// 同时初始化二维数组的两个维数
int[][] b = new int[3][4];

上面代码将定义一个b数组变量,这个数组变量指向一个长度为3的数组,这个数组的每个数组元素又是一个数组类型,它们各指向对应长度为4的int[]数组,每个数组元素的值为0。这行代码执行后在内存中的存储示意图如下图:
这里写图片描述

还可以使用静态初始化方式来初始化二维数组。使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值,代码如下:

// 使用静态初始化语法来初始化一个二维数组
String[][] str1 = new String{new String[3], new String[]{"hello"}};
// 使用简化的静态初始化语法来初始化二维数组
String[][] str2 = new String{new String[3], new String[]{"hello"}};

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值