1、成员变量和局部变量
定义:
成员变量:在类范围里定义的变量,也就是前面几篇介绍的 Field。
局部变量:在方法里定义的变量。
注意:在变量命名规范上,我们应该做到知名识意,且变量名要首字母小写,后面每个单词首字母大写。
成员变量又可根据是否有 static 关键字修饰分为:类 Field 和 实例 Field 两种。
类 Field 被 static 关键字修饰,它从这个类准备阶段就开始存在,直到系统完全销毁这个类时消亡。作用域上,类 Field 和这个类的生存范围相同。
实例 Field 未被 static 关键字修饰,它从这个类的实例被创建起开始存在,直到系统完全销毁这个实例。作用域上,实例 Field 和这个实例的生存范围相同。
类 Field 访问形式:
类名.类Field 实例.类Field
只要类存在,我们就可以通过类名访问其类成员变量。虽说也可以通过实例访问类 Field ,但由于这个实例并不拥有 类 Field ,所以本质上还是操作的类 field(修改值的时候,改的是类值而不是实例值)。
实例 Field 访问形式:
实例.实例Field
下面看一下具体代码了解其基本使用:
public class VariableTest { // 定义类 field public String name; // 定义实例 field public static int age; public static void main(String[] args) { // VariableTest 类已经初始化了,则 age 变量起作用了,输出 0 System.out.println("VariableTest 的 age 类 Field 值:" + VariableTest.age); // 创建 VariableTest 对象 VariableTest v = new VariableTest(); // 通过实例访问类field 和实例 field System.out.println("v 对象的 name field 值是:" + v.name + "v 对象的 age field 值是:" + v.age); // 为 v 实例变量赋值 v.name = "J3"; // 通过实例访问类 field,依然是访问 VariableTest 的 age 类 field v.age = 18; System.out.println("v 对象的 name field 值是:" + v.name + "v 对象的 age field 值是:" + v.age); System.out.println("VariableTest 的 age 的类 field 值:" + VariableTest.age); VariableTest v2 = new VariableTest(); System.out.println("v2 对象的 age 类 field 值:" + v2.age); } }
由程序可知,成员变量无须显示初始化,系统会自动为成员变量进行默认初始化,初始化规则与数组动态初始化元素赋值规则完全相同。
从运行结果可以看出,类 Field 的作用于比实例 Field 的作用于更大:实例 Field 随实例的存在而存在,而类 Field 则随类的存在而存在。
虽然实例可以访问类 Field ,但其本质还是访问的类 Field 指向的都是同一片内存空间(不提倡实例调用类 Field,可读性不好)。
局部变量根据定义形式不同,可以分为如下三种:
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。
代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到改代码块结束时失效。
局部变量与成员变量不同,必须显示初始化。也就是说,局部变量定义的时候必须制定默认值,否则不可访问它们。
下面演示局部变量基本使用:
public class VariableTest { public static void main(String[] args) { // ===================代码块====================== { // 定义一个代码块局部变量 a int a; // 下面代码将出现错误,因为 a 变量还未初始化 // System.out.println("代码库局部变量 a 的值:" + a); a = 5; System.out.println("代码块局部变量 a 的值:" + a); } // 下面试图访问的 a 变量并不存在 // System.out.println(a); // ===================方法变量====================== // 定义一个方法局部变量 b int b; // 下面代码将出现错误,因为 b 变量还未初始化 // System.out.println("代码库局部变量 b 的值:" + b); b = 5; System.out.println("方法局部变量 b 的值:" + b); // ===================形参变量====================== VariableTest variableTest = new VariableTest(); variableTest.sum(2, 3); } public void sum(int c, int d) { System.out.println("形参 c 和 d 相加的值:" + (c + d)); } }
对于上面程序中的形参是真个方法体内有效,初始化是在调用方法时由系统完成,形参的值由方法的调用者负责指定。
在同一个类里,成员变量的作用范围是整个类内有效,一个类不能定义两个同名成员变量,即使一个是类 Field ,一个是实例 Field 也不行;一个方法不能定义两个同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或者形参也不行。
Java 运行局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用 this 或类名作为调用者来限定访问成员变量。
同名变量案例:
public class VariableTest { // 定义 name 实例 field private String name = "J3"; // 定义 age 类 field private static int age = 18; public static void main(String[] args) { // 方法里的局部变量,局部变量覆盖成员变量 int age = 28; // 直接访问 age ,将输出局部变量的值 System.out.println(age); // 使用类名调用 System.out.println(VariableTest.age); } public void info() { // 方法里的局部变量,局部变量覆盖成员变量 String name = "西行"; // 直接访问 name 输出局部变量的值 System.out.println(name); // 使用 this ,限定调用成员变量 System.out.println(this.name); } }
2、成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值。
看下面代码,根据 VariableTest 类创建了两个实例,配合示意图来说明 Java 成员变量的初始化和内存中的运行机制。
public class VariableTest { // 定义 name 实例 field private String name; // 定义 age 类 field private static int age; public static void main(String[] args) { // 创建两个对象 VariableTest v1 = new VariableTest(); VariableTest v2 = new VariableTest(); // 给实例 field 赋值 v1.name = "J3"; v2.name = "西行"; // 给类 field 赋值 v1.age = 28; v2.age = 38; System.out.println("v1 中实例变量值:" + v1.name + ",v1 中类变量值:" + v1.age); System.out.println("v2 中实例变量值:" + v2.name + ",v2 中类变量值:" + v2.age); } }
当程序执行到第 9 行代码时,如果这行代码是第一次使用 VariableTest 类,则系统会在第一次使用 VariableTest 类时加载这个类,并初始化这个类。再类的准备阶段,系统将会为该类的类 Field 分配内存空间,并指定默认初始值。当 VariableTest 类初始化完成后,系统内存中的存储示意图如下:
当 VariableTest 类初始化完成后,系统将在堆内存中为 VariableTest 类分配一块内存区,在这块内存区里包含了保存 age 类 Field 的内存,并设置 age 的默认初始值:0。
系统接着创建了一个 VariableTest 对象,并把这个 VariableTest 对象赋给 v1 变量,VariableTest 对象里包含了名为 name 的实例 Field ,实例 Field 是在创建实例时分配内存空间并指定初始值的。当创建了第一个 VariableTest 对象后,系统内存的存储的示意图如下:
有图可知,age 类 Field 并不属于 VariableTest 对象,它时属于 VariableTest 类的,所以创建第一个对象时并不需要为 age 类 Field 分配内存,系统只是为 name 实例 Field 分配了内存空间,并指定默认初始值:null。
接着执行 10 行代码创建第二个 VariableTest 对象,此时因为 VariableTest 类已经存在于堆内存中了,所以不需要对 VariableTest 类进行初始化。并且创建第二个 VariableTest 对象与创建第一个 VariableTest 对象并没有什么不同。
当程序执行 12、13 代码时,将为 v1 的 name 实例 Field 赋值,也就是下图中 name 值向 “J3” 字符串。指向完后,两个 VariableTest 对象在内存中的存储示意图如下:
上图可以看出,name 实例 Field 是属于单个 VariableTest 实例的,因此修改第一个 VariableTest 对象的 name 实例 Field 时仅仅与该对象有关,同样修改第二个对象的 name 实例 Field 时,也与 VariableTest 类和其他 VariableTest 对象无关。
最后当程序执行到 15、16 行代码时,此时通过 VariableTest 对象来修改 VariableTest 的类 Field,结合图和运行结果不难看出,v1、v2 对象修改的都是统一内存中的值。修改成功后,内存中的存储示意图如下:
从上图可以看出,通过 v1 和 v2 来访问类 Field 时,实际上访问的时 VariableTest 类的 age 。
实际上,不管通过那个 VariableTest 实例来访问 age Field ,本质上还是通过 VariableTest 类来访问 age FIeld,他们所访问的时同一块内存。所以以后建议,当程序需要访问类 Field 时,尽量使用类作为主调,而不是使用对象作为主调,避免歧义,题号程序可读性。
3、局部变量的初始化和内中的运行机制
局部变量定义后,必须经过显示初始化后才能使用,系统不会为局部变量执行初始化。因为定义局部变量后,系统并未为这个变量分配内存空间,知道等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在的方法的栈内存中。如果局部变量是基本类型的变量,则直接把这个变量的值保存在改变量对应的内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。
栈内存中的变量无需系统垃圾回收,往往随方法或代码块的运行结束而结束。因此,局部变量的作用域是从初始化该变量开始,直到该方法或改代码快运行完成而结束。因为局部变量只保存基本类型的值或对象的引用,因此局部变量所占的内存通常比较小。
好了,今天的内容到这里就结束了