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[]) java中new出來一個物件和定義一個物件賦值為空有什麼不同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刪除,跟申請記憶體類似。所以 |