成員變數

蜡笔小新Belief發表於2024-08-12

引言

成員變數和區域性變數在每種程式語言中都有涉及,如果之前瞭解過其他語言的成員變數或者區域性變數,那麼在學習java中的成員變數和區域性變數時可以看看有那些聯絡和不同,這一塊的東西也不能說難,如果第一次接觸可能會感覺有點亂,所以接下來我先把java中總結的成員變數和區域性變數的部分內容先列出來,帶著這些內容學起來會輕鬆很多!

java變數分類

QQ_1722482587965

JVM中的主要記憶體空間

QQ_1722482643171

三大變數記憶體分配情況

QQ_1722482699222

三區介紹

棧區

存放各種方法(靜態方法、例項方法、構造方法等)
ps:區域性變數就在這些方法體中;

堆區

存放new出來的物件(例項);

方法區

存放各個類,且靜態變數在此初始化;

成員變數是在 類 中定義的變數 區域性變數是在 方法 中定義的變數

成員變數
成員變數分為靜態變數(類變數) 和 例項變數
靜態變數有static修飾,它從該類的準備階段就存在了,直到系統銷燬這個類,靜態變數的作用域與這個類的生存範圍相同;
例項變數沒有static修飾,它從該類的例項被建立時就存在,直到系統銷燬這個例項,例項變數的作用域與對應例項的生存範圍相同;

靜態變數訪問方法:透過類名訪問,不需要建立例項;

類.靜態變數

例項變數訪問方法:透過例項訪問,需要先new一個例項

例項.例項變數

這裡主要說一下成員變數初始化時在記憶體中執行機制;
先看一個程式碼:

public class MemoryShow {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        Person.name = "李四";
        System.out.println("姓名:" + Person.name);
        System.out.println("p1年齡:" + p1.age);
        System.out.println("p2年齡:" + p2.age);
    }
}
class Person {
    //靜態變數
    static String name = "張三";
    //例項變數
    int age;
}

簡單分析一下,當程式第一次執行Person類時,系統先載入這個類,並初始化這個類,在類的準備階段,系統就為該類的類變數分配記憶體空間,並指定預設初始值(靜態變數name也是在這個階段完成了初始化);
然後接下來系統就在堆記憶體中為Person類分配了一塊記憶體區,且為age預設賦值為0;然後生成了Person物件,並透過引用變數p1指向該物件;
當再次執行Person類時,已經不需要再為Person類初始化了,所以直接生成了Person物件,透過p2指向它;

區域性變數
區域性變數相對成員變數就沒有那麼複雜了
先分別介紹一下區域性變數中的三種不同形式:
形參: 在定義方法簽名時定義的變數,形參的作用域在整個方法內有效
方法區域性變數: 在方法體中定義的變數,作用域從定義該變數的地方生效,到該方法結束時失效
程式碼塊區域性變數: 在程式碼塊中定義的變數,作用域從定義該變數的地方生效,到該程式碼塊結束時失效

和成員變數不同的一點是:區域性變數除了形參外,都需要顯式初始化,就是指定一個初始值,否則無法訪問;

區域性變數在記憶體中的執行機制:
因為區域性變數需要顯式初始化,所以系統不會對它進行初始化,即系統並沒有給區域性變數分配記憶體空間,只有它賦值後,系統才會分配記憶體將該值放入其中;
因為區域性變數不屬於任何物件或者類,所以它存放在棧記憶體中,且棧記憶體的變數不需要系統垃圾回收,因為它們會隨著方法或者程式碼塊執行結束而結束;所以區域性變數只儲存基本型別或者物件的引用(引用變數),所以區域性變數佔用記憶體比較小;

java語法允許區域性變數和成員變數重名,但是如果在一個方法裡,區域性變數會覆蓋成員變數;
如果想要在該方法裡訪問成員變數,就需要透過this引用(針對例項變數)或者類名(針對靜態變數)作為呼叫者來限定訪問成員;
看一個程式碼就明白了

public class RepeatTest {
    // 靜態變數(類變數)
    static String name = "張三";
    // 例項變數 
    int age = 18;
 
    public static void main(String[] args) {
        // 區域性變數   和靜態變數name = "張三"重名
        String name = "李四";

        // 因為靜態變數name被這裡的區域性變數覆蓋,所以輸出為“李四”
        System.out.println("姓名:" + name);

        // 這時候如果想要呼叫靜態變數可以使用類來呼叫
        //不能用this呼叫的原因是:main方法是靜態方法
        System.out.println("姓名:" + RepeatTest.name);

        // 呼叫test方法
        new RepeatTest().test();
    }
    public void test() {
        // 區域性變數   和例項變數age = 18重名
        int age = 666;
        // 因為例項變數name被這裡的區域性變數覆蓋,所以輸出為“666”
        System.out.println("年齡:" + age);
        // 這時候如果想要呼叫例項變數可以使用this呼叫
        System.out.println("年齡:" + this.age);
    }
}


變數使用

說了這麼多,可能你已經暈了,但是這些變數歸根結底我們瞭解它們就是為了用它們,那什麼時候用區域性變數,什麼時候用成員變數呢?
我們先看三個程式碼:

public class ScopeTest01 {
    //靜態變數(類成員變數)
    //再次強調一下這裡為什麼定義靜態變數而不是例項變數
    //因為main方法是靜態方法,不需要物件只透過類呼叫,而例項變數必須有物件才能呼叫
    static int i;
    public static void main(String[] args) {
        for (i = 0; i < 5; ++i) {
            System.out.println("Hello World!!!");
        }
    }
}

public class ScopeTest02 {
    public static void main(String[] args) {
        //區域性變數
        int i;
        for (i = 0; i < 5; ++i) {
            System.out.println("Hello World!!!");
        }
    }
}

public class ScopeTest03 {
    public static void main(String[] args) {
        // 程式碼塊區域性變數
        for (int i = 0; i < 5; ++i) {
            System.out.println("Hello World!!!");
        }
    }
}

這三個程式碼的執行結果都是一樣的,而它們分別用了成員變數和區域性變數,結果都一樣,那我們就需要看看那種方法是最好的了;

首先,ScopeTest01 使用的是成員變數,我們都知道成員變數存在於堆記憶體中,且只有類銷燬時或者例項銷燬時它才銷燬,這就將作用域擴大到類存在範圍或者例項存在範圍,作用域的擴大有兩個壞處:

增加了變數的生存時間,會導致更大的記憶體開銷
擴大了變數的作用域,不利於提高程式的內聚性
同理可以對比ScopeTest02 和 ScopeTest03,最後可以總結出 ScopeTest03 最符合規範;

所以定義變數的時候要儘可能的保證作用範圍最小,這樣可以很好的提高程式的效能,包括區域性變數;

考慮使用成員變數有四種情況:

如果定義的變數需要描述物件的資訊,且每個物件都有可能不同,那麼用成員變數中的例項變數;
如果定義的變數所描述的資訊對這個類的所有物件都相同,那麼類相關的資訊就定義為成員變數中的靜態變數;
如果某個類中需要一個變數儲存該類或者例項執行時的狀態資訊,該變數定義為成員變數;
如果某個資訊需要在類的多個方法之間共享,則該資訊使用成員變數
總結
成員變數和區域性變數大概主要內容就是這些了,看完可能還是有點亂,一定要多看幾遍,自己總結一下,這些東西是學習java的基礎內容,這都搞不清的話,那麼之後一會一個靜態變數,一會一個區域性變數都把你搞暈了;並且想要用java寫出來好程式碼,就必須瞭解這些內容,這將會在你未來的開發中起到重要作用;
希望我們一起進步!!!

相關文章