栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

突破Java基本功——01数组与内存控制

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

突破Java基本功——01数组与内存控制

数组与内存控制

使用Java数组之前必须先对数组对象进行初始化。当数组的所有元素都被分配了合适的内存空间,并指定了初始值时,数组初始化完成。程序以后将不能重新改变数组对象在内存中的位置和大小。从用法角度来看,数组元素相当于普通变量,程序既可以把数组元素的值赋给普通变量,也可以把普通元素的值赋给数组元素。 数组初始化

Java语言的数组是引用类型的变量,具有Java独有的特性。 Java数组是静态的

Java语言是典型的静态语言,因此Java的数组是静态的,即当数组被初始化之后,该数组的长度是不可变的。数组初始化的两种方式

静态初始化:初始化时显式地指定每个元素的值,由系统决定数组的长度。动态初始化:初始化时只指定数组长度,由系统为数组元素分配初始值。指定初始值时,系统将按如下规则分配初始值:

数组元素的类型是整数类型(byte、short、int、long),则数组元素初始值为0;数组元素的类型是浮点类型(double、float),则数组元素初始值为0.0;数组元素的类型是字符类型(char),则数组元素初始值为’u0000’;数组元素的类型是布尔类型(boolean),则数组元素初始值为false;数组元素的类型是引用类型(类、接口、数组),则数组元素初始值为null; 注意:不要同时使用静态初始化和动态初始化。即在初始化数组时,不要既指定数组的长度,又为每个数组元素分配初始值。一旦完成数组的初始化,数组元素的内存空间分配就结束了,程序只能改变数组元素的值,无法改变数组的长度需要指出的是,Java的数组变量是一个引用类型的变量,数组变量不是数组本身,他只是指向堆内存中的数组对象。因此可以改变一个数组变量所引用的数组,这样可以造成数组长度发生变化的假象。

由上图可以看出,strArr、names和book数组都指向同一数组对象,因此当访问books数组、strArr数组的长度时,结果显示为3。实际上,数组对象本身的长度并没有发生变化,变的只是books数组变量的引用的数组对象。当原来books变量所引用的数组不再有任何引用变量引用该数组,他将会变成垃圾,等着垃圾回收器来回收。

数组一定要初始化吗

数组变量不需要初始化,数组对象一定要初始化。

使用Java数组之前必须进行初始化。

实际上,如果真正掌握了Java数组在内存中的分配机制,完全可以换一种方式来初始化数组,或者说,数组无需经过初始化。

始终记住:Java的数组是引用类型的变量,他并不是数组对象本身,只要让数组变量指向有效的数组对象,程序中即可使用该数组变量

public class ArrayTest3
{
	public static void main(String[] args) 
	{
		//定义、并初始化nums数组
		int[] nums = new int[]{3, 5, 20, 12};
		//定义一个prices数组变量
		int[] prices;
		//让prices数组指向nums所引用的数组
		prices = nums;
		for (int i = 0 ; i < prices.length ; i++ )
		{
			System.out.println(prices[i]);
		}
		//将prices数组的第3个元素赋值为34
		prices[2] = 34;
		//访问nums数组的第3个元素,将看到输出34.
		System.out.println("nums数组的第3个元素的值是:" + nums[2]);
	}
}

由以上程序可以看出,程序定义了prices数组之后,并未对其初始化,当执行了int[] prices;之后,程序的内存分配如下图所示:

此时的prices数组变量还未指向任何有效的内存,为指向任何数组对象,此时的程序还不可使用prices数组变量。

当程序执行prices = nums;之后,prices变量指向nums变量所引用的数组,此时prices变量和nums变量引用同一个数组对象。此后,程序完全可以正常使用prices变量了。

大部分时候我们把数组变量和数组对象搞混了,数组变量是一个引用变量,通常存放在栈内存中(也可以被放入堆内存中);而数组对象就是保存在堆内存中的连续内存空间。对数组进行初始化,实质上就是对数组对象进行初始化——也就是为该数组对象分配一块连续内存空间。

实际上Java中的任何引用变量都不需要经过所谓的初始化操作,需要进行初始化操作的是该引用变量引用的对象。

基本类型数组的初始化

对于基本类型数组而言,数组元素的值直接存放在对应的数组元素中,因此基本类型数组的初始化比较简单:程序直接先为数组分配内存空间,在将数组元素的值存放在对应内存里。

有一种说法:基本数据类型的值存储在栈内存中。这句话是完全错误的。由上图可以看出,2、5、-12、20都是基本类型的值,但是他们却存放在堆内存中。

实际上应该说:所有局部变量都是存放在栈内存中的,不管其是基本数据类型的变量,还是引用类型的变量。但是引用类型变量所引用的对象(包括数组、普通Java对象)则总是存储在堆内存中的。

对于Java语言而言,堆内存中的对象通常是不允许直接访问的,通常只能通过引用变量。例如,iArr本质上只是main栈区的引用变量,但使用iArr.length、IArr[2]时,系统将会自动变为访问堆内存中的数组对象。

那么引用类型变量何时只是栈内存中的变量本身,何时又变成引用实际的Java对象呢?

其实规则很简单:引用变量本质上只是一个指针,只要程序通过引用变量访问属性,或者通过调用引用变量来调用方法,该引用变量将会由它所引用的对象代替。

引用类型数组的初始化

引用类型数组的元素依然是引用类型,因此数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了该引用变量所引用的对象(包括数组和Java对象)。

class Person
{
	//年龄
	public int age;
	//身高
	public double height;
	//定义一个info方法
	public void info()
	{
		System.out.println("我的年龄是:" + age + ",我的身高是:" + height);
	}
}
public class ReferenceArrayTest
{
	public static void main(String[] args) 
	{
		//定义一个students数组变量,其类型是Person[]
		Person[] students;
		//执行动态初始化
		students = new Person[2];
		System.out.println("students所引用的数组的长度是:" + students.length);
		//创建一个Person实例,并将这个Person实例赋给zhang变量
		Person zhang = new Person();
		//为zhang所引用的Person对象的属性赋值
		zhang.age = 15;
		zhang.height = 158;
		//创建一个Person实例,并将这个Person实例赋给lee变量
		Person lee = new Person();
		//为lee所引用的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[]类型的数组而言,他的每个数组元素都相当于一个Person类型的变量,因此他的数组元素只能指向Person对象。

使用数组

当数组引用变量指向一个有效的数组对象之后,程序就可以通过该数组引用变量来访问数组对象。Java语言避免直接访问堆内存中的数据可以保证程序更加健壮,如果程序直接访问并修改堆内存中的数据,可能破坏内存中的数据完整性,从而导致程序crash。 数组元素就是变量

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

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

无论哪种类型的数组,其数组元素其实相当于一个普通变量,即int[]数组的元素就是一个int类型的变量。当通过索引来使用数组元素时和普通变量在用法上几乎没有太大的区别。

需要指出的是,main方法声明的变量都是局部变量,他们都保存在main方法栈中,但数组元素作为数组对象的一部分,总是保存在堆内存中,不管他们是基本数据类型的数组元素,还是引用类型的数组元素。

class Cat
{
	double weight;
	int age;
	public Cat(double weight , int age)
	{
		this.weight = weight;
		this.age = age;
	}
}
public class ArrayTest
{
	public static void main(String[] args) 
	{
		//定义,并动态初始化一个int[]数组
		int[] pos = new int[5];
		//采用循环为每个数组元素赋值
		for (int i = 0; i < pos.length ; i++ )
		{
			pos[i] = (i + 1) * 2;
		}
		//对于pos数组的元素来说,用起来完全等同于普通变量
		//下面即可将数组元素的值赋给int变量,也可将int变量的值赋给数组元素
		int a = pos[1];
		int b = 20;
		pos[2] = b;						//①
		//定义,并动态初始化一个Cat[]数组
		Cat[] cats = new Cat[2];
		cats[0] = new Cat(3.34, 2);
		//将cats数组的第1个元素的值赋给c1。
		Cat c1 = cats[0];
		Cat c2 = new Cat(4.3, 3);
		//将c2的值赋给cats数组的第2个元素
		cats[1] = c2;					//②
	}
}

由图中可以看出,虽然cats[0]、cats[1]数组元素也相当于引用类型的变量,用于指向实际的Cat对象,但他们与c1、c2两个引用类型变量不同的是,cats[0]、cats[1]作为数组的一部分。会被集中保存在堆内存中。

没有多维数组

前面说到,只要在已有数据类型之后增加方括号,就会产生一个新的数组类型,反过来,将数组类型最后的方括号去掉就得到了数组元素的类型。即int[][][]类型的数组,其数组元素就相当于int[][]类型的变量;int[][]类型的数组,其数组元素就相当于是int[]类型的变量;int[]类型的数组,其数组元素就相当于是int类型的变量。

Java允许将多维数组当成一维数组处理。初始化多维数组时可以先只初始化最左边的维数,此时该数组的每个元素都相当于一个数组引用变量,这些数组元素还需要进一步初始化。

public class TwoDimensionTest
{
	public static void main(String[] args) 
	{
		//定义一个二维数组
		int[][] a;
		//把a当成一维数组进行初始化,初始化a是一个长度为3的数组
		//a数组的数组元素又是引用类型
		a = new int[4][];
		//把a数组当成一维数组,遍历a数组的每个数组元素
		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]);
		}
	}
}

看上去如果试图让a[0][1]再次指向一个数组,就可以将其扩展成三维数组,甚至可以扩展成更多维的数组。但实际编程中,这样是行不通的。因为Java语言是强类型的语言,a[0]数组元素相当于int[]类型的数组,所以a[0][1]数组元素的值只能是int类型的值。

如果定义了一个Object[]类型的数组,会出现什么样的情况呢?此时,每个数组元素都相当于一个Object类型的引用变量,可以指向任何对象(包括数组和普通Java对象)。下面有一个“极端”的程序:

public class ObjectArrayTest
{
	public static void main(String[] args) 
	{
		//定义、并初始化一个Object数组
		Object[] objArr = new Object[3];
		//让objArr所引用数组的第二个元素再次指向一个长度为2的Object[]数组
		objArr[1] = new Object[2];						//①
		//将objArr[1]的值赋给objArr2,即让objArr2和objArr[1]指向同一个数组对象
		Object[] objArr2 = (Object[])objArr[1];			//②
		//让objArr2所引用数组的第二个元素再次指向一个长度为3的Object[]数组
		objArr2[1] = new Object[3];						//③
		//将objArr2[1]的值赋给objArr3,即让objArr3和objArr2[1]指向同一个数组对象
        Object[] objArr3 = (Object[])objArr2[1];		//④
		//让objArr2所引用数组的第二个元素再次指向一个长度为3的int[]数组
		objArr3[1] = new int[5];						//⑤
		将objArr3[1]的值赋给iArr,即让iArr和objArr3[1]指向同一个数组对象
		int[] iArr = (int[])objArr3[1];					//⑥
		//依次为iArr数组的每个元素赋值
		for (int i = 0 ; i < iArr.length ; i++ )
		{
			iArr[i] = i * 3 + 1;
		}
		//直接通过objArr访问iArr数组的第3个元素
		System.out.println(
            ((int[])((Object[])((Object[])objArr[1])[1])[1])[2]);
	}
}

此时的objArr很明显不再是一个简单的一维数组,他甚至可以被当成四维数组来使用,只要程序多次进行强制类型转换。

通过上面的分析可以看出,多维数组的本质依然是一维数组。Java程序中使用数组时,应该多从内存控制的角度来把握程序。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/776702.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号