在Java中,成员变量和局部变量存在较大的差异性。首先,我们来看一下变量的分类图:
成员变量
成员变量被分为:类属性和实例属性。
类属性:定义一个属性时,不使用static修饰的就是实例属性,
实例属性:定义一个属性时,使用static修饰的是类属性。
类属性从这个类的准备阶段起开始存在,直到系统完全销毁这个类,类属性的作用域与这个类的生存范围相同;
而实例属性则从这个类的实例被创建开始存在,直到系统完全销毁整个实例,实例属性的作用域与对应实例的生存范围相同。
PS:一个类在使用之前,要经过类加载、类验证、类准备、类解析、类初始化等几个阶段。以上就是类的生命周期。
注意:成员变量无须显式初始化,只要为一个类定义了类属性或实例属性,则系统会在这个类的准备阶段或创建这个类的实例时,进行默认初始化,成员变量默认初始化时的复制规则与数组动态初始化时数组元素的赋值规则完全相同。
实例属性随实例的存在而存在,而类属性则随类的存在而存在。实例也可以访问类属性,同一个类的所有实例访问类属性时,实际上访问的是同一个类属性,因为它们实际上都是访问到该类的类属性。
局部变量
局部变量根据定义形式不同,又可以被分为如下三种:
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效;
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生肖,到该方法结束时生效;
代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效。
与成员变量不同的是,局部变量除了形参之外,都必须显示初始化。也就说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它们。
成员变量的初始化和内存中的运行机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内控空间后,自动为成员变量指定初始值。
public class Person { public String name; public static int age; public static void main(String[] args) { //创建第一个Person对象 Person p1 = new Person(); //创建第二个Person对象 Person p2 = new Person(); //分别为两个Person对象的name属性赋值 p1.name="张三"; p2.name = "李四"; //分别为两个Person对象的eyeNum属性赋值 p1.age = 20; p2.age = 30; } }
当程序执行第一行代码Person p1 = new Person();时,如果这行代码是第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类。
在类的准备阶段,系统将会为该类的类属性分配内存空间,并指定默认初始值。当Person类初始化完成后,系统内存中的存储示意图如下:
(初始化Person类后的示意图)
当Person类初始化完成后,系统将在堆内存中为Person类分配一块内存区(当Person类初始化完成后,系统会隐含地为Person类创建一个类对象),在这块内存区里包含了保存类属性eyeNum的内存,并设置eyeNum的默认初始值:0
接着,系统会创建一个Person对象,并把整个Person对象赋给P1变量,Person变量里包含了名为name的实例属性,实例属性是在创建实例时分配内存空间、并指定初始值的。
当创建了第一个Person对象时,系统内存中的存储如下:
(创建第一个Person对象后的示意图)
age类属性并不属于Person对象,它是属于Person类的,所以创建第一个Person对象时并没有为eyeNum类属性分配内存,系统只是为name实例属性分配了内存空间,并指定默认初始值:null。
接着执行 Person p2 =new Person();代码创建第二个Person对象,此时因为Person类已经存在于堆内存中了,所以不再需要对Person类进行初始化。
创建第二个Person对象与创建第一个Person对象并没有什么不同。
当程序执行 p1.name ="张三";代码时,将为p1的name属性赋值,也就是让上图中堆内存中name指向一个"张三"的字符串。
执行完成后,两个Person对象在内存中的示意图如下:
(为第一个Person对象的name属性赋值后的存储示意图)
从上图中可以看出:name属性是Person的实例属性,因此修改第一个Person对象的name属性时仅仅与该对象有关,与Person类和其他Person对象没有任何关系。同样,修改第二个Person对象的name属性时,也只修改这个Person对象的name属性,与Person类和其他Person对象无关。
直到执行p1.age=2;代码时,此时通过Person对象来修改Person的类属性,从图5.12中不难看出,Person对象根本没有保存age属性,通过p1访问的age属性,其实还是Person类的age属性。因此,此时修改的是Person类的age属性。修改成功后,内存中的示意图如下:
(设置p1.age的属性后的存储示意图)
通过P1来访问其类属性时,实际上访问的是Person类的age属性。事实上,所有Person实例访问age属性时,都将访问到person类的age。因此,不管是通过Person类访问age属性,还是铜鼓哦任何Person独享来访问age属性,所访问的是同一块内存,Person类和所有的Person对象的age属性都会随之改变。
局部变量的初始化和内存中的运行机制
局部变量定义后,必须经过显示初始化才能使用,系统不会为局部变量执行初始化。这意味着:
定义局部变量之后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中的。
如果局部变量是基本类型的变量,则直接把这个变量的值保存在该变量对应的内存中;
如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过改地址,引用到该变量实际引用的对象或数组。
栈内存中的变量无须系统垃圾回收,栈内存中的变量往往是随方法或代码块的运行结束而结束的。
因此,局部变量的作用域是从初始化该变量开始,到该方法或该代码块运行完成而结束。因此局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常非常小。
PS:成员变量时被放置到堆内存中的,而局部变量放置在栈内存中。
成员变量将被放置到堆内存中,成员变量的作用域扩大到类存在范围或者对象存在范围,有两个害处:
- 增大了变量的生存时间,这将导致更大的系统开销;
- 扩大了变量的作用域,这不利于提高程序的内聚性。
版权声明:本文为博主原创文章,未经博主允许不得转载。