天天看點

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

文章目錄

在Java語言中,根據定義變量位置的不同,可以将變量分成兩大類:成員變量和局部變量。成員變量和局部變量的運作機制存在較大差異。

成員變量指的是聲明在一個類中,但在方法、構造方法和語句塊之外的變量, 局部變量指的是聲明在方法、構造方法或者語句塊中裡的變量。

不管是成員變量還是局部變量,都應該遵守相同的命名規則:從文法角度來看,隻要是一個合法的辨別符即可;但從程式可讀性角度來看,應該遵循駝峰命名法的原則:首個單詞首字母小寫,後面每個單詞首字母大寫。

Java 程式中的變量劃分如圖一:變量被分為類變量和執行個體變量兩種,定義成員變量時沒有 static 修飾的就是執行個體變量,有 static 修飾的就是類變量。

  • 類變量從該類的準備階段起開始存在, 直到系統完全銷毀這個類,類變的作用域與這個類的生存範圍相同;
  • 執行個體變量從該類的執行個體被建立起開始存在,直到系統完全銷毀這個執行個體,執行個體變量的作用域與對應執行個體的生存範圍相同;

圖一:變量分類

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

正是基于這個原因,可以把類變量和執行個體變量統稱為成員變量。

隻要類存在,程式就可以通路該類的類變量。在程式中通路類變量通過如下文法

類.類變量

隻要執行個體存在,程式就可以通路該執行個體的執行個體變量。在程式中通路執行個體變量通過如下文法:

執行個體.執行個體變量

當然,類變量 可以讓該類的執行個體來通路 通過執行個體來通路類變量的文法如下:

執行個體.類變量

但由于這個執行個體并不擁有這個類變量,是以它通路的并不是這個執行個體的變量,依然是通路它對應類的類變量。

也就是說如果通過一個執行個體修改了類變量的值,由于這個類變并不屬于它,而是屬于它對應的類,是以,修改的依然是類的類變量,與通過該類來修改類變量的結果完全相同。

下面程式定義了 Person類, 在這個Person類中定義兩個成員變量,一個執行個體變量 name ,以及一個類變量 eyeNum,并分别通過 Person 類和 Person執行個體來通路執行個體變量和類變量:

執行個體一、PersonTest.java

class Person {
   //定義一個執行個體變量
    public String name;
   //定義一個類變量
    public static int eyeNum;
}

public class PersonTest {
    public static void main(String[] args) {
   //第一次主動使用Person類,該類自動初始化,則eyeNum變量開始起作用,輸出 
        System.out.println("Person 的 eyeNum 類變量值:" + Person.eyeNum);
   //建立Person對象
        Person p = new Person();
   //通過Person對象的引用p來通路Person對象name執行個體變量
   //并通過執行個體通路eyeNum類變量
        System.out.println("p變量的 name變量值是:" + p.name + ";p對象的 eyeNum 變量值是:" + p.eyeNum);
    //直接為name執行個體變量指派
        p.name = "孫悟空";
     //通過P通路eyeNum類變量,依然是通路Person類的eyeNum類變量
        p.eyeNum = 2;
    //再次通過Person對象來通路name執行個體變量和eyeNum類變量
        System.out.println("p變量的 name變量值是:" + p.name + ";P 對象的 eyeNum 變量值是:" + p.eyeNum);
     //前面通過P修改了 Person的eyeNum,此處的Person. eyeNum将輸出2 
        System.out.println("Person類的 eyeNum 類變量值:" + Person.eyeNum);
        Person p2 = new Person();
    // P2通路的eyeNum類變量依然引用Person類的,是以依然輸出2 
        System.out.println("p2對象的 eyeNum類變量值:" + p2.eyeNum);
    }
}      

結果:

Person 的 eyeNum 類變量值:0
p變量的 name變量值是:null;p對象的 eyeNum 變量值是:0
p變量的 name變量值是:孫悟空;P 對象的 eyeNum 變量值是:2
Person類的 eyeNum 類變量值:2
p2對象的 eyeNum類變量值:2      

成員變量無須顯式初始化:隻要為一個類定義了類變量或執行個體變量,系統就會在這個類的準備階段或建立該類的執行個體時進行預設初始化,成員變量預設初始化時的指派規則與數組動态初始化時數組元素的指派規則完全相同。

類變量的作用域比執行個體變量的作用域更大:執行個體變量随執行個體的 存在而存在,而類變量則随類的存在而存在。執行個體也可通路類變量,同一個類的所有執行個體通路類變量時, 實際上通路的是該類本身的同一個變量,也就是說,通路了同一片記憶體區。

局部變量根據定義形式的不同,又可以被分為如下三種。

  • 形參:在定義方法簽名時定義的變量,形參的作用域在整個方法内有效。
  • 方法局部變量:在方法體内定義的局部變量,它的作用域是從定義該變量的地方生效,到該方 法結束時失效。
  • 代碼塊局部變量:在代碼塊中定義的局部變量,這個局部變量的作用域從定義該變量的地方生 效,到該代碼塊結束時失效。

與成員變量不同的是,局部變量除了形參之外,都必須顯式初始化。也就是說,必須先給方法局部變量和代碼塊局部變量指定初始值,否則不可以通路它們。

  • 代碼塊局部變量的作用域是所在代碼塊,隻要離開了代碼塊局部變量所在的代碼塊,這個局部變量就立即被銷毀, 變為不可見
  • 方法局部變量,其作用域從定義該變量開始,直到該方法結束
  • 形參的作用域是整個方法體内有效,而且形參也無須顯式初始化,形參的初始化在調用該方法時由 系統完成,形參的值由方法的調用者負責指定

當通過類或對象調用某個方法時,系統會在該方法棧區内為所有的形參配置設定記憶體空間,并将實參的值賦給對應的形參,這就完成了形參的初始化。

在同一個類裡,成員變量的作用範圍是整個類内有效,

  • 一個類裡不能定義兩個同名的成員變量,即使一個是類變量,一個是執行個體變量也不行;
  • 一個方法裡不能定義兩個同名的方法局部變量,方法局部變量與形參也不能同名;
  • 同一個方法中不同代碼塊内的代碼塊局部變量可以同名;
  • 如果先定義代碼塊局部變量,後定義方法局部變量,前面定義的代碼塊局部變量與後面定義的方法局部變量也可以同名。

Java允許局部變量和成員變量同名,如果方法裡的局部變量和成員變量同名,局部變量會覆寫成員 變量,如果需要在這個方法裡引用被覆寫的成員變量,則可使用this (對于執行個體變量)或類名(對于類變量)作為調用者來限定通路成員變量。

當系統加載類或建立該類的執行個體時,系統自動為成員變量配置設定記憶體空間,并在配置設定記憶體空間後,自動為成員變量指定初始值。

執行個體二、PersonDemo.java

//建立第一個Person對象
       Person pl = new Person();
       //建立第二個Person對象
       Person p2 = new Person();
       //分别對兩個Person對象的name執行個體變量指派
       pl.name = "張三";
       p2.name = "孫悟空";
       //分别為兩個Person對象的eyeNum類變量指派
       pl.eyeNum =2;
       p2.eyeNum = 3;      
  • 當程式執行第一行代碼Person p1 = new Person();時,如果這行代碼是第一次使用Person類,則系統通常會在第一次使用Person類時加載這個類,并初始化這個類。在類的準備階段,系統将會為該類的類變量配置設定記憶體空間,并指定預設初始值。 當Person類初始化完成後,系統内 存中的存儲示意圖如圖一所示。

圖一:初始化 Person 類後的存儲示意圖

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

從圖一中可以看出,當Person 類初始化完成後,系統将在堆記憶體中為Person類配置設定一塊記憶體區(當 Person類初始化完成後,系統會為 Person類建立一個類對象),在這塊記憶體區裡包含了儲存eyeNum類變量的記憶體,并設 置eyeNum的預設初始值:0。

  • 系統接着建立了一個Person 象,并把這個Person對象賦給p1變量,Person對象裡包含了名為name 的執行個體變量,執行個體變量是在建立執行個體 時配置設定記憶體空間并指定初始值的。當 建立了第一個Person對象後,系統記憶體中的存儲示意圖如圖二所示。

圖二:建立第一個 Person 對象後的存儲示意圖

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

從圖二中可以看出,eyeNum類變量并不屬于Person對象,它是屬于Person類的,是以建立第 ―個Person對象時并不需要為eyeNum類變量配置設定記憶體,系統隻是為name執行個體變量配置設定了記憶體空間, 并指定預設初始值:null。

  • 接着執行Person p2 = new Person(); 代碼建立第二個Person對象,此時因為Person類已經存在于堆記憶體中了,是以不再需要對Person類進行初始化。建立第二個Person對象與建立第一個Person對象并 沒有什麼不同。
  • 當程式執行 p1.name =“張三”; 代碼時,将為p1的name執行個體變量指派,也就是讓圖二中堆記憶體中的name指向”張三"字元串。執行完成後,兩個Person對象在記憶體中的存儲示意圖如圖三所示。

圖三:為第一個 Person 對象 name 執行個體變量指派後的存儲示意圖

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

從圖三中可以看出,name執行個體變量是屬于單個Person執行個體的,是以修改第一個Person對象的 name執行個體變量時僅僅與該對象有關,與Person類和其他Person對象沒有任何關系。同樣,修改第二個 Person對象的name執行個體變量時,也與Person類和其他Person對象無關。

  • 直到執行p1.eyeNum = 2;代碼時,此時通過Person對象來修改Person的類變量,從圖三中看出,Person對象根本沒有儲存eyeNum這個變量,通過p1通路的eyeNum類變量,其實還是Person 類的eyeNum類變量。是以,此時修改的是Person類的eyeNum類變量。修改成功後,記憶體中的存儲示 意圖如圖四所示。

圖四:設定 p1 的eyeNum 變量之後的存儲示意圖

Java Review (八、面向對象----成員變量和局部變量)成員變量和局部變量是什麼成員變量的初始化和記憶體中的運作機制局部變量的初始化和記憶體中的運作機制

從圖四中可以看出,當通過p1來通路類變量時,實際上通路的是Person類的eyeNum類變量。 事實上,所有的Person執行個體通路eyeNum類變量時都将通路到Person類的eyeNum類變量,也就是圖四中灰色覆寫的區域,本質其實還是通過Person類來通路eyeNum類變量時,通路的是同一塊記憶體。基于這個理由,當程式需要通路類變量時,盡量使用類作為主調,而不要使用對象作為主調,這樣可以避免程式産生歧義,提高程式的可讀性。

局部變量定義後,必須經過顯式初始化後才能使用,系統不會為局部變量執行初始化。這意味着定 義局部變量後,系統并未為這個變量配置設定記憶體空間,直到等到程式為這個變量賦初始值時,系統才會為局部變量配置設定記憶體,并将初始值儲存到這塊記憶體中。

與成員變量不同,局部變量不屬于任何類或執行個體,是以它總是儲存在其所在方法的棧記憶體中。

  • 如果局部變量是基本類型的變量,則直接把這個變量的值儲存在該變量對應的記憶體中;
  • 如果局部變量是一個 引用類型的變量,則這個變量裡存放的是位址,通過該位址引用到該變量實際引用的對象或數組。

棧記憶體中的變量無須系統垃圾回收,往往随方法或代碼塊的運作結束而結束。是以,局部變量的作用域是從初始化該變量開始,直到該方法或該代碼塊運作完成而結束。因為局部變量隻儲存基本類型的值或者對象的引用,是以局部變量所占的記憶體區通常比較小。

**參考:**

【1】:《瘋狂Java講義》

【2】:《Java核心技術 卷一》

【3】:

https://www.runoob.com/java/java-variable-types.html