本篇會講剩余的java的物件參考與物件的區別、多型性理解、向上轉型和向下轉型、堆疊和堆等綜合型的知識,亦是非常重要的難點!!!
一、物件參考與物件的區別
為便于說明,我們先定義一個簡單的類:
class Vehicle {
int passengers;
int fuelcap;
int mpg;
}
有了這個模板,就可以用它來創建物件:
Vehicle veh1 = new Vehicle();
通常把這條陳述句的動作稱之為創建一個物件,其實,它包含了四個動作,
1)右邊的“new Vehicle”,是以Vehicle類為模板,在堆空間里創建一個Vehicle類物件(也簡稱為Vehicle物件),
2)末尾的()意味著,在物件創建后,立即呼叫Vehicle類的建構式,對剛生成的物件進行初始化,建構式是肯定有的,如果你沒寫,Java會給你補上一個默認的建構式,
3)左邊的“Vehicle veh1”創建了一個Vehicle類參考變數,所謂Vehicle類參考,就是以后可以用來指向Vehicle物件的物件參考,
4)“=”運算子使物件參考指向剛創建的那個Vehicle物件,
我們可以把這條陳述句拆成兩部分:
Vehicle veh1;
veh1 = new Vehicle();
效果是一樣的,這樣寫,就比較清楚了,有兩個物體:一是物件參考變數,一是物件本身,
在堆空間里創建的物體,與在資料段以及堆疊空間里創建的物體不同,盡管它們也是確確實實存在的物體,但是,我們看不見,也摸不著,不僅如此,我們仔細研究一下第二句,找找剛創建的物件叫什么名字?有人說,它叫“Vehicle”,不對,“Vehicle”是類(物件的創建模板)的名字,一個Vehicle類可以據此創建出無數個物件,這些物件不可能全叫“Vehicle”,
物件連名都沒有,沒法直接訪問它,我們只能通過物件參考來間接訪問物件,
為了形象地說明物件、參考及它們之間的關系,可以做一個或許不很妥當的比喻,物件好比是一只很大的氣球,大到我們抓不住它,參考變數是一根繩,可以用來系汽球,
如果只執行了第一條陳述句,還沒執行第二條,此時創建的參考變數veh1還沒指向任何一個物件,它的值是null,參考變數可以指向某個物件,或者為null,它是一根繩,一根還沒有系上任何一個汽球的繩,執行了第二句后,一只新汽球做出來了,并被系在veh1這根繩上,我們抓住這根繩,就等于抓住了那只汽球,
再來一句:
Vehicle veh2;
就又做了一根繩,還沒系上汽球,如果再加一句:
veh2 = veh1;
系上了,這里,發生了復制行為,但是,要說明的是,物件本身并沒有被復制,被復制的只是物件參考,結果是,veh2也指向了veh1所指向的物件,兩根繩系的是同一只汽球,
如果用下句再創建一個物件:
veh2 = new Vehicle();
則參考變數veh2改指向第二個物件,
從以上敘述再推演下去,我們可以獲得以下結論:(1)一個物件參考可以指向0個或1個物件(一根繩子可以不系汽球,也可以系一個汽球);(2)一個物件可以有N個參考指向它(可以有N條繩子系住一個汽球),
如果再來下面陳述句:
veh1 = veh2;
按上面的推斷,veh1也指向了第二個物件,這個沒問題,問題是第一個物件呢?沒有一條繩子系住它,它飛了,多數書里說,它被Java的垃圾回識訓制回收了,這不確切,正確地說,它已成為垃圾回識訓制的處理物件,至于什么時候真正被回收,那要看垃圾回識訓制的心情了,
由此看來,下面的陳述句應該不合法吧?至少是沒用的吧?
new Vehicle();
不對,它是合法的,而且可用的,譬如,如果我們僅僅為了列印而生成一個物件,就不需要用參考變數來系住它,最常見的就是列印字串:
System.out.println(“I am Java!”);
字串物件“I am Java!”在列印后即被丟棄,有人把這種物件稱之為臨時物件,
物件與參考的關系將持續到物件回收,但是,關于這一點,打算在下文“簡述Java回識訓制”再說,
例題:
public interface IA {
void ma();
}
public interface IB extends IA{
void mb();
}
public interface IC {
void mc();
}
public interface ID extends IB, IC{
void md();
}
public class IE implements ID{
public void mb() {
}
?
public void ma() {
System.out.println("呵呵");
}
?
public void mc() {
System.out.println("哈哈");
}
?
public void md() {
}
}
public class TestIE {
public static void main(String args[]){
IC ic = new IE();
new IE().ma(); //呼叫ma方法只能新建一個IE物件來呼叫
new IE().mc(); //呼叫mc方法可以新建一個IE物件來呼叫,也可以用ic.的方式呼叫
ic.mc();
}
}
?
public class TestIE {
public static void main(String args[]){
IC ic = new IE();
System.out.println(ic instanceof IA);
System.out.println(ic instanceof IB);
System.out.println(ic instanceof IC);
System.out.println(ic instanceof ID);
System.out.println(ic instanceof IE);
//五個都是對的,因為instanceof是Java中的二元運算子,左邊是物件,右邊是類;當物件是右邊類或子類所創建物件時,回傳true;否則,回傳false,
}
}
二、多型性理解
1. Java中的多型性理解(注意與C++區分)
Java中除了static方法和final方法(private方法本質上屬于final方法,因為不能被子類訪問)之外,其它所有的方法都是動態系結,這意味著通常情況下,我們不必判定是否應該進行動態系結—它會自動發生,
- final方法會使編譯器生成更有效的代碼,這也是為什么說宣告為final方法能在一定程度上提高性能(效果不明顯),
- 如果某個方法是靜態的,它的行為就不具有多型性:
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
?
public String dynamicGet() {
return "Base dynamicGet()";
}
}
?
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
?
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
?
public class StaticPolymorphism {
?
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
輸出:
Base staticGet()
Derived dynamicGet()
建構式并不具有多型性,它們實際上是static方法,只不過該static宣告是隱式的,因此,建構式不能夠被override,
在父類建構式內部呼叫具有多型行為的函式將導致無法預測的結果,因為此時子類物件還沒初始化,此時呼叫子類方法不會得到我們想要的結果,
class Glyph {
void draw() {
System.out.println("Glyph.draw()");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
?
class RoundGlyph extends Glyph {
private int radius = 1;
?
RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius);
}
?
void draw() {
System.out.println("RoundGlyph.draw(). radius = " + radius);
}
}
?
public class PolyConstructors {
?
public static void main(String[] args) {
new RoundGlyph(5);
}
}
輸出:
Glyph() before draw()
RoundGlyph.draw(). radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5
為什么會這樣輸出?這就要明確掌握Java中建構式的呼叫順序:
(1)在其他任何事物發生之前,將分配給物件的存盤空間初始化成二進制0;(2)呼叫基類建構式,從根開始遞回下去,因為多型性此時呼叫子類覆寫后的draw()方法(要在呼叫RoundGlyph建構式之前呼叫),由于步驟1的緣故,我們此時會發現radius的值為0;(3)按宣告順序呼叫成員的初始化方法;(4)最后呼叫子類的建構式,
只有非private方法才可以被覆寫,但是還需要密切注意覆寫private方法的現象,這時雖然編譯器不會報錯,但是也不會按照我們所期望的來執行,即覆寫private方法對子類來說是一個新的方法而非多載方法,因此,在子類中,新方法名最好不要與基類的private方法采取同一名字(雖然沒關系,但容易誤解,以為能夠覆寫基類的private方法),
Java類中屬性域的訪問操作都由編譯器決議,因此不是多型的,父類和子類的同名屬性都會分配不同的存盤空間,如下:
// Direct field access is determined at compile time.
class Super {
public int field = 0;
public int getField() {
return field;
}
}
?
class Sub extends Super {
public int field = 1;
public int getField() {
return field;
}
public int getSuperField() {
return super.field;
}
}
?
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.filed = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.filed = " + sub.field +
", sub.getField() = " + sub.getField() +
", sub.getSuperField() = " + sub.getSuperField());
}
}
輸出:
sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1,
sub.getSuperField() = 0
Sub子類實際上包含了兩個稱為field的域,然而在參考Sub中的field時所產生的默認域并非Super版本的field域,因此為了得到Super.field,必須顯式地指明super.field,
三、向上轉型和向下轉型
在我的理解:java的向上和向下轉型可以看成是型別的轉換,
public class Person {
public void eat(){
System.out.println("Person eatting...");
}
public void sleep() {
System.out.println("Person sleep...");
}
}
?
public class Superman extends Person{
public void eat() {
System.out.println("Superman eatting...");
}
public void fly() {
System.out.println("Superman fly...");
}
}
測驗向上轉型的主方法:
public class Main {
public static void main(String[] args) {
Person person = new Superman();
person.sleep(); //呼叫的是父類person的方法
person.eat(); // 呼叫的是Superman里面的eat方法,Superman重寫了Person父類的方法
//person.fly(); //報錯了,丟失了Superman類的fly方法
}
}
運行的結果:Person sleep…Superman eatting…
分析:當在執行Person person = new Superman()時,我們來看看它的記憶體存盤:

從此圖我們可以看出 向上轉型會丟失子類的新增方法,同時會保留子類重寫的方法,
測驗向下轉型的主方法:
public class Main {
public static void main(String[] args) {
Person person = new Superman();
Superman s = (Superman)person; //向下轉型
s.sleep();
s.fly();
s.eat();
}
}
運行的結果:Person sleep…Superman fly…Superman eatting…
分析:當在執行Superman s = (Superman)person;時,我們來看看他們的記憶體存盤:

在這里我們看出 向下轉型可以得到子類的所有方法(包含父類的方法),
四、堆疊和堆
JAVA在程式運行時,在記憶體中劃分5片空間進行資料的存盤,分別是:1:暫存器,2:本地方法區,3:方法區,4:堆疊,5:堆,
基本,堆疊stack和堆heap這兩個概念很重要,不了解清楚,后面就不用學了,
以下是這幾天堆疊和堆的學習記錄和心得,得些記錄下來,以后有學到新的,會慢慢補充,
一、先說一下最基本的要點
基本資料型別、區域變數都是存放在堆疊記憶體中的,用完就消失,new創建的實體化物件及陣列,是存放在堆記憶體中的,用完之后靠垃圾回識訓制不定期自動消除,
二、先明確以上兩點,以下示例就比較好理解了
示例1
main() int x=1;show () int x=2
主函式main()中定義變數int x=1,show()函式中定義變數int x=1,最后show()函式執行完畢,
以上程式執行步驟:
第1步——main()函式是程式入口,JVM先執行,在堆疊記憶體中開辟一個空間,存放int型別變數x,同時附值1,第2步——JVM執行show()函式,在堆疊記憶體中又開辟一個新的空間,存放int型別變數x,同時附值2, 此時main空間與show空間并存,同時運行,互不影響,第3步——show()執行完畢,變數x立即釋放,空間消失,但是main()函式空間仍存在,main中的變數x仍然存在,不受影響,
示例2
main() int[] x=new int[3]; x[0]=20
主函式main()中定義陣列x,元素型別int,元素個數3,
以上程式執行步驟第1步——執行int[] x=new int[3]; 隱藏以下幾分支 JVM執行main()函式,在堆疊記憶體中開辟一個空間,存放x變數(x變數是區域變數), 同時,在堆記憶體中也開辟一個空間,存放new int[3]陣列,堆記憶體會自動記憶體首地址值,如0x0045, 陣列在堆疊記憶體中的地址值,會附給x,這樣x也有地址值,所以,x就指向(參考)了這個陣列,此時,所有元素均未附值,但都有默認初始化值0,
第2步——執行x[0]=20 即在堆記憶體中將20附給[0]這個陣列元素,這樣,陣列的三個元素值分別為20,0,0
示例3main() int[] x=new int[3]; x[0]=20 x=null;
以上步驟執行步驟第1、2步——與示例2完全一樣,略,
第3步——執行x=null; null表示空值,即x的參考陣列記憶體地址0x0045被洗掉了,則不再指向堆疊記憶體中的陣列,此時,堆中的陣列不再被x使用了,即被視為垃圾,JVM會啟動垃圾回識訓制,不定時自動洗掉,
示例4main() int[] x=new int[3]; int[] y=x; y[1]=100 x=null;
以上步驟執行步驟
第1步——與示例2第1步一致,略,第2步——執行int[] y=x, 在堆疊記憶體定義了新的陣列變數記憶體y,同時將x的值0x0045附給了y,所以,y也指向了堆記憶體中的同一個陣列,第3步——執行y[1]=100 即在堆記憶體中將20附給[0]這個陣列元素,這樣,陣列的三個元素值分別為0,100,0第4步——執行x=null 則變數x不再指向堆疊記憶體中的陣列了,但是,變數y仍然指向,所以陣列不消失,
示例5
Car c=new Car;c.color="blue";Car c1=new Car;c1.num=5;
雖然是個物件都參考new Car,但是是兩個不同的物件,每一次new,都產生不同的物體
示例6
Car c=new Car;c.num=5;Car c1=c;c1.color="green";c.run();
Car c1=c,這句話相當于將物件復制一份出來,兩個物件的記憶體地址值一樣,所以指向同一個物體,對c1的屬性修改,相當于c的屬性也改了,
三、堆疊和堆的特點
堆疊:
函式中定義的基本型別變數,物件的參考變數都在函式的堆疊記憶體中分配,堆疊記憶體特點,數資料一執行完畢,變數會立即釋放,節約記憶體空間,堆疊記憶體中的資料,沒有默認初始化值,需要手動設定,
堆:
堆記憶體用來存放new創建的物件和陣列,堆記憶體中所有的物體都有記憶體地址值,堆記憶體中的物體是用來封裝資料的,這些資料都有默認初始化值,堆記憶體中的物體不再被指向時,JVM啟動垃圾回識訓制,自動清除,這也是JAVA優于C++的表現之一(C++中需要程式員手動清除),
注:
什么是區域變數:定義在函式中的變數、定義在函式中的引數上的變數、定義在for回圈內部的變數
好啦,到目前為止,關于java的面向物件部分,基本寫完啦,幫助到你們的話,可以點贊關注收藏一波O(∩_∩)O~!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/73372.html
標籤:Java
上一篇:java面向物件(匯總)
