4.3 成员变量和局部变量

一、定义

成员变量指的是在类里面定义的变量,也就是前面所说的field;局部变量指的是在方法里定义的变量。
Java程序里的变量划分为如图所示:

1.1 成员变量

成员变量可以不用初始化,系统会为它分配默认初始值。初始化规则与数组初始化规则相同。
成员变量分为类变量和实例变量两种,定义成员变量时,有static修饰的是类变量,类变量从该类准备阶段起开始存在,知道系统完全销毁这个类,类的作用域与这个类的生存范围相同;无static修饰的是实例变量,实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个类,实例变量的作用域与对于实例的生存范围相同。
只要类存在,程序就可以访问类变量,访问类变量的语法格式:

类名.类变量

只要实例存在,程序就可以访问实例变量,访问实例变量的语法格式:

实例.实例变量

注意:类变量也可以用该类的实例来访问,但实质上还是通过该类来访问类变量,语法格式:

实例.类变量

既是说通过一个实例来修改了类变量的值,由于这个类变量并不属于它,而是属于对于的类。因此修改的仍然是类变量,与通过该类来修改类变量完全相同,这也会导致通过其他实例来访问这个类变量也是被修改的值。

class FieldTest 
{
	//定义一个类变量
	public static String name;
	//定义一个实例变量
	public int a;

	public static void main(String[] args) 
	{
		//定义两个FieldTest类的实例
		var p1=new FieldTest();
		var p2=new FieldTest();

		//访问类变量name;:系统分配默认初始值null
		System.out.println(FieldTest.name);
		//下面通过实例方式访问类变量,实质也是通过类来访问
		System.out.println(p1.name);

		//通过实例p1来修改类变量和实例变量
		p1.name="Java";
		p1.a=10;

		//通过实例p1访问类变量和实例变量
		System.out.println(p1.name);
		System.out.println(p1.a);

		//通过实例p2访问类变量和实例变量
		System.out.println(p2.name);//类变量已经被p1进行修改
		System.out.println(p2.a);
	}
}
---------- 运行Java捕获输出窗 ----------
null
null
Java
10
Java
0

输出完成 (耗时 0 秒) - 正常终止

从上面可以发现:类变量的作用域比实例变量的作用域大。实例变量也可以访问类变量,同一个类的所有实例变量访问类变量时,实际上都是访问的该类的同一个变量。在以后的编程中,对于static修饰的类变量,最好用类来访问,增加程序的可读性

1.2 局部变量

与成员变量不同的是,局部变量除形参以外,必须进行显示初始化,也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它。局部变量分为三种
1、形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。
2、方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。
3、代码块内的局部变量:在代码块定义的局部变量,这个局部变量的作用域从定义改变了的地方生效,到代码块结束的地方失效。

{
	public static void main(String[] args) 
	{
		{
			//定义一个代码块局部变量
			int a;
			//下面代码将出现错误,因为变量a还未初始化
			//System.out.println("代码块局部变量a:"+a);
			//BlockTest.java:9: 错误: 可能尚未初始化变量a
			a=5;
			System.out.println("代码块局部变量a:"+a);
		}

		//在代码块外访问变量a是不存在的,超出了代码块局部变量a的作用域
		//System.out.println(a);
		//BlockTest.java:16: 错误: 找不到符号
	}
}
---------- 运行Java捕获输出窗 ----------
代码块局部变量a:5

输出完成 (耗时 0 秒) - 正常终止

Java允许局部变量和成员变量同名,如果方法里的局部变量与成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的长远变量,则可以使用this(对于实例)或类名(对于类变量)作为调用者来访问成员变量。

class  VariableOverrrideTest
{
	//定义一个name实例变量
	private String name="U盘";
	//定义一个price类变量
	public static double price=55.0;

	//定义普通方法
	public void info()
	{
		//方法里的局部变量覆盖成员变量
		var name="孙悟空";
		//直接访问name变量,将输出name的局部值
		System.out.println(name);

		//使用this来输出name实例变量的值
		System.out.println(this.name);
	}
	public static void main(String[] args) 
	{
		//方法里的局部变量会覆盖成员变量
		var price=65.0;
		//直接访问price变量,将输出局部变量的值
		System.out.println(price);
		//使用类名访问局部类变量
		System.out.println(VariableOverrrideTest.price);
		//运行info()
		new VariableOverrrideTest().info();
	}
}
---------- 运行Java捕获输出窗 ----------
65.0
55.0
孙悟空
U盘

输出完成 (耗时 0 秒) - 正常终止

二、成员变量的初始化和内存中的运行机制

当系统加载类或创建类的实例时,系统会为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值,初始值的规则与数组相同。

public class  ProgramRun
{
	//这是一个类变量
	public static int num;
	//这是一个实例变量
	public String name;
	

	public static void main(String[] args) 
	{
		//创建两个实例变量
		var p1=new ProgramRun();
		var p2=new ProgramRun();

		//分别为两个Person对象的name变量赋值
		p1.name="张三";
		p2.name="孙悟空";
		
		p1.num=2;
		System.out.println(p2.num);
	}
}
---------- 运行Java捕获输出窗 ----------
2

输出完成 (耗时 0 秒) - 正常终止

(1)当程序执行到var p1=new ProgramRun();时,这行代码第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类。系统为该类的类变量分配内存空间,并指定默认初始值。

当ProgramRun类初始化完成后,系统会在堆内存中为类变量num分配一块内存区,并设置num的默认值0。
(2)接着创建两个ProgramRun对象p1,p2,类里面包含了名为name的实例变量,实例变量在创建实例时分配内存空间并指定初始值的。

(3)分别为两个Person对象的name变量赋值
p1.name="张三";
p2.name="孙悟空";

(4)当执行到p1.num=2;时,此时通过ProgramRun对象来修改ProgramRun类的类变量,其实通过ProgramRun对象p1来访问类变量num,实质上还是通过类来访问,故通过对象p2来访问变量num,还是访问的同一块区域。

三、局部变量的初始化和在内存中的运行机制

局部变量定义后,必须经过显式的初始化后才能使用,系统不会为局部变量执行初始化,这意味着定义局部变量时,系统并未为其分配内存空间,知道等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
局部变量不属于任何类或实例,因此它总是保存在栈内存中。栈内存中的变量无需系统垃圾回收,往往随方法或代码块的运行结束而结束。因此局部变量的作用域是从初始化该变量开始,知道该方法或代码块完成而结束。由于局部变量只保存基本类型或者对象的引用,因此局部变量所占的内存空间很小。

posted @ 2020-02-22 23:01  小新和风间  阅读(268)  评论(0编辑  收藏  举报