1. 程式人生 > >java中用new建立一個物件的過程解析

java中用new建立一個物件的過程解析

java中用new建立一個物件的過程解析


對於用new 建立一個物件,我們需要弄清楚它的過程:
引用和建立一個物件的格式是:

類名 變數名;

變數名=new 類名(引數列表);

比如 Vehicle veh1=new Vehicle();

這個語句具體的執行過程是:

1.右邊的“new vheicle"是以vehicle類為模板,在堆空間裡建立一個vehicle類物件(也簡稱vehicle物件)。

2.末尾的()意味著,在物件建立後,立即呼叫vehicle類的建構函式,對剛生成的物件進行初始化。建構函式肯定是有的,如果沒有建立,java會補上一個預設的建構函式。

3.左邊的'Vehicle veh1'建立了一個vehicle類引用變數

4.“=”操作符使物件引用指向剛建立的Vehicle物件。


將上面的語句分為兩個步驟:

Vechicle veh1;

veh1=new  Vechicle;

 這樣寫,就比較清楚了,有兩個實體:一是物件引用變數,一是物件本身。在堆空間裡建立的實體,與在棧空間裡建立的實體不同。儘管它們也是確確實實存在的實體,但是似乎很難準確的“抓”住它。


/*
SubClass sub = new SubClass();
這句話到底做了什麼事情呢?
1.javac編譯.java原始檔形成.class位元組碼檔案;
2.new SubClass()物件時,先檢查有沒有父類,有父類,類載入器(ClassLoader)先將父類的Class檔案讀入記憶體,建立一個java.lang.Class物件,然後載入子類,類載入器將子類的Class檔案讀入記憶體,建立一個java.lang.Class物件;
3.先初始化父類的靜態屬性,再初始化父類的靜態程式碼塊;
4.再初始化子類的靜態屬性,再初始化子類的靜態程式碼;
5.在堆記憶體中分配記憶體空間,分配記憶體地址,此時是因為父類的特有屬性才在堆記憶體中為父類物件分配空間。
6.初始化父類的特有屬性。
7.初始化父類的構造程式碼塊。
8.初始化父類物件相應的構造方法。
9.在堆記憶體中分配記憶體空間,分配記憶體地址,此時是因為子類的特有屬性才在堆記憶體中為子類物件分配空間的。
10.初始化子類的特有屬性。
11.初始化子類的構造程式碼塊。
12.初始化子類相應的構造方法。
13.將子類的記憶體地址賦值給棧中的引用物件。
*/
package com.zhangyike.staticExcise;

// 靜態變數

    public static String staticField = "父類--靜態變數";

    public String field = "父類--普通變數";

    // 靜態塊
    static {
        System.out.println(staticField);
        System.out.println("父類--靜態塊");
    }

    // 初始化塊
    {
        System.out.println(field);
        System.out.println("父類--普通塊");
    }

    // 構造器
    public ParentClass() {
        System.out.println("父類--構造器");
    }
}

public class SubClass extends ParentClass {

    // 靜態變數
    public static String sstaticField = "子類--靜態變數";
    // 變數
    public String sField = "子類--變數";

    // 靜態塊
    static {
        System.out.println(sstaticField);
        System.out.println("子類--靜態塊");
    }

    // 初始化塊
    {
        System.out.println(sField);
        System.out.println("子類--普通塊");
    }

    // 構造器
    public SubClass() {
        System.out.println("子類--構造器");
    }

    public static void main(String[] args) {

        System.out.println("順序:" + "第一次new SubClass");
        SubClass sub = new SubClass();
        System.out.println("順序:" + "第二次new SubClass");
        new SubClass();
    }
}

程式執行的結果為:
父類–靜態變數
父類–靜態塊
子類–靜態變數
子類–靜態塊
順序:第一次new SubClass
父類–普通變數
父類–普通塊
父類–構造器
子類–變數
子類–普通塊
子類–構造器
順序:第二次new SubClass
父類–普通變數
父類–普通塊
父類–構造器
子類–變數
子類–普通塊
子類–構造器

====================================================================================================================================


本文大部分內容來自於IBM的博文多型在 Java 和 C++ 程式設計語言中的實現比較 。這裡寫一遍主要是加深自己的理解,方便以後檢視,加入了一些自己的見解及行文組織,不是出於商業目的,如若需要下線,請告知。

結論

基於基類的呼叫和基於介面的呼叫,從效能上來講,基於基類的呼叫效能更高 。因為invokevirtual是基於偏移量的方式來查詢方法的,而invokeinterface是基於搜尋的。

概述

多型是面向物件程式設計的重要特性。多型允許基類的引用指向派生類的物件,而在具體訪問時實現方法的動態繫結。
java對方法動態繫結的實現方法主要基於方法表,但是這裡分兩種呼叫方式invokevirtual和invokeinterface,即類引用呼叫和介面引用呼叫。類引用呼叫只需要修改方法表的指標就可以實現動態繫結(具有相同簽名的方法,在父類、子類的方法表中具有相同的索引號),而介面引用呼叫需要掃描整個方法表才能實現動態繫結(因為,一個類可以實現多個介面,另外一個類可能只實現一個介面,無法具有相同的索引號。這句如果沒有看懂,繼續往下看,會有例子。寫到這裡,感覺自己看書時,有的時候也會不理解,看不懂,思考一段時間,還是不明白,做個標記,繼續閱讀吧,然後回頭再看,可能就豁然開朗。)。
類引用呼叫的大致過程為:java編譯器將java原始碼編譯成class檔案,在編譯過程中,會根據靜態型別將呼叫的符號引用寫到class檔案中。在執行時,JVM根據class檔案找到呼叫方法的符號引用,然後在靜態型別的方法表中找到偏移量,然後根據this指標確定物件的實際型別,使用實際型別的方法表,偏移量跟靜態型別中方法表的偏移量一樣,如果在實際型別的方法表中找到該方法,則直接呼叫,否則,按照繼承關係從下往上搜索。
下面對上面的描述做具體的分析討論。

JVM的執行時結構

這裡寫圖片描述
從上圖可以看出,當程式執行時,需要某個類時,類載入子系統會將相應的class檔案載入到JVM中,並在內部建立該類的型別資訊,這個型別資訊其實就是class檔案在JVM中儲存的一種資料結構,他包含著java類定義的所有資訊,包括方法程式碼,類變數、成員變數、以及本博文要重點討論的方法表<喎�"/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqGj1eK49sDg0M3Qxc+ivs205rSi1Nq3vbeox/ihozxiciAvPg0K16LS4qOs1eK49re9t6jH+NbQtcTA4NDN0MXPorj61Nq20dbQtOa3xbXEY2xhc3O21M/zyseyu82stcSho9Tat723qMf41tCjrNXiuPZjbGFzc7XEwODQzdDFz6LWu9PQzqjSu7XEyrXA/aOoy/nS1MrHuPe49s/fs8y5ss/ttcTE2rTmx/jT8qOpo6y2+NTattHW0L/J0tTT0LbguPa4w2NsYXNzttTP86Gjv8nS1M2ouf220dbQtcRjbGFzc7bUz/O3w87Ktb23vbeox/jW0MDg0M3Qxc+ioaO+zc/x1NpqYXZht7TJ5Lv61sbEx9H5o6zNqLn9Y2xhc3O21M/zv8nS1LfDzsq1vbjDwOC1xMv509DQxc+i0rvR+aGjPGJyIC8+DQq3vbeose3Kx8q1z9a2r8ystffTw7XEusvQxKGjt723qLHttOa3xdTat723qMf41tC1xMDg0M3Qxc+i1tCho7e9t6ix7dbQtOa3xdPQuMPA4Lao0uW1xMv509C3vbeovLDWuM/yt723qLT6wuu1xNa41euho9Xi0Km3vbeo1tCw/MCotNO4uMDgvMyz0LXEy/nT0Le9t6jS1Lyw19TJ7dbY0LSjqG92ZXJyaWRlo6m1xLe9t6ihozwvcD4NCjxoMiBpZD0="類引用呼叫invokevirtual">類引用呼叫invokevirtual

程式碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 <code class = "hljs java" > package org.fan.learn.methodTable;   /**   * Created by fan on 2016/3/30.   */ public class ClassReference {      static class Person {          @Override          public String toString(){              return "I'm a person." ;          }          public void eat(){              System.out.println( "Person eat" );          }          public void speak(){              System.out.println( "Person speak" );          }        }        static class Boy extends Person{          @Override          public String toString(){              return "I'm a boy" ;          }          @Override          public void speak(){              System.out.println( "Boy speak" );          }          public void fight(){              System.out.println( "Boy fight" );          }      }        static class Girl extends Person{          @Override          public String toString(){              return "I'm a girl" ;          }          @Override          public void speak(){              System.out.println( "Girl speak" );          }          public void sing(){              System.out.println( "Girl sing" );          }      }        public static void main(String[] args) {          Person boy = new Boy();          Person girl = new Girl();          System.out.println(boy);          boy.eat();          boy.speak();          //boy.fight();          System.out.println(girl);          girl.eat();          girl.speak();          //girl.sing();      } } </code>

注意,boy.fight();girl.sing(); 這兩個是有問題的,在IDEA中會提示“Cannot resolve method ‘fight()’”。因為,方法的呼叫是有靜態型別檢查的,而boy和girl的靜態型別都是Person型別的,在Person中沒有fight方法和sing方法。因此,會報錯。
執行結果如下:
這裡寫圖片描述
從上圖可以看到,boy.eat()girl.eat() 呼叫產生的輸出都是”Person eat”,因為Boy和Girl中沒有override 父類的eat方法。
位元組碼指令:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <code class = "hljs scss" > public static void main(java.lang.String[]);    Code:     Stack= 2 , Locals= 3 , Args_size= 1     0 :   new     # 2 ; //class ClassReference$Boy     3 :   dup     4 :   invokespecial   # 3 ; //Method ClassReference$Boy."<init>":()V     7 :   astore_1     8 :   new     # 4 ; //class ClassReference$Girl     11 :  dup     12 :  invokespecial   # 5 ; //Method ClassReference$Girl."<init>":()V     15 :  astore_2     16 :  getstatic       # 6 ; //Field java/lang/System.out:Ljava/io/PrintStream;     19 :  aload_1     20 :  invokevirtual   # 7 ; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V     23 :  aload_1     24 :  invokevirtual   # 8 ; //Method ClassReference$Person.eat:()V     27 :  aload_1     28 :  invokevirtual   # 9 ; //Method ClassReference$Person.speak:()V     31 :  getstatic       # 6 ; //Field java/lang/System.out:Ljava/io/PrintStream;     34 :  aload_2     35 :  invokevirtual   # 7 ; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V     38 :  aload_2     39 :  invokevirtual   # 8 ; //Method ClassReference$Person.eat:()V     42 :  aload_2     43 :  invokevirtual   # 9 ; //Method ClassReference$Person.speak:()V     46 return </init></init></code>

其中所有的invokevirtual呼叫的都是Person類中的方法。

下面看看java物件的記憶體模型:
這裡寫圖片描述
從上圖可以清楚地看到呼叫方法的指標指向。而且可以看出相同簽名的方法在方法表中的偏移量是一樣的。這個偏移量只是說Boy方法表中的繼承自Object類的方法、繼承自Person類的方法的偏移量與Person類中的相同方法的偏移量是一樣的,與Girl是沒有任何關係的。

下面再看看呼叫過程,以girl.speak() 方法的呼叫為例。在我的位元組碼中,這條指令對應43: invokevirtual #9; //Method ClassReference$Person.speak:()V ,為了便於使用IBM的圖,這裡採用跟IBM一致的符號引用:invokevirtual #12; 。呼叫過程圖如下所示:
這裡寫圖片描述
(1)在常量池中找到方法呼叫的符號引用
(2)檢視Person的方法表,得到speak方法在該方法表的偏移量(假設為15),這樣就得到該方法的直接引用。
(3)根據this指標確定方法接收者(girl)的實際型別
(4)根據物件的實際型別得到該實際型別對應的方法表,根據偏移量15檢視有無重寫(override)該方法,如果重寫,則可以直接呼叫;如果沒有重寫,則需要拿到按照繼承關係從下往上的基類(這裡是Person類)的方法表,同樣按照這個偏移量15檢視有無該方法。

介面引用呼叫invokeinterface

程式碼如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <code class = "hljs axapta" > package org.fan.learn.methodTable;   /**   * Created by fan on 2016/3/29.   */ public class InterfaceReference {      interface IDance {          void dance();      }        static class Person {          @Override          public String toString() {              return "I'm a person" ;          }          public void speak() {              System.out.println( "Person speak" );          }          public void eat() {              System.out.println( "Person eat" );          }      }        static class Dancer extends Person implements IDance {          @Override          public String toString() {              return "I'm a Dancer" ;          }          @Override          public void speak() {              System.out.println( "Dancer speak" );          }          public void dance() {              System.out.println( "Dancer dance" );          }      }        static class Snake implements IDance {          @Override          public String toString() {              return "I'm a Snake" ;          }          public void dance() {              System.out.println( "Snake dance" );          }      }        public static void main(String[] args) {          IDance dancer = new Dancer();          System.out.println(dancer);          dancer.dance();          //dancer.speak();          //dancer.eat();          IDance snake = new Snake();          System.out.println(snake);          snake.dance();      } } </code>

上面的程式碼中dancer.speak(); dancer.eat(); 這兩句同樣不能呼叫。
執行結果如下所示:
這裡寫圖片描述
其位元組碼指令如下所示:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <code class = "hljs scss" > public static void main(java.lang.String[]);    Code:     Stack= 2 , Locals= 3 , Args_size= 1     0 :   new     # 2 ; //class InterfaceReference$Dancer

相關推薦

java中用new建立一個物件過程解析

java中用new建立一個物件的過程解析 對於用new 建立一個物件,我們需要弄清楚它的過程: 引用和建立一個物件的格式是: 類名 變數名; 變數名=new 類名(引數列表); 比如 Vehicle veh1=new Vehicle(); 這個語句具體的執行過

java 使用new新建一個物件時的操作過程

</pre><p></p><p><span style="font-family:Microsoft YaHei">/**</span></p><span style="font-f

Java中用引號建立String物件和用建構函式的區別

建立一個String物件一般有以下兩種方式: String str1 = "abcd"; String str2 = new String("abcd"); 這兩種方式有什麼區別呢?我們可以通過下面兩個小例子來說明. Example 1: String a = "

在spring 中如果使用new建立一個物件時 這個物件將不在受spring管理器管理

文章如標題具體如下 比如現在有一個service 層 package com.zyc.service.impl; import javax.annotation.Resource; import org.springframework.stereotype.Servi

java關於new一個物件的例子

首先看下面一段程式碼: public class ChangeStr {String str = new String("good");char[] ch = { 'a', 'b', 'c' };public static void main(String args[])

javanew出來一個物件和定義一個物件賦值為空有什麼不同

new 一個物件出來,比如SomeClass sc=new SomeClass();這個時候已經為sc這個物件分配了指向 new SomeClass() 所建立的記憶體空間。即對這個物件sc進行了例項化。而SomeClass sc=null,則sc物件未進行例項化,是一個空的物件,未能指向任何記憶體空間。

Java中如何建立一個新的物件的/Creating Objects/Java/New方法/原文

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later rele

JavaScript (new建立一個物件過程

在JavaScript的世界中,物件Object的操作是比較靈活的,可以通過建立一個物件,來進行繼承,拓展,而且物件的屬性是極其容易拓展的。 所以建立一個物件例項流程可以是這樣子的: function Person(name , age){

java建立物件時,new一個物件 和 = null的區別

首先要明白,java裡物件傳遞的時候,傳遞的都是引用(也就是物件的地址),這比傳遞整個物件高效的多。而基礎型別,int,double等傳遞的才是值。比如,(new ArrayList<String>).add(new String("hello")),jvm只是把

new一個物件和使用類名建立一個物件有什麼區別?

1.儲存空間不同 new出來的在堆上 直接定義的在棧上  2.一個在堆,一個在棧 棧就是CXXX XX這種的,是在程式執行前就分配好的,不需自已釋放 而堆,是執行時分配的,得自已釋放  3.執行時間

java中序列化一個物件儲存在檔案中的簡單過程

為什麼要序列化?因為在儲存一個物件或者大型資料型別時,因為平臺的不同(比如作業系統不同),需要通過網路傳遞時,需要適應對方的環境或者網路的協議,要將物件的資料轉化成一種標準的位元組流序列,從而能在其他平臺還原出來和符合網路傳輸的要求。所有分散式應用常常需要跨平臺,跨網路,因此

Java List中新增一個物件多次

在實際應用場景中,可以需要在一個List中新增多個物件,在使用的時候有個誤區就是將一個物件新增多次到List中,導致資料不一致。 測試程式碼: public class test { public static void main(String[] args) {

java中用jdom建立xml文件/將資料寫入XML中

1 import java.io.FileNotFoundException; 2 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 6 import org.jdom.Attribute; 7 imp

向上引用時new建立物件的析構

假設Employee是基類,Singer是派生類,並新增一個char*成員,該成員指向有new分配的記憶體。 下面的程式碼: Employee *pe = new Singer; delete pe; 如果使用預設的靜態編譯,delete語句將呼叫~Employee()解構函式。這將釋放

例題:建立一個物件陣列,內放5個學生的資料(學號、成績),用指標指向陣列首元素,輸出第1,3,5個學生的資料。【面向物件設計】

題目: 建立一個物件陣列,內放5個學生的資料(學號、成績),用指標指向陣列首元素,輸出第1,3,5個學生的資料。 解答:  程式程式碼如下: #include <iostream>

Java中如何獲得一個物件所對應的類及Class類的簡單理解

Java中如何獲得一個物件所對應的類及Class類的簡單理解 前言 在之前的學習中,所用的程式語言主要是Python,最近開始學習Java,熟悉Python的同學應該會知道在Python中有一個函式type(),通過這個函式可以非常方便地獲得一個變數的型別。那麼在Java中可不可以實

JavaScript中 建立一個物件

在JavaScript當中建立一個物件有兩種語法, 一種是通過字面量的形式,另外一種是通過new Object()的形式 建立一個person物件 它有 name,age,sex等屬性。 1.字面量形式(literal syntax) var person =

Java)IDEA 建立一個簡單的 “Hello world”

  早就聽說  IDEA   好用,今天晚上終於下載下來了, 下載---安裝--破解  一氣呵成~   起飛的地址看下面,這哥們的部落格也很帥~ https://www.cnblogs.com/jajian/p/79890

C++用new和不用new建立物件區別

new建立類物件,使用完後需使用delete刪除,跟申請記憶體類似。所以,new有時候又不太適合,比如在頻繁呼叫場合,使用區域性new類物件就不是個好選擇,使用全域性類物件或一個經過初始化的全域性類指標似乎更加高效。 一、new建立類物件與不new區別 下面是自

【C++】C++用new和不用new建立物件區別

起初剛學C++時,很不習慣用new,後來看老外的程式,發現幾乎都是使用new,想一想區別也不是太大,但是在大一點的專案設計中,有時候不使用new的確會帶來很多問題。 當然這都是跟new的用法有關的。new建立類物件,使用完後需使用delete刪除,跟申請記憶體類似。所以