瘋狂Java筆記之對象及其內存管理

(復習瘋狂Java的筆記)

1.實例變量和類變量

Java程序的變量大體可分為成員變量和局部變量。其中局部變量可分為如下二類。

  • 形參:在方法簽名中定義的局部變量,由方法調用者負責為其賦值,隨方法的結束而消亡。
  • 方法內的局部變量:在方法內定義的局部變量,必須在方法內對其進行顯式初始化口這種類型的局部變量從初始化完成后開始生效,隨方法的結束而消亡。
  • 代碼塊內的局部變量:在代碼塊內定義的局部變量,必須在代碼塊內對其進行顯式初始化。這種類型的局部變量從初始化完成后開始生效,隨代碼塊的結束而消亡。

局部變量的作用時間很短暫,他們都被存儲在棧內存中。

類體內定義的變量被稱為成員變量〔英文是Field)。如果定義該成員變量時沒有使用static
修飾,該成員變量又被稱為非靜態變量或實例變量;如果使用了static修飾,則該成員變量又可被稱為靜態變量或類變量

(坑:表面上看定義成員變量是沒有先后順序的,實際上還是要采用合法的前向引用)如:

int num=num2+1;
int num2=2;

是會報錯的,出得num2位靜態比變量的時候。

2.實例變量和類變量的屬性

使用static修飾的成員變量是類變量,屬于該類本身:沒有使用屬于該類的實例。在同一個JVM內,侮個類只對應一個
Java對象口static修飾的成員變量是Class對象,但侮個類可以創建多個

由于同一個JVM內每個類只對應一個static對象,因此同一個JVM內的一個類的類變量只需一塊內存空間;但對于實例變量而言,改類每創建一次實例,就需要為實例變量分配一塊內存空間。也就是說,程序中有幾個實例,實例變量就需要幾塊內存空間。

3.實例變量的初始化時機

對于實例變量,它是Java對象本身。每創建Java對象時都需要為實例變量分配內存空間,并對實例進行初始化。
程序可以在三個地方進行初始化:

  • 定義實例變量時指定初始值。
  • 非靜態初始化塊中對實例變量指定初始值。
  • 構造器中對實例變量指定初始值。
    其中第1,2種方式都比在構造器初始化更早執行,當第1,2種的執行順序與他們在源程序中的排列順序相同。

4.類變量的初始化時機

類變量是屬于Java類本身。從程序運行的角度來看,每個jvm對一個Java類只初始化一次,因此只有每次運行Java程序時,才會初始化該Java類,才會為該類的類變量分配內存空間,并執行初始化。

程序可以在兩個地方對類變量執行初始化:

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

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

父類構造器

1.隱式調用和顯式調用

當創建Java對象時,系統會先調用父類的非靜態初始化塊進行初始化。而這種調用是隱式調用。而第一次初始化時最優先初始化的是靜態初始化塊。接著會調用父類的一個或多個構造器進行初始化,這個調用是用過super()的方法來顯式調用或者隱式調用。當所有父類初始化完之后才初始化子類。實例代碼如下:

class Animal{
    
    static{
        System.out.println("Animal靜態初始化塊");
    }
    
    {
       System.out.println("Animal初始化塊");
    }
        
    public Animal(){
        System.out.println("Animal構造器");
    }
    
    
}

class Cat extends Animal{
    public Cat(String name,int age){
        super();
        System.out.println("Cat構造器");
    }
    
    static{
        System.out.println("Cat靜態初始化塊");
    }
    
    {
        System.out.println("Cat初始化塊");
        weight=2.0;
    }
    
    double weight=2.3;

    public String toString(){
        return "weight="+weight;
    }
    
}

public class JavaTest {

    public static void main(String[] args) {
        
        Cat cat=new Cat("kitty",2);
        System.out.println(cat);
//      Cat cat2=new Cat("Garfied",3);
//      System.out.println(cat2);
    }
    
}

輸出的結果是:

java.PNG

2訪問子類對象的實例變量

子類因為繼承父類所以可以訪問父類的成員方法和變量,當一般情況下父類是訪問不了子類的,因為父類不知道哪個子類繼承。但是在特殊情況下是可以的,如下代碼:

class BaseClass{
    private int i=2;
    public BaseClass(){
        this.display();
    }
    public void display(){
        System.out.println("BaseClass");
        System.out.println(i);
    }
    
}

class Derived extends BaseClass{
    private int i=22;
    public Derived(){
        i=222;
    }
    public void display(){
        System.out.println("Derived");
        System.out.println(i);
    }
    public void sub(){
        System.out.println("sub");
    }
}

public class JavaTest {
    public static void main(String[] args) {
        Derived derived=new Derived();
    }
}

結果如下:

java2.PNG

仔細看代碼,好像怎么也不會輸出0吧,為什么呢。

首先我們要知道Java構造器只是起到對變量進行初始化的作用,而在執行構造器之前我們的對象已經初始化了,在內存中已經被分配起來了,而這些值默認是空值。

其次this在代表正在初始化的對象,一般看會以為就是BaseClass對象,不過在上面代碼里,this是放在BaseClass的構造器里,當時我們是在Derived()構造器執行的,是Derived()構造器隱式調用了BaseClass()構造器的代碼,所以在這個情況下是this是Derived對象。所以當我們改為this.sub()時是報錯的。

此外這個this的編譯類型是BaseClass,所以我們改為this.i的時候輸出是2.

所以應該避免在父類構造器中調用被子類重寫的方法。

父子實例的內存控制

1.繼承成員變量和繼承方法的區別

class Animal{
    public String name="Animal";
    
    public void sub(){
        System.out.println("AnimalSub");
    }
    
}
class Wolf extends Animal{
    public String name="Wolf";

    public void sub(){
        System.out.println("WolfSub");
    }
}

public class JavaTest {
    public static void main(String[] args) {
        Animal animal=new Animal();
        System.out.println(animal.name);
        animal.sub();
        Wolf wolf=new Wolf();
        System.out.println(wolf.name);
        wolf.sub();
        Animal sub=new Wolf();
        System.out.println(sub.name);
        sub.sub();
    }
}

結果如下:

image.png

所以當聲明類型為父類,運行類型為子類是,成員變量表現出父類,而方法表現出子類,這就是多態。

2.內存中的子類實例

class Fruit{
    String color="未確定顏色";
    
    public Fruit getThis(){
        return this;
    }
    
    public void info(){
        System.out.println("Fruit方法");
    }
    
}

public class JavaTest extends Fruit{
    
    @Override
    public void info() {
        System.out.println("JavaTest方法");
    }
    
    public void AccessSuperInfo(){
        super.info();
    }
    
    public Fruit getSuper(){
        return super.getThis();
    }
    
    String color="紅色";
    
    public static void main(String[] args) {
        JavaTest javaTest=new JavaTest();
        Fruit f=javaTest.getSuper();
        
        System.out.println("javaTest和f所引用的對象是否相同:"+(javaTest==f));
        System.out.println("所引用對象的color實例變量:"+javaTest.color);
        System.out.println("所引用對象的color實例變量:"+f.color);
        
        javaTest.info();
        f.info();
        javaTest.AccessSuperInfo();
    }
    
}

當創建一個對象時,系統不僅為該類的實例變量分配內存,同時也為其父類定義的所有實例變量分配內存,即是子類定義了與父類同名的實例變量。也就是說,當系統創建一個Java對象時,如果該Java類有兩個父類(一個直接父類A,一個間接父類g ),假設A類中定義了2個實例變量,B類
中定義了3個實例變量,當前類中定義了2個實例變量,那么這個Java對象將會保存2+3十2個實例變量。

如果子類里定義了與父類中已有變量同名的變量,那么子類中定義的變量會隱藏父類中定義的變量,而不是覆蓋。因此系統創建子類對象是依然會為父類定義的,被隱藏的變量分配內存空間。

為了在子類中訪問父類定義的,被隱藏的變量和方法,可以使用super來限定修飾這些變量和方法。

3.父,子類的類變量

如果在子類中要訪問父類中被隱藏的靜態變量和方法,程序有兩種方式:

  • 直接使用父類的類名作為主調來訪問類變量
  • 使用super.作為限定來訪問類變量

一般情況下,都建議使用第一種方式訪問類變量,因為類變量屬于類本身,使用類名做主調來訪問可以較好的可讀性

final修飾符

1.final 修飾的變量

final修飾的實例變量必須顯示指定初始值,只能在如下三個位置指定初始值。

  • 定義final實例變量時指定初始值
  • 在非靜態初始化塊中為final實例變量指定初始值
  • 在構造器中為final實例變量指定初始值

對于普通實例java可以指定默認初始化,而final實例變量只能顯示指定初始化。

2.執行‘宏替換’的變量

在定義時final類變量指定了初始值,該初始值在編譯時就被確定下來,這個final變量本質上已經不再是變量而是一個直接量,如果被賦的表達式只是基木的算術表達式或字符串連接運算,沒有訪問普通變量,調用方法,Java編譯器同樣會將這種final變量當成“宏變量”來處理。

3.final方法不能重寫

如果父類中某個方法使用了final修飾符進行修飾,那么這個方法將不可能被他的子類訪問到,因此這個方法也不可能被他的子類重寫。從這個層面說,private和final同時修飾某個方法沒有太大的意義,但是被java語法允許。

4.內部類中的局部變量

Java要求所有被內部類訪問的局部變量都使用final修飾也是有其原因的。對于井通的局部變量而言,‘它的作用域就停留在該方法內,當方法執行結束后,該局部變量也隨之消失;但內部類則可能產生隱式的“閉包(Closure)”,閉包將使得局部變量脫離它所在的方法繼續存在。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容