成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

談?wù)凧ava的面向?qū)ο?

ormsf / 3269人閱讀

摘要:也就是說(shuō),一個(gè)實(shí)例變量,在的對(duì)象初始化過(guò)程中,最多可以被初始化次。當(dāng)所有必要的類都已經(jīng)裝載結(jié)束,開(kāi)始執(zhí)行方法體,并用創(chuàng)建對(duì)象。對(duì)子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。

類的拷貝和構(gòu)造

C++是默認(rèn)具有拷貝語(yǔ)義的,對(duì)于沒(méi)有拷貝運(yùn)算符和拷貝構(gòu)造函數(shù)的類,可以直接進(jìn)行二進(jìn)制拷貝,但是Java并不天生支持深拷貝,它的拷貝只是拷貝在堆上的地址,不同的變量引用的是堆上的同一個(gè)對(duì)象,那最初的對(duì)象是怎么被構(gòu)建出來(lái)的呢?

Java對(duì)象的創(chuàng)建過(guò)程

關(guān)于對(duì)象的創(chuàng)建過(guò)程一般是從new指令(我說(shuō)的是JVM的層面)開(kāi)始的(具體請(qǐng)看圖1),JVM首先對(duì)符號(hào)引用進(jìn)行解析,如果找不到對(duì)應(yīng)的符號(hào)引用,那么這個(gè)類還沒(méi)有被加載,因此JVM便會(huì)進(jìn)行類加載過(guò)程(具體加載過(guò)程可參見(jiàn)我的另一篇博文)。符號(hào)引用解析完畢之后,JVM會(huì)為對(duì)象在堆中分配內(nèi)存,HotSpot虛擬機(jī)實(shí)現(xiàn)的JAVA對(duì)象包括三個(gè)部分:對(duì)象頭、實(shí)例字段和對(duì)齊填充字段(對(duì)齊不一定),其中要注意的是,實(shí)例字段包括自身定義的和從父類繼承下來(lái)的(即使父類的實(shí)例字段被子類覆蓋或者被private修飾,都照樣為其分配內(nèi)存)。相信很多人在剛接觸面向?qū)ο笳Z(yǔ)言時(shí),總把繼承看成簡(jiǎn)單的“復(fù)制”,這其實(shí)是完全錯(cuò)誤的。JAVA中的繼承僅僅是類之間的一種邏輯關(guān)系(具體如何保存記錄這種邏輯關(guān)系,則設(shè)計(jì)到Class文件格式的知識(shí)),唯有創(chuàng)建對(duì)象時(shí)的實(shí)例字段,可以簡(jiǎn)單的看成“復(fù)制”。

為對(duì)象分配完堆內(nèi)存之后,JVM會(huì)將該內(nèi)存(除了對(duì)象頭區(qū)域)進(jìn)行零值初始化,這也就解釋了為什么JAVA的屬性字段無(wú)需顯示初始化就可以被使用,而方法的局部變量卻必須要顯示初始化后才可以訪問(wèn)。最后,JVM會(huì)調(diào)用對(duì)象的構(gòu)造函數(shù),當(dāng)然,調(diào)用順序會(huì)一直上溯到Object類。

Java對(duì)象的初始化

初始化的順序是父類的實(shí)例變量構(gòu)造、初始化->父類構(gòu)造函數(shù)->子類的實(shí)例變量構(gòu)造、初始化->子類的構(gòu)造函數(shù)。對(duì)于靜態(tài)變量、靜態(tài)初始化塊、變量、初始化塊、構(gòu)造器,它們的初始化順序依次是(靜態(tài)變量、靜態(tài)初始化塊)>(變量、初始化塊)>構(gòu)造器。

JVM在為一個(gè)對(duì)象分配完內(nèi)存之后,會(huì)給每一個(gè)實(shí)例變量賦予默認(rèn)值,這個(gè)時(shí)候?qū)嵗兞勘坏谝淮钨x值,這個(gè)賦值過(guò)程是沒(méi)有辦法避免的。如果我們?cè)趯?shí)例變量初始化器中對(duì)某個(gè)實(shí)例x變量做了初始化操作,那么這個(gè)時(shí)候,這個(gè)實(shí)例變量就被第二次賦值了。 如果我們?cè)趯?shí)例初始化器中,又對(duì)變量x做了初始化操作,那么這個(gè)時(shí)候,這個(gè)實(shí)例變量就被第三次賦值了。如果我們?cè)陬惖臉?gòu)造函數(shù)中,也對(duì)變量x做了初始化操作,那么這個(gè)時(shí)候,變量x就被第四次賦值。也就是說(shuō),一個(gè)實(shí)例變量,在Java的對(duì)象初始化過(guò)程中,最多可以被初始化4次。

下面還是舉一個(gè)例子吧

class Parent {
        /* 靜態(tài)變量 */
    public static String p_StaticField = "父類--靜態(tài)變量";
         /* 變量 */
    public String    p_Field = "父類--變量";
    protected int    i    = 9;
    protected int    j    = 0;
        /* 靜態(tài)初始化塊 */
    static {
        System.out.println( p_StaticField );
        System.out.println( "父類--靜態(tài)初始化塊" );
    }
        /* 初始化塊 */
    {
        System.out.println( p_Field );
        System.out.println( "父類--初始化塊" );
    }
        /* 構(gòu)造器 */
    public Parent()
    {
        System.out.println( "父類--構(gòu)造器" );
        System.out.println( "i=" + i + ", j=" + j );
        j = 20;
    }
}

public class SubClass extends Parent {
         /* 靜態(tài)變量 */
    public static String s_StaticField = "子類--靜態(tài)變量";
         /* 變量 */
    public String s_Field = "子類--變量";
        /* 靜態(tài)初始化塊 */
    static {
        System.out.println( s_StaticField );
        System.out.println( "子類--靜態(tài)初始化塊" );
    }
       /* 初始化塊 */
    {
        System.out.println( s_Field );
        System.out.println( "子類--初始化塊" );
    }
       /* 構(gòu)造器 */
    public SubClass()
    {
        System.out.println( "子類--構(gòu)造器" );
        System.out.println( "i=" + i + ",j=" + j );
    }


        /* 程序入口 */
    public static void main( String[] args )
    {
        System.out.println( "子類main方法" );
        new SubClass();
    }
}

上面的初始化結(jié)果是:

父類--靜態(tài)變量

父類--靜態(tài)初始化塊

子類--靜態(tài)變量

子類--靜態(tài)初始化塊

子類main方法

父類--變量

父類--初始化塊

父類--構(gòu)造器

i=9, j=0

子類--變量

子類--初始化塊

子類--構(gòu)造器

i=9,j=20

子類的靜態(tài)變量和靜態(tài)初始化塊的初始化是在父類的變量、初始化塊和構(gòu)造器初始化之前就完成了。靜態(tài)變量、靜態(tài)初始化塊,變量、初始化塊初始化了順序取決于它們?cè)陬愔谐霈F(xiàn)的先后順序。

分析:

訪問(wèn)SubClass.main(),(這是一個(gè)static方法),于是裝載器就會(huì)為你尋找已經(jīng)編譯的SubClass類的代碼(也就是SubClass.class文件)。在裝載的過(guò)程中,裝載器注意到它有一個(gè)基類(也就是extends所要表示的意思),于是它再裝載基類。不管你創(chuàng)不創(chuàng)建基類對(duì)象,這個(gè)過(guò)程總會(huì)發(fā)生。如果基類還有基類,那么第二個(gè)基類也會(huì)被裝載,依此類推。

執(zhí)行根基類的static初始化,然后是下一個(gè)派生類的static初始化,依此類推。這個(gè)順序非常重要,因?yàn)榕缮惖摹皊tatic初始化”有可能要依賴基類成員的正確初始化。

當(dāng)所有必要的類都已經(jīng)裝載結(jié)束,開(kāi)始執(zhí)行main()方法體,并用new SubClass()創(chuàng)建對(duì)象。

類SubClass存在父類,則調(diào)用父類的構(gòu)造函數(shù),你可以使用super來(lái)指定調(diào)用哪個(gè)構(gòu)造函數(shù)?;惖臉?gòu)造過(guò)程以及構(gòu)造順序,同派生類的相同。首先基類中各個(gè)變量按照字面順序進(jìn)行初始化,然后執(zhí)行基類的構(gòu)造函數(shù)的其余部分。

對(duì)子類成員數(shù)據(jù)按照它們聲明的順序初始化,執(zhí)行子類構(gòu)造函數(shù)的其余部分。

靜態(tài)變量初始化器和靜態(tài)初始化器基本同實(shí)例變量初始化器和實(shí)例初始化器相同,也有相同的限制(按照編碼順序被執(zhí)行,不能引用后定義和初始化的類變量)。靜態(tài)變量初始化器和靜態(tài)初始化器中的代碼會(huì)被編譯器放到一個(gè)名為static的方法中(static是Java語(yǔ)言的關(guān)鍵字,因此不能被用作方法名,但是JVM卻沒(méi)有這個(gè)限制),在類被第一次使用時(shí),這個(gè)static方法就會(huì)被執(zhí)行。

Java對(duì)象的引用方式

接下來(lái)我們?cè)賳?wèn)一個(gè)問(wèn)題,Java是怎么通過(guò)引用找到對(duì)象的呢?

至此,一個(gè)對(duì)象就被創(chuàng)建完畢,此時(shí),一般會(huì)有一個(gè)引用指向這個(gè)對(duì)象。在JAVA中,存在兩種數(shù)據(jù)類型,一種就是諸如int、double等基本類型,另一種就是引用類型,比如類、接口、內(nèi)部類、枚舉類、數(shù)組類型的引用等。引用的實(shí)現(xiàn)方式一般有兩種,具體請(qǐng)看圖3。此處說(shuō)一句題外話,經(jīng)常用人拿C++中的引用和JAVA的引用作對(duì)比,其實(shí)他們兩個(gè)只是“名稱”一樣,本質(zhì)并沒(méi)什么關(guān)系,C++中的引用只是給現(xiàn)存變量起了一個(gè)別名(引用變量只是一個(gè)符號(hào)引用而已,編譯器并不會(huì)給引用分配新的內(nèi)存),而JAVA中的引用變量卻是真真正正的變量,具有自己的內(nèi)存空間,只是不同的引用變量可以“指向”同一個(gè)對(duì)象而已。因此,如果要拿C++和JAVA引用對(duì)象的方式相對(duì)比,C++中的指針倒和JAVA中的引用如出一轍,畢竟,JAVA中的引用其實(shí)就是對(duì)指針的封裝。

關(guān)于對(duì)象引用更深層次的問(wèn)題,我們將在JVM篇章中詳細(xì)解釋。

匿名類、內(nèi)部類和靜態(tài)類

這一部分的內(nèi)容相當(dāng)寬泛,詳細(xì)的可以查閱下面的參考文章,我在這里主要強(qiáng)調(diào)幾個(gè)問(wèn)題:

內(nèi)部類的訪問(wèn)權(quán)限(它對(duì)外部類的訪問(wèn)權(quán)限和外部對(duì)它的訪問(wèn)權(quán)限)

成員內(nèi)部類為什么不能有靜態(tài)變量和靜態(tài)函數(shù)(final修飾的除外)

內(nèi)部類和靜態(tài)內(nèi)部類(嵌套內(nèi)部類)的區(qū)別

局部?jī)?nèi)部類使用的形參為什么必須是final的

匿名內(nèi)部類無(wú)法具有構(gòu)造函數(shù),怎么做初始化操作

內(nèi)部類的繼承問(wèn)題(由于它必須和外部類實(shí)例相關(guān)聯(lián))

在這里只回答一下最后一個(gè)問(wèn)題,由于成員內(nèi)部類的實(shí)現(xiàn)其實(shí)是其構(gòu)造函數(shù)的參數(shù)添加了外部類實(shí)體,所以內(nèi)部類的實(shí)例化必須有外部類,但就類定義來(lái)說(shuō),內(nèi)部類的定義只和外部類定義有關(guān),代碼如下

public class Out {
    private static int a;
    private int b;

    public class Inner {
        public void print() {
            System.out.println(a);
            System.out.println(b);
        }
    }
}

// 內(nèi)部類實(shí)例化
Out out = new Out();
Out.Inner inner = out.new Inner();

public class InheritInner extends Out.Inner {
  InheritInner(Out out){
    out.super();
  }
}

最后關(guān)于內(nèi)部類的實(shí)現(xiàn)原理,請(qǐng)閱讀參考文章中的《內(nèi)部類的簡(jiǎn)單實(shí)現(xiàn)原理》,這非常重要

Java多態(tài)的實(shí)現(xiàn)原理

Java的多態(tài)主要有以下幾種形式:

繼承

覆蓋

接口

方法調(diào)用的原理

多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問(wèn)時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定。Java 對(duì)于方法調(diào)用動(dòng)態(tài)綁定的實(shí)現(xiàn)主要依賴于方法表,但通過(guò)類引用調(diào)用(invokevitual)和接口引用調(diào)用(invokeinterface)的實(shí)現(xiàn)則有所不同。

類引用調(diào)用的大致過(guò)程為:Java編譯器將Java源代碼編譯成class文件,在編譯過(guò)程中,會(huì)根據(jù)靜態(tài)類型將調(diào)用的符號(hào)引用寫到class文件中。在執(zhí)行時(shí),JVM根據(jù)class文件找到調(diào)用方法的符號(hào)引用,然后在靜態(tài)類型的方法表中找到偏移量,然后根據(jù)this指針確定對(duì)象的實(shí)際類型,使用實(shí)際類型的方法表,偏移量跟靜態(tài)類型中方法表的偏移量一樣,如果在實(shí)際類型的方法表中找到該方法,則直接調(diào)用,否則,認(rèn)為沒(méi)有重寫父類該方法。按照繼承關(guān)系從下往上搜索。

方法表是實(shí)現(xiàn)動(dòng)態(tài)調(diào)用的核心。方法表存放在方法區(qū)中的類型信息中。為了優(yōu)化對(duì)象調(diào)用方法的速度,方法區(qū)的類型信息會(huì)增加一個(gè)指針,該指針指向一個(gè)記錄該類方法的方法表,方法表中的每一個(gè)項(xiàng)都是對(duì)應(yīng)方法的指針。這些方法中包括從父類繼承的所有方法以及自身重寫(override)的方法。

Java 的方法調(diào)用有兩類:

動(dòng)態(tài)方法調(diào)用:動(dòng)態(tài)方法調(diào)用需要有方法調(diào)用所作用的對(duì)象,是動(dòng)態(tài)綁定的。

靜態(tài)方法調(diào)用:靜態(tài)方法調(diào)用是指對(duì)于類的靜態(tài)方法的調(diào)用方式,是靜態(tài)綁定的;

類調(diào)用 (invokestatic) 是在編譯時(shí)就已經(jīng)確定好具體調(diào)用方法的情況。

實(shí)例調(diào)用 (invokevirtual)則是在調(diào)用的時(shí)候才確定具體的調(diào)用方法,這就是動(dòng)態(tài)綁定,也是多態(tài)要解決的核心問(wèn)題。

JVM 的方法調(diào)用指令有四個(gè),分別是 invokestatic,invokespecial,invokesvirtual 和 invokeinterface。前兩個(gè)是靜態(tài)綁定,后兩個(gè)是動(dòng)態(tài)綁定的。

class Person {   
 public String toString(){   
    return "I"m a person.";   
     }   
 public void eat(){}   
 public void speak(){}   
      
 }   
  
 class Boy extends Person{   
 public String toString(){   
    return "I"m a boy";   
     }   
 public void speak(){}   
 public void fight(){}   
 }   
  
 class Girl extends Person{   
 public String toString(){   
    return "I"m a girl";   
     }   
 public void speak(){}   
 public void sing(){}   
 }  

如果子類改寫了父類的方法,那么子類和父類的那些同名的方法共享一個(gè)方法表項(xiàng)。因此,方法表的偏移量總是固定的。所有繼承父類的子類的方法表中,其父類所定義的方法的偏移量也總是一個(gè)定值。Person 或 Object中的任意一個(gè)方法,在它們的方法表和其子類 Girl 和 Boy 的方法表中的位置 (index) 是一樣的。這樣 JVM 在調(diào)用實(shí)例方法其實(shí)只需要指定調(diào)用方法表中的第幾個(gè)方法即可。

在常量池(這里有個(gè)錯(cuò)誤,上圖為ClassReference常量池而非Party的常量池)中找到方法調(diào)用的符號(hào)引用 。

查看Person的方法表,得到speak方法在該方法表的偏移量(假設(shè)為15),這樣就得到該方法的直接引用。

根據(jù)this指針得到具體的對(duì)象(即 girl 所指向的位于堆中的對(duì)象)。

根據(jù)對(duì)象得到該對(duì)象對(duì)應(yīng)的方法表,根據(jù)偏移量15查看有無(wú)重寫(override)該方法,如果重寫,則可以直接調(diào)用(Girl的方法表的speak項(xiàng)指向自身的方法而非父類);如果沒(méi)有重寫,則需要拿到按照繼承關(guān)系從下往上的基類(這里是Person類)的方法表,同樣按照這個(gè)偏移量15查看有無(wú)該方法。

接口方法調(diào)用的原理

因?yàn)?Java 類是可以同時(shí)實(shí)現(xiàn)多個(gè)接口的,而當(dāng)用接口引用調(diào)用某個(gè)方法的時(shí)候,情況就有所不同了。
Java 允許一個(gè)類實(shí)現(xiàn)多個(gè)接口,從某種意義上來(lái)說(shuō)相當(dāng)于多繼承,這樣同樣的方法在基類和派生類的方法表的位置就可能不一樣了。

interface IDance{   
   void dance();   
 }   
  
 class Person {   
 public String toString(){   
   return "I"m a person.";   
     }   
 public void eat(){}   
 public void speak(){}   
      
 }   
  
 class Dancer extends Person   
 implements IDance {   
 public String toString(){   
   return "I"m a dancer.";   
     }   
 public void dance(){}   
 }   
  
 class Snake implements IDance{   
 public String toString(){   
   return "A snake.";   
     }   
 public void dance(){   
 //snake dance   
     }   
 }  

方法調(diào)用的補(bǔ)充

我們先來(lái)看一個(gè)示例

public class Test {

  public static class A {
    public void print() {
      System.out.println("A");
    }

    public void invoke() {
      print();
      sprint();
    }

    static void sprint() {
      System.out.println("sA");
    }
  }

  public static class B extends A {
    @Override
    public void print() {
      System.out.println("B");
    }

    static void sprint() {
      System.out.println("sB");
    }
  }

  public static void main(String[] args){
    A a = new B();
    a.invoke(); // B SA
  }
}

由于靜態(tài)方法是靜態(tài)調(diào)用的,在編譯期就決定了跳轉(zhuǎn)的符號(hào),所以進(jìn)入父類的invoke方法調(diào)用的sprint在編譯期即是A的sprint,A的sprint符號(hào)和B的sprint在class中并不相同,這個(gè)符號(hào)在編譯期已經(jīng)確定了。

但是當(dāng)在invoke中調(diào)用print,Java是通過(guò)傳進(jìn)來(lái)的this去找他的類型信息,再?gòu)念悇e信息里去找方法表,所以依然調(diào)用的是子類方法表中的print。

我們?cè)倏匆粋€(gè)例子。

public class Test {

  public static class A {

    public int a = 3;

    public void print() {
      System.out.println(a);
    }
  }

  public static class B extends A {

    public int a = 4;

  }

  public static void main(String[] args){
    B b = new B();
    b.print(); // 3
  }
}

多態(tài)只適用于父子類同樣簽名的方法,而屬性是不參與多態(tài)的。在print里的符號(hào)a在編譯期就確定是A的a了。同樣的還有private的方法,私有方法不參與繼承, 也不會(huì)出現(xiàn)在方法表中,因?yàn)樗接蟹椒ㄊ怯蒳nvokespecial指令調(diào)用的。

成員變量的訪問(wèn)只根據(jù)靜態(tài)類型進(jìn)行選擇,不參與多態(tài)

私有方法不會(huì)發(fā)生多態(tài)選擇,只根據(jù)靜態(tài)類型進(jìn)選擇。

繼承的實(shí)現(xiàn)原理

上面已經(jīng)說(shuō)明了類方法調(diào)用的問(wèn)題,子類繼承父類在方法調(diào)用時(shí)依然是根據(jù)對(duì)象頭找型別信息,然后去自己的類信息里找到方法區(qū)調(diào)用方法指針,和C++通過(guò)在對(duì)象中增加虛函數(shù)表指針不一樣,Java需要通過(guò)自己的運(yùn)行時(shí)型別信息找到自己的方法表,而且這張方法表不僅包含覆蓋的方法也包含不覆蓋的,不像C++,不同的虛函數(shù)表包含不同的方法。比如A->B->C,那么A對(duì)象部分包含的虛函數(shù)表只有A聲明的虛方法,假設(shè)B新聲明了虛方法X,在C類的B類部分的末尾的虛函數(shù)表指針指向的才包含X,但是A類部分的指向的虛函數(shù)表則不會(huì)包含X。Java實(shí)際上是先在編譯時(shí)期就得知方法的偏移,在調(diào)用的時(shí)候直接找到真正型別的方法表對(duì)應(yīng)偏移的方法,如果一個(gè)父類引用調(diào)用了一個(gè)父類沒(méi)有的方法,在編譯期就會(huì)報(bào)錯(cuò)。

和C++不同,C++的內(nèi)存布局是非常緊湊的,這也是為了支持它天然的拷貝語(yǔ)義,c++父類對(duì)象的內(nèi)存空間是直接被包含在子類對(duì)象的連續(xù)內(nèi)存空間中的,其屬性的偏移都取決于聲明順序和對(duì)齊。而Java雖然父類的實(shí)例變量依然是和子類的放在同一個(gè)連續(xù)的內(nèi)存空間,但并非是通過(guò)簡(jiǎn)單的偏移來(lái)取成員的。不過(guò)在Java對(duì)象的內(nèi)存布局中,依然是先安置父類的再安置子類的,所以講sizeof(Parent)大小的內(nèi)容轉(zhuǎn)型成為父類指針,就可以實(shí)現(xiàn)super了。具體是在字節(jié)碼中子類會(huì)有個(gè)u2類型的父類索引,屬于CONSTANT_Class_info類型,通過(guò)CONSTANT_Class_info的描述可以找到CONSTANT_Utf8_info,然后可以找到指定的父類。

重載、覆蓋和隱藏

重載:方法名相同,但參數(shù)不同的多個(gè)同名函數(shù)

參數(shù)不同的意思是參數(shù)類型、參數(shù)個(gè)數(shù)、參數(shù)順序至少有一個(gè)不同

返回值和異常以及訪問(wèn)修飾符,不能作為重載的條件(因?yàn)閷?duì)于匿名調(diào)用,會(huì)出現(xiàn)歧義,eg:void a ()和int a() ,如果調(diào)用a(),出現(xiàn)歧義)

main方法也是可以被重載的

覆蓋:子類重寫父類的方法,要求方法名和參數(shù)類型完全一樣(參數(shù)不能是子類),返回值和異常比父類小或者相同(即為父類的子類),訪問(wèn)修飾符比父類大或者相同

子類實(shí)例方法不能覆蓋父類的靜態(tài)方法;子類的靜態(tài)方法也不能覆蓋父類的實(shí)例方法(編譯時(shí)報(bào)錯(cuò)),總結(jié)為方法不能交叉覆蓋

隱藏:父類和子類擁有相同名字的屬性或者方法時(shí),父類的同名的屬性或者方法形式上不見(jiàn)了,實(shí)際是還是存在的。

當(dāng)發(fā)生隱藏的時(shí)候,聲明類型是什么類,就調(diào)用對(duì)應(yīng)類的屬性或者方法,而不會(huì)發(fā)生動(dòng)態(tài)綁定

方法隱藏只有一種形式,就是父類和子類存在相同的靜態(tài)方法

屬性只能被隱藏,不能被覆蓋

子類實(shí)例變量/靜態(tài)變量可以隱藏父類的實(shí)例/靜態(tài)變量,總結(jié)為變量可以交叉隱藏

隱藏和覆蓋的區(qū)別:

被隱藏的屬性,在子類被強(qiáng)制轉(zhuǎn)換成父類后,訪問(wèn)的是父類中的屬性

被覆蓋的方法,在子類被強(qiáng)制轉(zhuǎn)換成父類后,調(diào)用的還是子類自身的方法

因?yàn)楦采w是動(dòng)態(tài)綁定,是受RTTI(run time type identification,運(yùn)行時(shí)類型檢查)約束的,隱藏不受RTTI約束,總結(jié)為RTTI只針對(duì)覆蓋,不針對(duì)隱藏

java的對(duì)象模型

Java中存在兩種類型,原始類型和對(duì)象(引用)類型。原始類型,即數(shù)據(jù)類型,內(nèi)存布局符合其類型規(guī)范,并無(wú)其他負(fù)載。而對(duì)象類型,則由于自定義類型、垃圾回收,對(duì)象鎖等各種語(yǔ)義與JVM性能原因,需要使用額外空間。

Java對(duì)象的內(nèi)存布局:對(duì)象頭(Header),實(shí)例數(shù)據(jù)(Instance Data),對(duì)齊填充(Padding)。

詳細(xì)的內(nèi)容可以查閱參考文章

這里我們主要講講在繼承和組合兩種情形下會(huì)對(duì)內(nèi)存布局造成什么變化。

類屬性按照如下優(yōu)先級(jí)進(jìn)行排列:長(zhǎng)整型和雙精度類型;整型和浮點(diǎn)型;字符和短整型;字節(jié)類型和布爾類型,最后是引用類型。這些屬性都按照各自的單位對(duì)齊。

不同類繼承關(guān)系中的成員不能混合排列。首先按照規(guī)則2處理父類中的成員,接著才是子類的成員

當(dāng)父類中最后一個(gè)成員和子類第一個(gè)成員的間隔如果不夠4個(gè)字節(jié)的話,就必須擴(kuò)展到4個(gè)字節(jié)的基本單位。

如果子類第一個(gè)成員是一個(gè)雙精度或者長(zhǎng)整型,并且父類并沒(méi)有用完8個(gè)字節(jié),JVM會(huì)破壞規(guī)則1,按照整形(int),短整型(short),字節(jié)型(byte),引用類型(reference)的順序,向未填滿的空間填充。

數(shù)組有一個(gè)額外的頭部成員,用來(lái)存放“長(zhǎng)度”變量。數(shù)組元素以及數(shù)組本身,跟其他常規(guī)對(duì)象同樣,都需要遵守8個(gè)字節(jié)的邊界規(guī)則。

下面給一個(gè)例子

public class Test {

  public static class A {
    public A() {
      System.out.println(this.hashCode());
    }
  }

  public static class B extends A {
    public B(){
      System.out.println(this.hashCode());
      System.out.println(super.equals(this));
    }
  }

  public static void main(String[] args){
    B b = new B();
  }
}
/*
 * 輸出如下:
 * 1627674070
 * 1627674070
 * true
 */
參考文章

Java對(duì)象的創(chuàng)建

Java類初始化順序(看這篇文章就夠了)

詳解內(nèi)部類

詳解匿名內(nèi)部類

內(nèi)部類的簡(jiǎn)單實(shí)現(xiàn)原理(時(shí)間不夠,內(nèi)部類只看這篇即可)

Java技術(shù)——多態(tài)的實(shí)現(xiàn)原理(時(shí)間緊多態(tài)只看這篇)

java方法調(diào)用之重載、重寫的調(diào)用原理(一)

java方法調(diào)用之單分派與多分派(二)

java方法調(diào)用之動(dòng)態(tài)調(diào)用多態(tài)(重寫override)的實(shí)現(xiàn)原理——方法表

java方法調(diào)用之多態(tài)的補(bǔ)充示例

java的重載、覆蓋和隱藏的區(qū)別

Java 對(duì)象內(nèi)存布局

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70101.html

相關(guān)文章

  • 談?wù)?/em>我所理解面向對(duì)象

    摘要:眾多面向?qū)ο蟮木幊趟枷腚m不盡一致,但是無(wú)論哪種面向?qū)ο缶幊陶Z(yǔ)言都具有以下的共通功能。原型編程以類為中心的傳統(tǒng)面向?qū)ο缶幊?,是以類為基礎(chǔ)生成新對(duì)象。而原型模式的面向?qū)ο缶幊陶Z(yǔ)言沒(méi)有類這樣一個(gè)概念。 什么是面向?qū)ο??這個(gè)問(wèn)題往往會(huì)問(wèn)到剛畢業(yè)的新手or實(shí)習(xí)生上,也是往往作為一個(gè)技術(shù)面試的開(kāi)頭題。在這里我們不去談如何答(fu)好(yan)問(wèn)(guo)題(qu),僅談?wù)勎宜斫獾拿嫦驅(qū)ο蟆?從歷...

    avwu 評(píng)論0 收藏0
  • 談?wù)?/em>我對(duì)面向對(duì)象以及類與對(duì)象理解

    showImg(https://segmentfault.com/img/remote/1460000007103938?w=391&h=247); 文章最初發(fā)表于我的個(gè)人博客非典型性程序猿 對(duì)于剛接觸JAVA或者其他面向?qū)ο缶幊陶Z(yǔ)言的朋友們來(lái)說(shuō),可能一開(kāi)始都很難理解面向?qū)ο蟮母拍钜约邦惡蛯?duì)象的關(guān)系。筆者曾經(jīng)帶過(guò)一個(gè)短期培訓(xùn)班教授java入門基礎(chǔ),在最后結(jié)束課程的時(shí)候,還有很多同學(xué)不太理解面向?qū)ο?..

    walterrwu 評(píng)論0 收藏0
  • 談?wù)?/em> javascript 面向對(duì)象一些細(xì)節(jié)問(wèn)題

    摘要:同時(shí),創(chuàng)建的子類有幾個(gè)固定字段,分別是初始化函數(shù)原型初始化函數(shù)對(duì)象通過(guò)這個(gè)函數(shù),把基類和子類的函數(shù)合并執(zhí)行,這樣解決了基類構(gòu)造函數(shù)無(wú)法執(zhí)行的問(wèn)題。二是構(gòu)造函數(shù)可能不止會(huì)操作,還可能會(huì)修改全局的某些狀態(tài)比如計(jì)數(shù)器。 綜述 在 ES6 之前,ES5 實(shí)現(xiàn)面向?qū)ο笫谴蠹医?jīng)常討論的問(wèn)題,趁著 ES6 還沒(méi)進(jìn)入瀏覽器,借我自己的一段腳本,跟大家討論一下 js 面向?qū)ο蟮囊恍┘?xì)節(jié)問(wèn)題,歡迎留言指教...

    newsning 評(píng)論0 收藏0
  • Java項(xiàng)目經(jīng)驗(yàn)——程序員成長(zhǎng)鑰匙

    摘要:當(dāng)你真正到公司里面從事了幾年開(kāi)發(fā)之后,你就會(huì)同意我的說(shuō)法利用找工作,需要的就是項(xiàng)目經(jīng)驗(yàn),項(xiàng)目經(jīng)驗(yàn)就是理解項(xiàng)目開(kāi)發(fā)的基本過(guò)程,理解項(xiàng)目的分析方法,理解項(xiàng)目的設(shè)計(jì)思 Java就是用來(lái)做項(xiàng)目的!Java的主要應(yīng)用領(lǐng)域就是企業(yè)級(jí)的項(xiàng)目開(kāi)發(fā)!要想從事企業(yè)級(jí)的項(xiàng)目開(kāi)發(fā),你必須掌握如下要點(diǎn): 1、掌握項(xiàng)目開(kāi)發(fā)的基本步驟 2、具備極強(qiáng)的面向?qū)ο蟮姆治雠c設(shè)計(jì)技巧 3、掌握用例驅(qū)動(dòng)、以架構(gòu)為核心的主流開(kāi)發(fā)...

    zhangfaliang 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.32 - 七夕將至,你對(duì)象”還好嗎?

    摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過(guò)對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來(lái)。注意中,對(duì)象一定是通過(guò)類的實(shí)例化來(lái)的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...

    李昌杰 評(píng)論0 收藏0
  • SegmentFault 技術(shù)周刊 Vol.32 - 七夕將至,你對(duì)象”還好嗎?

    摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對(duì)象。對(duì)象就是數(shù)據(jù),對(duì)象本身不包含方法。類是相似對(duì)象的描述,稱為類的定義,是該類對(duì)象的藍(lán)圖或原型。在中,對(duì)象通過(guò)對(duì)類的實(shí)體化形成的對(duì)象。一類的對(duì)象抽取出來(lái)。注意中,對(duì)象一定是通過(guò)類的實(shí)例化來(lái)的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...

    Lyux 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

ormsf

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<