详解JAVA成员变量和局部变量

 我们首先要理解下概念:    

     成员变量指的是类范围里定义的变量,也就是前面所说的属性;局部变量指的是一个方法内定义的变量。不管是成员变量、还是局部变量,都应该遵守相同的命名规则:从语法角度来看,只要一个合法的标识符即可,但我们应该知道,从程序可读性角度来看,应该是多个意义的单词连缀而成,其中一个单词首字母小写,后面每个单词首字母大写。

 

如图:

                       

 

成员变量被分为类属性和实例属性两种,定义一个属性时不使用static修饰的就是实例属性,使用static修饰的就是类属性。其中类属性从这个类的准备阶段起开始存在,直到系统完全销毁这个类,类属性的作用域与这个类的生存范围相同; 而实例属性则从这个类的实例被创建开始起存在,直到系统完全销毁这个实例,实例属性的作用域与对应实例的生存范围相同。

 

提醒大家注意的是:一个类在使用之前要经过类加载、类验证、类准备、类解析、类初始化等几个阶段。

 

正是基于这个原因,我们把类属性和实例属性统称为成员变量,其中类属性可以理解为类成员变量,它作为类的一个成员,与类共存亡;实例属性则可理解为实例成员变量,它作为实例的一个成员,与实例共存亡。

 

(1)只要类存在,程序就可以访问该类的类属性,在程序中访问类属性通过如下格式:

类.类属性;

 

(2)只要实例存在,程序就可以访问该实例属性,在程序中访问实例属性通过如下格式:

实例.实例属性

 

(3)当然,类属性也可以让该类的实例来访问,通过实例来访问类属性的语法格式:

实例.类属性

 

但这个实例访问并不是这个实例的属性,依然是访问它对应类的类属性。也就是说,如果通过一个实例修改了类属性的值,由于这个类属性并不属于实例,而是属于实例对应的类。因此,修改的依然是类的类属性,与通过该类来修改类属性的结果完全相同,这会导致该类的其他实例来访问这个类属性时也将获得这个被修改过的值。

 

通过了理论理解,现在看代码:

 

class Person

{

      //定义一个实例Field, 因为代码没有被static修饰

      public String name;

 

      //定义一个类Field

      public static int eyeNum;

}

 

public class PersonTest

{

      public static void main(String[] args)

      {

           //Person类已经初始化了,则eyeNum变量起作用了,输出0

           System.out.println("Person的eyeNum类Field值:" + Person.eyeNum+"\n");

            //因为是类的field(eyeNum)所以可以直接调用

 

           //创建Person对象

           Person p = new Person();            //用来调用非static修饰的属性

 

           //通过Person对象的引用p来访问Person对象name实例Field

           //并通过实例访问eyeNum类Field

 

          System.out.println("p变量的name Field值是:" + p.name
   + " p对象的eyeNum Field值是:" + p.eyeNum+"\n");

 

          //直接为name实例Field赋值

          p.name = "孙悟空";

 

          //通过p访问eyeNum类Field,依然是访问Person的eyeNum类Field

          p.eyeNum = 2;

 

         //再次通过Person对象来访问name实例Field和eyeNum类Field

        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");

      }

}

 

编译结果:

 

 

 

从上面程序中来看,成员变量无须显式初始化,只要为了一个类定义了类属性或实例属性,则系统会在这个类的准备阶段或创建这个类的实例时进行默认初始化,成员变量默认初始化时的赋值规则与数组动态初始化时数组元素的赋值规则完全相同.

 

这时我们发现类属性的作用域比实例属性的作用域更大:实例属性随实例的存在而存在,而类属性则随类的存在而存在.实例也可访问类属性,同一个类的所有实例访问类属性时,实际上访问的是同一个类属性,因为它们实际上都是访问该类的类属性.

 

局部变量:

 

根据定义形式的不同,分为三种:

形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效.

 

方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量 的地方生效,到该方法结束时消失

 

代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效.

 

与成员变量 不同的是,局部变量除了形参之外,都必须显式初始化.也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它们.

 

看代码:

public class BlockTest

{

        public static void main(String[] args)

        {

              //在主方法中通过大括号定义一个局部代码块

 

              {

                    //定义一个代码块局部变量a

                     int a;

                     //下面代码将出现错误,因为a变量还未初始化

                     System.out.println("代码块局部变量a的值:" + a);

   /*****

 

 

 

  */                 

                     //为a变量赋初始值,也就是进行初始化

                    a = 5;

                    System.out.println("代码块局部变量a的值:" + a);

 

                   

              }

              //下面试图访问的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;

           System.out.println("方法局部变量a的值:" + a);

      }

}

看下编译结果:

 

 

 

 

形参的作用域是整个方法体内有效,而且形参也无须显式初始化,形参的初始化在调用该方法时由系统完成。

   当通过类或对象调用某个方法时,系统会在该方法栈区内为所有形参分配内存空间,并将实参的值赋给对应的形参,这样就完成了形参的初始化。

 

我提醒大家注意: 在同一个类里,成员变量的作用范围是整个类内有效,一个类里不能定义两个同名的成员变量,即使一个是类属性,一个是实例也不行;一个方法里不能定义两个同名的局部变量,即使一个是方法局部变量,一个是代码块局部变量或者形参也不行。

 

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

 

 

public class VariableOverrideTest

{

      //定义一个name实例Field

       private String name = "世界";

 

      //定义一个price类Field

      private static double price = 78.0;

 

      //主方法,程序的入口

       public static void main(String[] args)

       {

              //方法里的局部变量,局部变量覆盖成员变量

              int price = 65;

              //直接访问price变量,将输出price局部变量的值:65

              System.out.println("这时price输出的是局部变量的值:"+price);

 

              //使用类名作为price变量的限定

              //将输出price类Field的值:78.0

              System.out.println(VariableOverrideTest.price);

            

       }

 

      public void info()

      {

            //方法里的局部变量,局部变量覆盖成员变量

            String name = "孙悟空";

 

           //直接访问name变量,将输出name局部变量的值:"孙悟空"

           System.out.println(name);

 

           //使用this来作为name变量的限定,

           //将输出price实例Field的值:"世界"

           System.out.println(this.name);

       }

 }

 

看下编译结果:

 

 

 

从上面的那段程序我们可以清楚地看出局部变量覆盖成员变量,不过依然可以在方法中显式指定类名和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类初始化完成后,系统内存中的存储图:

 

 

从图可以看出,当Person类初始化完成后,系统将在堆内存中为Person类分配一块内存区(当Person类初始化完成后,系统会隐含地为Person类创建一个类对象,在这块内存区里包含了保存类属性eyeNumr 内存,并设置eyeNum的默认初始值:0)

 

系统接着创建了一个Person对象,并把这个Person对象赋给P1变量,Person对象里包含了名为name的实例属性,实例属性是在创建实例时分配内存空间,并指定初始值的。当创建了第一个Person对象时,系统的内存图如下:

 

 

从上图可以看出,因为eyeNum类属性并不属于Person对象,它是属于person类的,所以创建第一个Person对象时并没有为eyeNum类属性分配内存,系统只是为name实例属性分配了内存空间,并指定默认初始值:null.

 

接着执行Person p2 = new Person();代码创建第二个Person对象,此时因为Person类已经存在于堆内存中了,所以不再需要对Person类进行初始化。创建第二个Person对象与创建第一个Person对象并没有什么不同。

 

当程序执行p1.name="世界";代码时,将为p1的name属性赋值,也就是堆内存中的name指向一个“世界”的字符串,执行完成后,两个Person对象在内存中的图如下:

 

 

从图中我们可以理解到,name属性是Person的实例属性,因此修改第一个Person对象的name属性时仅仅与该对象有关,与Person类和其他Person对象没有任何关系。同样,修改第二个Person对象的name属性时,也只修改这个Person对象的name属性,与Person类和其他Person对象无关。

 

直到执行p1.eyeNum=2 ;代码时,此时通过Person对象来修改Person的类属性,从前面的图看出,Person对象根本没有保存eyeNum属性,通过p1访问的eyeNum属性,其实还是Person类的eyeNum属性。如果我们悠久Person类的eyeNum属性。修改成功后,内存图:

 

 

从图中可以看出,当通过P1来访问其类属性时,实际上访问的是Person类的eyeNum属性。事实上,所有Person实例访问eyeNum属性时都将访问到Person类的eyeNum属性,因此不管是Person类访问eyeNum属性,还是通过任何Person对象来访问eyeNum属性,所访问的是同一块内存,如果修改数值后,Person类和所有的Person对象的eyeNum属性都会随之改变(因为它们所引用的内存地址是一样的)。

 

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

局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化,这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会局部变量分配内存,并将初始化值保存到这块内存中。

 

与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中的。如果局部变量 是基本类型的变量,则直接把这个变量的值保存在该变量对应内存中;如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对象或数组。

 

栈内存中的变量无须系统垃圾回收,栈内存中的变量往往是随方法或代码块的运行结束而结束的。因此,局部变量的作用域是从初始化该变量开始,直到该方法或该代码块运行完成而结束。因为局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常比较小。

 

 

好了写到这里了,有点累,概念性的东西太多了,不写又不行; 不然大家看到会不明白。

posted @ 2013-05-04 22:50  溜溜小毛驴  阅读(413)  评论(0编辑  收藏  举报