1. 程式人生 > >Java例項變數和類變數

Java例項變數和類變數

Java程式的變數大體可分為成員變數和區域性變數。其中區域性變數可分為如下3類。

  • 形參:在方法簽名中定義的區域性變數,由方法呼叫者負責為其賦值,隨方法的結束而消亡。
  • 方法內的區域性變數:在方法內定義的區域性變數,必須在方法內對其進行顯示初始化。這種型別的區域性變數從初始化完成後開始生效,隨方法的結束而消亡。
  • 程式碼塊的區域性變數:在程式碼塊內定義的區域性變數,必須在程式碼塊內對其進行顯式初始化,這種型別的區域性變數從初始化完成後開始生效,隨程式碼的結束而消亡。

區域性變數的作用時間很短暫,它們都被儲存在方法的棧記憶體中。類體內定義的變數被稱為成員變數(英文是Field)。如果定義該成員變數時沒有使用static修飾,該成員變數又被稱為非靜態變數或例項變數;如果使用了static修飾,則該成員變數又可被稱為靜態變數或類變數

對於static關鍵字而言,從詞義上來看,它是“靜態”的意思。但從Java程式的角度來看,static的作用就是將例項成員變為類成員。static只能修飾在類裡定義的成員部分,包括成員變數、方法、內部類、初始化塊、內部列舉類。如果沒有使用static修飾這裡類裡的成員,這裡成員屬於該類的例項;如果使用了static修飾,這些成員就屬於類本身。從這個意義上看,static只能修飾類裡的成員,不能修飾外部類,不能修改區域性變數、區域性內部類。

表明上看,Java類裡定義成員變數時沒有先後順序,但實際上Java要求定義成員變數時必須採用合法的前後引用。示例如下

public class
ErrorDef {
int num1 = num2 + 5; //Cannot reference a field before it is defined int num2 = 20; }

上面程式中定義num1成員變數的初始值時,需要根據num2變數的值進行計數,這就是“非法前後引用”。
類似地,兩個類變數也不允許採用這樣“非法前後引用”,示例如下。

public class ErrorDef {
    static int num1 = num2 + 5; //Cannot reference a field before it is defined
    static
int num2 = 20; }

但如果一個是例項變數,一個是類變數,則例項變數總是可以引用類變數,示例如下

public class RightDef {
    int num1 = num2 + 5;
    static int num2 = 20;
}

上面程式中num1是一個例項變數,而num2是一個類變數。雖然num2位於num1之後被定義,但nun1的初始值卻可根據num2計算得到。這是因為,num2變數是一個類變數,num1是例項變數,而類變數的初始化時機總是處於例項變數的初始化時機之前。所以,雖然原始碼中先定義了num1,再定義了num2,但num2的初始化時機總是位於num1之前,因此num1變數的初始化可根據num2的值計算得到

例項變數和類變數的屬性

使用static修飾的成員變數是類變數,屬於該類本身;沒有使用static修飾的成員變數是例項變數,屬於該類的例項。在同一個JVM內,每個類只對應一個Class物件,但每個類可以建立多個Java物件。

由於同一個JVM內每個類只對應一個Class物件,因此同一個JVM內的一個類的類變數只需一塊記憶體空間;但對於例項變數而言,該類每建立一次例項,就需要為例項變數分配一塊記憶體空間。也就是說,程式中有幾個例項,例項變數就需要幾塊記憶體空間。

下面程式可以很好地表現出來例項變數屬於物件,而類變數屬於類的特性。


class Person {
    static int eyeNum;
    String name;
    int age;

    public void info() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}


public class FieldTest {
    public static void main(String[] args) {
        // 類變數屬於該類本身,只要改類初始化完成,程式即可使用類變數
        Person.eyeNum = 2; // ①

        // 通過Person類訪問 eyeNum類變數
        System.out.println("Person的eyeNum屬性:" + Person.eyeNum);

        // 建立第一個Person物件
        Person p1 = new Person();
        p1.name = "zhangsan";
        p1.age = 22;

        System.out.println("通過p1變數訪問eyeNum類變數:" + p1.eyeNum); // ②
        p1.info();

        // 建立第二個Person物件
        Person p2 = new Person();
        p2.name = "lisi";
        p2.age = 30;
        p2.info();

        // 通過p2修改Person類的eyeNum類變數
        p2.eyeNum = 4; // ③

        System.out.println("通過p1變數訪問eyeNum類變數:" + p1.eyeNum);
        System.out.println("通過p2變數訪問eyeNum類變數:" + p2.eyeNum);
        System.out.println("通過Person類訪問eyeNum類變數:" + Person.eyeNum);
    }
}

上面程式中①行程式碼直接對Person類的eyeNum類變數賦值。這沒任何問題,因為eyeNum類變數是屬於Person類的,當Person類初始化完成後,eyeNum類變數也隨之初始化完成。因此,程式即可對該類變數賦值,也可訪問該類變數的值。

執行①行程式碼之後,程式的記憶體分配如圖所示。

這裡寫圖片描述

一旦Person類初始化完成,程式即可通過Person類訪問eyeNum類變數。除此之外,Java還允許通過Person類的任意例項來訪問eyeNum類變數

雖然Java允許通過Person物件來訪問Person類的eyeNum類變數,但由於Person物件本身並沒有eyeNum類變數(只有例項變數才屬於Person例項),因此程式通過Person物件來訪問eyeNum類變數時,底層依然會轉換為通過Person訪問eyeNum類變數。也就是說,不管通過哪個Person物件來訪問eyeNum類變數,都與通過Person類訪問eyeNum類變數的效果完全相同。因此,在②行程式碼處通過p1來訪問eyeNum變數將再次輸出2.

執行完②行程式碼後,程式的記憶體分配如圖

這裡寫圖片描述

從圖中可以看出,當程式建立Person物件時,系統不再為eyeNum類變數分配記憶體空間,執行初始化,而只為Person物件
的例項變數執行初始化 – 因為例項變數才屬於Person例項,而類變數屬於Person類本身。

當Person類初始化完成之後,類變數也隨之初始化完成,以後不管程式建立多少個Person物件,系統不再為eyeNum類變數分配記憶體,但程式每建立一個Person物件,系統將再次為name、age例項變數分配記憶體,並執行初始化。

當程式執行完③行程式碼之後,記憶體中再次增加了一個Person物件。當程式通過p2對eyeNum類變數進行賦值時,實際上依然是對Person類的eyeNum類變數進行賦值。此時程式的記憶體分配如圖所示。

這裡寫圖片描述

當Person類的eyeNum類變數被改變之後,程式通過p1、p2、Person類訪問eyeNum類變數都將輸出4。這是由於,不管通過哪個Person物件來訪問eyeNum類變數,底層都將轉換為通過Person來訪問eyeNum類變數。由於p1和p2兩個變數指向不同的Java物件,當通過它們訪問例項變數時,程式將輸出不同的結果。

例項變數的初始化時機

對於例項變數而言,它屬於Java物件本身,每次程式建立Java物件時都需要為例項變數分配記憶體空間,並執行初始化。
從程式執行的角度來看,每次建立Java物件都會為例項變數分配記憶體空間,並對例項變數執行初始化。
從語法角度來看,程式可以在3個地方對例項變數執行初始化:

  • 定義例項變數時指定初始值
  • 非靜態初始化塊中對例項變數指定初始值
  • 構造器中對例項變數指定初始值

其中第1、2種方式(定義時指定的初始值和非靜態初始化塊中指定的初始值)比第3種方式(構造器中指定初始值)更早執行,但第1、2中方式的執行順序與他們在源程式中的排列順序相同。


class Cat {
    String name;
    int age;

    public Cat(String name, int age) {
        System.out.println("執行非靜態初始化塊");
        this.name = name;
        this.age = age;
    }

    {
        System.out.println("執行構造器");
        weight = 3.0;
    }

    double weight = 2.5;

    public String toString() {
        return "Name: " + name + ", Age: " + age + "Weight: " + weight;
    }
}


public class InitTst {
    public static void main(String[] args) {
        Cat cat1 = new Cat("tom", 3); // ①
        System.out.println(cat1);

        Cat cat2 = new Cat("jiafei", 2); // ②
        System.out.println(cat2);
    }
}

每當程式呼叫指定構造器來建立Java物件時,該構造器必然會獲得執行的機會。除此之外,該類所包含的非靜態初始化塊將會獲得執行的機會,而且總是在構造器執行之前獲得執行。

當程式執行①行程式碼建立第一個Cat物件的時候,程式將會先執行Cat類的非靜態初始化塊,再呼叫該Cat類的構造器來初始化該Cat例項。執行完①行程式碼後的記憶體分配如圖所示。

這裡寫圖片描述

從圖中可以看出,該Cat物件的weight例項變數的值為2.5,二不是初始化塊中指定的。這是因為,初始化塊中指定初始值,定義weight時指定初始值,都屬於對該例項變數執行的初始化操作,他們的執行順序與它們的順序相同。在本程式中,初始化塊中對weight的賦值位於定義weight語句之前,因此程式將先執行初始化塊中的初始化操作,執行完成後weight例項變數的值為3.0,然後再執行定義weight時指定的初始值,執行完成後weight例項變數的值為2.5。 從這個意義上來看,初始化塊中對weight所指定的初始化值每次都將被2.5所覆蓋。

當執行②行程式碼再次建立一個Cat物件時,程式將再一次呼叫非靜態初始化塊、相應的構造器來初始化Cat物件。

執行完②行程式碼後,程式的記憶體分配如圖所示。

這裡寫圖片描述

類變數的初始化時機

例項變數屬於Java類本身,只有當程式初始化該Java類時才會為該類的類變數分配記憶體空間,並執行初始化。
從程式執行的角度來看,每JVM對一個Java類只初始化一次,因此Java程式每執行一次,系統只為類變數分配一次記憶體空間,執行一次初始化。
從語法角度來看,程式可以在2個地方對類變數執行初始化:

  • 定義類變數時指定初始值
  • 靜態初始化塊中對類變數指定初始值。

這兩種方式的執行順序與它們在源程式中的排列順序相同。

public class StaticInitTest {
    // 定義count類變數,定義時指定初始值
    static int count = 2;

    // 通過靜態初始化塊為name類變數指定初始值
    static {
        System.out.println("StaticInitTest的靜態初始化塊");
        name = "hello";
    }

    // 定義name類變數時指定初始值
    static String name = "itmyhome";

    public static void main(String[] args) {
        System.out.println("count類變數的值" + StaticInitTest.count);
        System.out.println("name類變數的值" + StaticInitTest.name);
    }
}

靜態初始化塊中為類變數指定初始值,每次執行該程式,系統將會對StaticInitTest類執行初始化:先為所有類變數分配記憶體空間,再按原始碼中的排序執行靜態初始化塊中所指定的初始值和定義類變數時所指定的初始值.

對於本例程式而已,靜態初始化誇中對name變數的指定初始值位於定義name變數時指定初始值之前,因此係統先將name類變數賦值為“hello”,然後再將該name類變數賦值為“itmyhome”。每執行該程式一次,這個初始化過程只執行一次,因此執行上面程式將看到輸出name類變數的值為“itmyhome”.

下面程式更清楚地表現了類變數的初始化過程。首先定義了Price類,該Price類裡有一個靜態的initPrice變數,用於代表初始價格。每次建立Price例項時,系統會以initPrice為基礎,減去當前打折價格(由discount引數代表)即得到該Price的currentPrice變數值


class Price {
    // 類成員是Price例項
    final static Price INSTANCE = new Price(2.8);

    // 再定義一個類變數
    static double initPrice = 20;

    // 定義該Price的currentPrice例項變數
    double currentPrice;

    public Price(double distinct) {
        // 根據靜態變數計算例項變數
        currentPrice = initPrice - distinct;
    }
}


public class PriceTest {
    public static void main(String[] args) {
        // 通過Price的INSTANCE訪問currentPrice例項變數
        System.out.println(Price.INSTANCE.currentPrice); // ①

        // 建立Price例項
        Price p = new Price(2.8);
        // 通過建立的Price例項訪問currentPrice例項變數
        System.out.println(p.currentPrice); // ②
    }
}

上面程式中①、②行程式碼都訪問Price例項的currentPrice例項變數,而且程式都是通過new Price(2.8)來建立Price例項的。表面上看,程式輸出兩個Price的currentPrice都應該返回17.2(由20減去2.8得到),但實際上執行程式並沒有輸出兩個17.2,而是輸出-2.8和17.2

如果僅僅停留在程式碼表面來看這個問題,往往很難得到正確的結果,下面從記憶體角度來分析這個程式。第一次用到Price類時,程式開始對Price類進行初始化,初始化分成以下2個階段。

  • (1)系統為Price的兩個類變數分配記憶體空間。
  • (2)按初始化程式碼(定義時指定初始化值和初始化塊中執行初始值)的排列順序對類變數執行初始化。

初始化第一階段,系統先為INSTANCE、initPrice兩個類變數分配記憶體空間,此時INSTANCE、initPrice的預設值null和0.0。截止初始化進入第二個階段,程式按順序依次為INSTANCE、initPrice進行復制。對INSTANCE賦值時要呼叫Price(2.8),建立Price例項,此時立即執行程式中③程式碼為currentPrice進行賦值,此時initPrice類變數的值為0,因此賦值的結果是currentPrice等於-2.8。接著,程式再次將initPrice賦為20,但此時對INSTANCE的currentPrice例項變數以及不起作用了。

當Price類初始化完成後,INSTANCE類變數引用到一個currentPrice為-2.8的Price例項,而initPrice類變數的值為20.0。當再次建立Price例項時,該Price例項的currentPrice例項變數的值才等於20.0

相關推薦

java例項變數變數的區別

最近在學習java虛擬機器,有點搞不清例項變數和類變數的區別,特此記錄一下 例項變數 個人理解有點像成員變數,在建構函式的時候進行初始化 1.例項變數宣告在一個類中,但在方法、構造方法和語句塊之外; 2.當一個物件被例項化之後,每個例項變數的值就跟著確定; 3.例項變數在物件

Java例項變數變數

Java程式的變數大體可分為成員變數和區域性變數。其中區域性變數可分為如下3類。 形參:在方法簽名中定義的區域性變數,由方法呼叫者負責為其賦值,隨方法的結束而消亡。 方法內的區域性變數:在方法內定義的區域性變數,必須在方法內對其進行顯示初始化。這種型別的區域

JAVA中的變數----例項變數變數

java中的變數分為2種:一種是成員變數,一種是區域性變數。 成員變數是在類內定義的變數,成員變數有分為兩種, 如果是用static修飾的就是靜態變數或者叫類變數;沒有被static修飾就是非靜態變數或者叫例項變數。 區域性變數包括3種: 方法的形參,通

python中的例項變數變數以及區別

1,例項變數 :可以通過self點出的變數全部為例項變數。例項變數就是物件,呼叫方式:通過物件名呼叫。一個物件的值改變不影響另 外一個物件值 2,例項變數的生命週期:物件銷燬,物件執行完,物件被後面的覆蓋,例項變數就銷燬。, 3,類變數的生命週期:隨著類存在,是要類不刪除

例項變數變數的區別

  Java類體中的成員變數可以分為例項變數和類變數。其中類變數需用static修飾,否則則為例項變數。類變數又稱為static變數或者靜態變數。例如: class Book{ string name;             //例項變數 static int a=0;

例項變數變數方法例項方法

類體中包括成員變數和區域性變數,而成員變數又可以細分為例項變數和類變數,在宣告成員變數的時候,用static給予修飾的稱作類變數,否則稱作例項變數。(類變數也稱為static變數,靜態變數) 那麼,類變數和例項變數有什麼區別呢? 我們知道,一個類通過使用ne

java 成員變數變數的區別

由static修飾的變數稱為靜態變數,其實質上就是一個全域性變數。如果某個內容是被所有物件所共享,那麼該內容就應該用靜態修飾;沒有被靜態修飾的內容,其實是屬於物件的特殊描述。不同的物件的例項變數將被分配不同的記憶體空間, 如果類中的成員變數有類變數,那麼所有物件的這個類變數都

Java三大變數分別是變數例項變數區域性變數

一、什麼是變數:就是內容可以改變的量,它與常量相對應。而這三大變數實際上是從變數的作用域來定義和劃分的。 1、類變數,是歸屬類的變數,它是通過在定義類的屬性的時,增加static修飾符,所以又稱為靜態變數。類變數不僅可以直接通過類名+點操作符+變數名來操作,也

Java學習:“this”的引用及變數例項變數區域性變數

class caculate { static int staticVar = 0; //類變數、靜態變數 static final float STATIC_CONSTANT = 1; /

JAVA例項變數變數的區別,例項方法方法的區別

class TiXing{ private float up,height; private static float down; TiXing(float x,float y,float z){ up=x; height=y;

1.java區域性變數 & 例項變數 & 變數(靜態變數

 區域性變數 區域性變數宣告在方法、構造方法或者語句塊中; 區域性變數在方法、構造方法、或者語句塊被執行的時候建立,當它們執行完成後,變數將會被銷燬; 訪問修飾符不能用於區域性變數; 區域性變數只在宣告它的方法、構造方法或者語句塊中可見; 區域性變數是

java基礎1-成員變數區域性變數

成員變數:寫在類體的裡面,方法體的外面,宣告時可以不進行初始化值,可以被本類或其他類的方法進行呼叫。區域性變數:寫在方法體的裡面,宣告時必須進行初始化,只能在宣告區域性變數的方法內進行呼叫。 public class Student { String name; int age=20; b

java基礎———與物件,成員變數區域性變數,封裝及關鍵字staticthis

類與物件,成員變數和區域性變數,封裝及關鍵字static和this 面向物件的思想 類與物件及其應用 物件的記憶體圖 成員變數和區域性變數的區別 匿名物件 封裝(private) this關鍵字 構造方法 物件的建立步驟

Java筆記:成員變數,區域性變數變數例項變數以及注意事項

區域性變數:方法中定義的變數。 成員變數(類似C中的全域性變數):成員變數定義在方法體和語句塊之外。成員變數就概括描述了類中的變數,不區分static。是以下變數的統稱。 類變數(靜態變數):獨立於方法之外的變數,屬於類本身。需要static修飾,事實上,類變數就是以static修飾的獨立於方法之外的成員

Java基礎(四)Java的成員變數區域性變數

在Java中,成員變數和區域性變數存在較大的差異性。首先,我們來看一下變數的分類圖: 成員變數 成員變數被分為:類屬性和例項屬性。 例項屬性:定義一個屬性時,不使用static修飾的就是例項屬性, 類屬性:定義一個屬性時,使用static修飾的是類屬性。 類屬性從這

Java例項變數區域性變數未初始化的情況

例項變數會有預設初始值(0,false,null,'\u0000'等),區域性變數不會有預設初始值, public class test01 { private String s; private int [] year = new int[3]; public

Java例項變數變數與區域性變數

一、例項變數 也叫物件變數、類成員變數;從屬於類由類生成物件時,才分配儲存空間,各物件間的例項變數互不干擾,能通過物件的引用來訪問例項變數。但在Java多執行緒中,例項變數是多個執行緒共享資源,要注意同步訪問時可能出現的問題。 <span style="font-si

Java千百問_03基本語法(001)_區域性變數變數例項變數有什麼區別

區域性變數、類變數、例項變數有什麼區別 在聊區域性變數、類變數、例項變數有什麼區別之前,我們需要了解一下Java變數。 1、Java變數是什麼 在數學世界中,我們知道有常量、變數。 舉一個例

java 區域性變數變數例項變數的作用域,生命週期

1,區域性變數 區域性變數是指定義在方法或程式碼塊中的變數,區域性變數必須初始化,在方法或程式碼塊內有效,之外則無效,方法執行開始入棧時建立,執行完畢出棧時銷燬。 2,例項變數 其作用域受限定符限定,Private的只能在本類中使用,protected子類可用,publi

分析java中的(static)變數(static)方法

靜態方法和例項方法的區別主要體現在兩個方面: 在外部呼叫靜態方法時,可以使用"類名.方法名"的方式,也可以使用"物件名.方法名"的方式。而例項方法只有後面這種方式。也就是說,呼叫靜態方法可以無需建立物件。 靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變數和靜態方法),而不允許訪問例項成員變