详解JAVA成员变量和局部变量
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
//直接为 name 实例 Field 赋值 p.name = "孙悟空";
//通过 p 访问 eyeNum 类 Field,依然是访问 Person 的 eyeNum 类 Field p.eyeNum = 2;
//再次通过 Person 对象来访问 name 实例 Field 和 eyeNum 类 Field
/*****
{ //定义一个代码块局部变量 a
int a; //下面代码将出现错误,因为 a 变量还未初始化 System.out.println("代码块局部变量 a 的值:" + a);
*/ //为 a 变量赋初始值,也就是进行初始化
a = 5; System.out.println("代码块局部变量 a 的值:" + a);
//Person 类已经初始化了,则 eyeNum 变量起作用了,输出0 System.out.println("Person 的 eyeNum 类 Field 值:" + Person.eyeNum+"\n"); //因为是类的 field(eyeNum)所以可以直接调用
//创建 Person 对象 Person p = new Person();
成员变量被分为类属性和实例属性两种,定义一个属性时不使用 static 修饰的就是实例属性, 使用 static 修饰的就是类属性。其中类属性从这个类的准备阶段起开始存在,直到系统完全 销毁这个类,类属性的作用域与这个类的生存范围相同; 而实例属性则从这个类的实例被 创建开始起存在,直到系统完全销毁这个实例,实例属性的作用域与对应实例的生存范围相 同。 提醒大家注意的是:一个类在使用之前要经过类加载、类验证、类准备、类解析、类初始化 等几个阶段。 正是基于这个原因,我们把类属性和实例属性统称为成员变量,其中类属性可以理解为类成 员变量,它作为类的一个成员,与类共存亡;实例属性则可理解为实例成员变量,它作为实 例的一个成员,与实例共存亡。 (1)只要类存在,程序就可以访问该类的类属性,在程序中访问类属性通过如下格式: 类.类属性; (2)只要实例存在,程序就可以访问该实例属性,在程序中访问实例属性通过如下格式: 实例.实例属性
(3)当然,类属性也可以让该类的实例来访问,通过实例来访问类属性的语法格式: 实例.类属性
但这个实例访问并不是这个实例的属性,依然是访问它对应类的类属性。也就是说,如果通 过一个实例修改了类属性的值,由于这个类属性并不属于实例,而是属于实例对应的类。因 此,修改的依然是类的类属性,与通过该类来修改类属性的结果完全相同,这会导致该类的 其他实例来访问这个类属性时也将获得这个被修改过的值。
与成员变量 不同的是,局部变量除了形参之外,都必须显式初始化.也就是说,必须先给方法局 部变量和代码块局部变量指定初始值,否则不可以访问它们.
看代码: public class BlockTest {
public static void main(String[] args) {
//在主方法中通过大括号定义一个局部代码块
通过了理论理解,现在看代码:
class Person {
//定义一个实例 Field, 因为代码没有被 static 修饰 public String name;
//定义一个类 Field public static int eyeNum; }
public class PersonTest {
public static void main(String[] args) {
public class VariableOverrideTest {
//定义一个 name 实例 Field private String name = "世界";
//定义一个 price 类 Field private static dowk.baidu.comble price = 78.0;
//主方法,程序的入口 public static void main(String[] args) {
我们首先要理解下概念: 成员变量指的是类范围里定义的变量,也就是前面所说的属性;局部变量指的是一个 方法内定义的变量。不管是成员变量、还是局部变量,都应该遵守相同的命名规则:从语法 角度来看,只要一个合法的标识符即可,但我们应该知道,从程序可读性角度来看,应该是 多个意义的单词连缀而成,其中一个单词首字母小写,后面每个单词首字母大写。 如图:
System.out.println("方法局部变量 a 的值:" + a); } } 看下编译结果:
形参的作用域是整个方法体内有效,而且形参也无须显式初始化,形参的初始化在调用该方 法时由系统完成。
当通过类或对象调用某个方法时,系统会在该方法栈区内为所有形参分配内存空间,并 将实参的值赋给对应的形参,这样就完成了形参的初始化。
System.out.println("p 变量的 name Field 值是:" + p.name + " p 对象的 eyeNum Field 值是:" + p.eyeNum+"\n");
//前面通过 p 修改了 Person 的 eyeNum,此处的 Person.eyeNum 将输出2 System.out.println("Person 的 eyeNum 类 Field 值:" + Person.eyeNum+"\n"); //类和对 象 Person p2 = new Person(); //p2访问的 eyeNum 类 Field 依然引用 Person 类的,因此依然输出2 System.out.println("p2对象的 eyeNum 类 Field 值:" + p2.eyeNum+"\n"); } } 编译结果:
从上图可以看出,因为 eyeNum 类属性并不属于 Person 对象,它是属于 person 类的,所以 创建第一个 Person 对象时并没有为 eyeNum 类属性分配内存,系统只是为 name 实例属性分 配了内存空间,并指定默认初始值:null. 接着执行 Person p2 = new Person();代码创建第二个 Person 对象,此时因为 Person 类已经存 在于堆内存中了,所以不再需要对 Person 类进行初始化。创建第二个 Person 对象与创建第 一个 Person 对象并没有什么不同。 当程序执行 p1.name="世界";代码时,将为 p1的 name 属性赋值,也就是堆内存中的 name 指向一个“世界”的字符串,执行完成后,两个 Person 对象在内存中的图如下:
看下编译结果:
从上面的那段程序我们可以清楚地看出局部变量覆盖成员变量,不过依然可以在方法中显式 指定类名和 this 作为调用者来访问被覆盖的成员变量,这让 我们编程时更自由。不过大部 分时间,我们还是应该尽量避免这种局部变量和成员变量同名的情形。
成员变量 的初始化和内存中的运行机制 当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间 后,自动为成员变量指定初始值。 看代码: //创建第一个 Person 对象 Perosn p1 = new Person(); //创建第个二 Person 对象 Person p2 = new Person(); //分别为两个 person 对象的 name 属性赋值 p1.name = "世界"; p2.name = "您好"; //分别为两个 person 对象的 eyeNum 属性赋值 //在这里我是接着前面的代码接着讲的,eyeNum 是使用了 static 修饰的 p1.eyeNum = 2; p2.eyeNum = 3; 当程序执行第一行代码 Perosn p1 = new Person(); 时,如果这行代码是第一次使用 Person 类,刚系统通常会在第一 次使用 Personod 时加载这个类,并初始化这个类。在类的准备阶段,系统将会为该类的类 属性分配内存空间,并指定默认初始值。当 Person 类初始化完成后,系统内存中的存储图:
//用来调用非 static 修饰的属性
//通过 Person 对象的引用 p 来访问 Person 对象 name 实例 Field //并通过实例访问 eyeNum 类 Field
System.out.println("p 变量的 name Field 值是:" + p.name + " p 对象的 eyeNum Field 值是:" + p.eyeNum+"\n");
}
public void info() {
//方法里的局部变量,局部变量覆盖成员变量 String name = "孙悟空";
//直接访问 name 变量,将输出 name 局部变量的值:"孙悟空" System.out.println(name);
//使用 this 来作为 name 变量的限定, //将输出 price 实例 Field 的值:"世界" System.out.println(this.name); } }
从图可以看出,当 Person 类初始化完成后,系统将在堆内存中为 Person 类分配一块内存区 (当 Person 类初始化完成后,系统会隐含地为 Person 类创建一个类对象,在这块内存区里 包含了保存类属性 eyeNumr 内存,并设置 eyeNum 的默认初始值:0)
系统接着创建了一个 Person 对象,并把这个 Person 对象赋给 P1变量,Person 对象里包含了 名为 name 的实例属性,实例属性是在创建实例时分配内存空间,并指定初始值的。当创建 了第一个 Person 对象时,系统的内存图如下:
}
//下面试图访问的 a 变量并不存在
System.out.println(a);
//因为变量 a 的作用域只在代码块中有效
}
}
从程序的运行结果可以看出,只要离开了代码块局部变量所在的代码块,则这个局部变量将 立即被销毁,变为不可见。
对于方法局部变量,其作用域从定义该变量开始,直到该方法结束。 public class MethodLocalVariableTest {
public static void main(String[] args) {
//定义一个方法局部变量 a int a;
//下面代码将出现错误,因为 a 变量还未初始化 //System.out.println("方法局部变量 a 的值:" + a);
//为 a 变量赋初始值,也就是进行初始化 a = 5;
//方法里的局部变量,局部变量覆盖成员变量 int price = 65; //直接访问 price 变量,将输出 price 局部变量的值:65 System.out.println("这时 price 输出的是局部变量的值:"+price);
//使用类名作为 price 变量的限定 //将输出 price 类 Field 的值:78.0 System.out.println(VariableOverrideTest.price);
我提醒大家注意: 在同一个类里,成员变量的作用范围是整个类内有效,一个类里不能定 义两个同名的成员变量,即使一个是类属性,一个是实例也不行;一个方法里不能定义两个 同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或者形参也不行。
JAVA 允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会 覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用 this(对于实例属 性)或类名(对于类属性)作为调用者来限定访问成员变量。
从上面程序中来看,成员变量无须显式初始化,只要为了一个类定义了类属性或实例属性,则 系统会在这个类的准备阶段或创建这个类的实例时进行默认初始化,成员变量默认初始化时 的赋值规则与数组动态初始化时数组元素的赋值规则完全相同. 这时我们发现类属性的作用域比实例属性的作用域更大:实例属性随实例的存在而存在,而类 属性则随类的存在而存在.实例也可访问类属性,同一个类的所有实例访问类属性时,实际上 访问的是同一个类属性,因为它们实际上都是访问该类的类属性. 局部变量: 根据定义形式的不同,分为三种: 形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效. 方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量 的地方生效,到该方 法结束时消失 代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生 效,到该代码块结束时失效.