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