聲明:原創(chuàng)作品,轉(zhuǎn)載時請注明文章來自SAP師太技術(shù)博客( 博/客/園www.cnblogs.com):m.survivalescaperooms.com/jiangzhengjun,并以超鏈接形式標(biāo)明文章原始出處,否則將追究法律責(zé)任!原文鏈接:http://m.survivalescaperooms.com/jiangzhengjun/p/4257583.html 類34.參數(shù)兼容的方法重載public class Confusing { PRivate Confusing(Object o) { System.out.println("Object"); } private Confusing(double[] dArr) { System.out.println("double array"); } public static void main(String[] args) { new Confusing(null); } } 上面的程序打印的是“double array”,為什么? null可代表任何非基本類型對象。 Java的重載解析過程是分兩階段運行的。第一階段選取所有可獲得并且可應(yīng)用的方法或構(gòu)造器。第二階段在第一階段選取的方法或構(gòu)造器中選取最精確的一個。如果一個方法或構(gòu)造器可以接受傳遞給另一個方法或構(gòu)造器的任何參數(shù),那么我們就說第一個方法比第二個方法缺乏精確性,調(diào)用時就會選取第二個方法。 使用上面的規(guī)則來解釋該程序:構(gòu)造器Confusing(Object o)可以接受任何傳遞Confusing(double[] dArr)的參數(shù),因此Confusing(Object o)相對缺乏精確性,所以Confusing(null)會調(diào)用Confusing(double[] dArr)構(gòu)造器。 如果想強制要求編譯器選擇一個自己想要的重載版本,需要將實參強制轉(zhuǎn)型為所需要的構(gòu)造器或方法的參數(shù)類型:如這里要調(diào)用Confusing(Object o)本版,則這樣調(diào)用:Confusing((Object)null)。 如果你確實進行了重載,那么請確保所有的重載版本所接受的參數(shù)類型都互不兼容,這樣,任何兩個重載版本都不會同時是可應(yīng)用的。
35.靜態(tài)方法不具有多態(tài)特性class A1 { public static void f() { System.out.println("A1.f()"); } } class A2 extends A1 { public static void f() { System.out.println("A2.f()"); } } class T { public static void main(String[] args) { A1 a1 = new A1(); A1 a2 = new A2(); // 靜態(tài)方法不具有多態(tài)效果,它是根據(jù)引用聲明類型來調(diào)用 a1.f();// A1.f() a2.f();// A1.f() } } 對靜態(tài)方法的調(diào)用不存在任何動態(tài)的分派機制。當(dāng)一個程序調(diào)用了一個靜態(tài)方法時,要被調(diào)用的方法都是在編譯時就被選定的,即調(diào)用哪個方法是根據(jù)該引用被聲明的類型決定的。上面程序中a1與a2引用的類型都是A1類型,所以調(diào)用的是A1中的f()方法。
36.屬性只能被隱藏class P { public String name = "P"; } class S extends P { // 隱藏父類的name域,而不像方法屬于重寫 private String name = "S"; } public class Test { public static void main(String[] args) { // !! S.name is not visible // !! System.out.println(new S().name); // 屬性不能被重寫,只是被隱藏,所以不具有多態(tài)性為 System.out.println(((P) new S()).name);// p } } 屬性的調(diào)用與靜態(tài)方式的調(diào)用一樣,只與前面引用類型相關(guān),與具體的實例沒有任何關(guān)系。 當(dāng)你在聲明一個域、一個靜態(tài)方法或一個嵌套類型時,如果其名與基類中相對應(yīng)的某個可訪問的域、方法或類型相同時,就會發(fā)生隱藏。
37.屬性對嵌套類的遮掩class X { static class Y { static String Z = "Black"; } static C Y = new C(); } class C { String Z = "White"; } public class T { public static void main(String[] args) { System.out.println(X.Y.Z);// White System.out.println(((X.Y) null).Z);// Black } } 當(dāng)一個變量和一個類型具有相同的名字,并且它們位于相同的作用域時,變量名具有優(yōu)先權(quán)。變量名將遮掩類型名。相似地,變量名和類型名可以遮掩包名。
38.不能重寫不同包中的defualt訪問權(quán)限方法package click; public class P { public void f() { //因為子類沒有重寫該方法,所以調(diào)用的還是父類中的方法 prt(); } void prt() { System.out.println("P"); } } package hack; import click.P; public class T { private static class S extends P { // 這里沒有重寫父類的方法,因為父類方法不可見 void prt() { System.out.println("S"); } } public static void main(String[] args) { new S().f();// P } } 一個包內(nèi)私有(default)的方法不能被位于另一個包中的某個方法直接重寫。
39.重寫、隱藏、重載、遮蔽、遮掩重寫:一個實例方法可以重寫在其超類中可訪問到的具有相同簽名的所有實例方法,從而能動態(tài)分派,換句話說,VM將基于實例的運行期類型來選擇要調(diào)用的重寫方法。重寫是面向?qū)ο缶幊碳夹g(shù)的基礎(chǔ)。 public class P{ public void f(){} } class S extends P{ public void f(){}//重寫 } 重寫時異常要求: l 如果父類方法拋出的是捕獲型異常,則子類也只能拋出同類的捕獲型異常或其子類,或不拋出。 l 父類拋出捕獲型異常,子類卻拋出運行時異常,這是可以,因為拋出運行時就相當(dāng)于沒有拋出任何異常。 l 如果父類拋出的是非捕獲型異常,則子類可以拋出任意的非捕獲型異常,沒有擴大異常范圍這一問題。 l 如果父類拋出的是非捕獲異常,子類也可以不用拋出,這與父類為捕獲型異常是一樣的。 l 如果父類拋出的是非捕獲異常,子類就不能拋出任何捕獲型異常,因為這樣會擴大異常的范圍。 返回類型的協(xié)變:從Java SE5開始子類方法可以返回比它重寫的基類方法更具體的類型,但是這在早先的Java版本是不允許——重寫時子類的返回類型一定要與基類相同。但要注意的是:子類方法返回類型要是父類方法返回類型的子類,而不能反過來。 方法參數(shù)類型協(xié)變:如果父子類同名方法的參數(shù)類型為父子關(guān)系,則為參數(shù)類型協(xié)變,此時不屬于重寫,而是方法的重載,以前版本就是這樣。 如果父類的方法為private時,子類同名的方法的方法名前可以使用任何修飾符來修飾。我們可以隨意地添加一個新的私有成員(方法、域、類型),或都是修改和刪除一個舊的私有成員,而不需要擔(dān)心對該類的客戶造成任何損害。換而言之,私有成員被包含它們的類完全封裝了。 父與子類相同簽名方法不能一靜一動的,即父類的方法是靜態(tài)的,而子類不是,或子類是靜態(tài)的,而父類不是,編譯時都不會通過。 父與子相同簽名方法都是靜態(tài)的方法時,方法名前的修飾符與非靜態(tài)方法重寫的規(guī)則一樣,但不屬于重寫,因為靜態(tài)方法根本就不具有多態(tài)性。 最后,屬于成員也不具有多態(tài)特性,相同名的域?qū)儆陔[藏,而不管域前面的修飾符為什么: class P { public static final String str = "P"; } class S extends P { //編譯能通過。可以是final,這里屬于隱藏 public static final String str = "S"; public static void main(String[] args) { System.out.println(S.str);//s } } 隱藏:一個域、靜態(tài)方法或成員類型可以分別隱藏在其超類中可訪問到的具有相同名字(對方法而言就是相同的方法簽名)的所有域、靜態(tài)方法或成員類型。隱藏一個成員將阻止其被繼承。 public class P{ public static void f(){} } class S extends P{ //隱藏,不會繼承P.f() public static void f(){} } 重載:在某個類中的方法可以重載另一個方法,只要它們具有相同的名字和不同的簽名。由調(diào)用所指定的重載方法是在編譯期選定的。 public class T{ public static void f(int i){} public static void f(String str){}//重載 } 遮蔽:一個變量、方法或類型可以分別遮蔽在一個閉合的文本范圍內(nèi)的具有相同名字的所有變量、方法或類型。如果一個實體被遮蔽了,那么你用它的簡單名是無法引用到它的;根據(jù)實體的不同,有時你根本就無法引用到它。 public class T { private static String str = "feild"; public static void main(String[] args) { String str = "local";// 遮蔽 System.out.println(str);// local // 可以通過適當(dāng)?shù)姆绞絹碇付? System.out.println(T.str);// feild } } public class T { private final int size; // 參數(shù)屬于方法局部范圍類變量,遮蔽了同名成員變量 public T(int size) { //使用適當(dāng)?shù)囊脕碇付? this.size = size; } } 遮掩:一個變量可以遮掩具有相同名字的一個類型,只要它們都在同一個范圍內(nèi):如果這個名字被用于變量與類型都被許可的范圍,那么它將引用到變量上。相似地,一個變量或一個類型可以遮掩一個包。遮掩是唯一一種兩個名字位于不同的名字空間的名字重用形式,這些名字空間包括:變量、包、方法或類型。如果一個類型或一個包被遮掩了,那么你不能通過其簡單名引用到它,除非是在這樣一個上下文環(huán)境中,即語法只允許在其名字空間中出現(xiàn)一種名字: public class T { static String System; public static void main(String[] args) { // !!不能編譯,遮掩 java.lang.System // !! System.out.println("Hello"); // 可明確指定 java.lang.System.out.println("Hello"); } }
40.構(gòu)造器中靜態(tài)常量的引用問題class T { // 先于靜態(tài)常量t初始化,固可以在構(gòu)造器中正常使用 private static final int y = getY(); /* * 嚴(yán)格按照靜態(tài)常量聲明的先后順來初始化:即t初始 * 化完后,才初始化后面的靜態(tài)常量j,所以構(gòu)造器中 * 引用后面的靜態(tài)常量j時,會是0,即內(nèi)存清零時的值 */ public static final T t = new T(); // 后于靜態(tài)常量t初始化,不能在構(gòu)造器中正常使用 private static final int j = getJ(); private final int i; static int getY() { return 2; } static int getJ() { return 2; } // 單例 private T() { i = y - j - 1; //為什么j不是2 System.out.println("y=" + y + " j=" + j);// y=2 j=0 } public int getI() { return i; } public static void main(String[] args) { System.out.println(T.t.getI());// 1 System.out.println(T.j);// 2 } } 該程序所遇到的問題是由類初始化順序中的循環(huán)而引起的:初始化t時需調(diào)用構(gòu)造函數(shù),而調(diào)用構(gòu)造函數(shù)前需初始化所有靜態(tài)成員,此時又包括對t的再次初始化。 T類的初始化是由虛擬機對main方法的調(diào)用而觸發(fā)的。首先,其靜態(tài)域被設(shè)置缺省值,其中y、j被初始化為0,而t被初始化為null。接下來,靜態(tài)域初始器按照其聲明的順序執(zhí)行賦值動作。第一個靜態(tài)域是y,它的值是通過調(diào)用getY獲取的,賦值操作完后結(jié)果為2。第一個初始化完成后,再進行第二個靜態(tài)域的賦值操作,第二個靜態(tài)域為t,它的值是通過調(diào)用T()構(gòu)造函數(shù)來完成的。這個構(gòu)造器會用二個涉及靜態(tài)域y、j來初始化非靜態(tài)域i。通常,讀取一個靜態(tài)域是會引起一個類被初始化,但是我們又已經(jīng)在初始化T類。JavaVM規(guī)范對遞歸的初始化嘗試會直接被忽略掉(按理來說在創(chuàng)建出實例前需初始化完所有的靜態(tài)域后再來創(chuàng)建實例),這樣就導(dǎo)致在靜態(tài)域被初始化之前就調(diào)用了構(gòu)造器,后面的靜態(tài)域j將得不到正常的初始化前就在構(gòu)造器中被使用了,使用時的值為內(nèi)存分配清零時的,即0。 當(dāng)t初始化完后,再初始化j,此時j得到的值為2,但此時對i的初始化過程來說已經(jīng)晚了。 在final類型的靜態(tài)域被初始化之前,存在著讀取其值的可能,而此時該靜態(tài)域包含的還只是其所屬類型的缺省值。這是與直覺想違背的,因為我們通常會將final類型的域看作是常量,但final類型的域只有在其初始化表達(dá)式是字面常量表達(dá)式時才是真正的常量。 再看看另一程序: class T { private final static int i = getJ(); private final static int j; static { j = 2; } static int getJ() { return j; } public static void main(String[] args) { System.out.println(T.j);// 2 /* * 因為上面的語句已經(jīng)初使完T類,所以下面語句是 * 不 會 再引起類的初始化,這里的結(jié)果用的是第一 * 次( 即上面語句)的初始化結(jié)果 */ System.out.println(T.i);// 0 } } 為什么第二個輸出是0而不是2呢?這就是因為VM是嚴(yán)格按照你聲明的順序來初始化靜態(tài)域的,所以前面的引用后面的靜態(tài)域時,基本類型就是0,引用類型就會是null。 所以要記住:靜態(tài)域,甚至是final類型的靜態(tài)域,可能會在它們被初始化之前,被讀走其缺省值。 另,類初始化規(guī)則請參考《惰性初始化》一節(jié)
41.instanceof與轉(zhuǎn)型System.out.println(null instanceof String);//false System.out.println(new Object() instanceof String);//false //編譯能通過 System.out.println((Object) new Date() instanceof String);//false //!!程序不具有實際意義,但編譯時不能通過 //!!System.out.println(new Date() instanceof String); //!!運行時拋ClassCastException,這個程序沒有任何意義,但可以編譯 //!!System.out.println((Date) new Object()); null可以表示任何引用類型,但是instanceof操作符被定義為在其左操作數(shù)為null時返回false。 如果instanceof告訴你一個對象引用是某個特定類型的實例,那么你就可以將其轉(zhuǎn)型為該類型,并調(diào)用該類型的方法,而不用擔(dān)心會拋出ClassCastException或NullPointerException異常。 instanceof操作符有這樣的要求:左操作數(shù)要是一個對象的或引用,右操作數(shù)是一個引用類型,并且這兩個操作數(shù)的類型是要父子關(guān)系(左是右的子類,或右是左的子類都行),否則編譯時就會出錯。
42.父類構(gòu)造器調(diào)用已重寫的方法public class P { private int x, y; private String name; P(int x, int y) { this.x = x; this.y = y; // 這里實質(zhì)上是調(diào)用子類被重寫的方法 name = makeName(); } protected String makeName() { return "[" + x + "," + y + "]"; } public String toString() { return name; } } class S extends P { private String color; S(int x, int y, String color) { super(x, y); this.color = color; } protected String makeName() { return super.makeName() + ":" + color; } public static void main(String[] args) { System.out.println(new S(1, 2, "red"));// [1,2]:null } } 在一個構(gòu)造器調(diào)用一個已經(jīng)被其子類重寫了的方法時,可能會出問題:如果子類重寫的方法要訪問的子類的域還未初始化,因為這種方式被調(diào)用的方法總是在實例初始化之前執(zhí)行。要想避免這個問題,就千萬不要在父類構(gòu)造器中調(diào)用已重寫的方法。
43.靜態(tài)域與靜態(tài)塊的初始順序public class T { public static int i = prt(); public static int y = 1; public static int prt() { return y; } public static void main(String[] args) { System.out.println(T.i);// 0 } } 上面的結(jié)果不是1,而是0,為什么? 類初始化是按照靜態(tài)域或靜態(tài)塊在源碼中出現(xiàn)的順序去執(zhí)行這些靜態(tài)初始器的(即誰先定義,就先初始化誰),上現(xiàn)程序中由于i先于y聲明,所以先初始化i,但由于i初始化時需要由y來決定,此時y又未初始化,實為初始前的值0,所以i的最后結(jié)果為0。
44.請使用引用類型調(diào)用靜態(tài)方法public class Null { public static void greet() { System.out.println("Hello world!"); } public static void main(String[] args) { ((Null) null).greet(); } } 上面程序運行時不會打印NullPointerException異常,而是輸出"Hello world!",關(guān)鍵原因是:調(diào)用靜態(tài)方法時將忽略前面的調(diào)用對象或表達(dá)示,只與對象或表達(dá)式計算結(jié)果的類型有關(guān)。 在調(diào)用靜態(tài)方法時,一定要使用類去調(diào)用,或是靜態(tài)導(dǎo)入后直接使用。
45.循環(huán)中的不能聲明局部變量for (int i = 0; i < 1; i++) Object o ; //!! 編譯不能通過 for (int i = 0; i < 1; i++) Object o = new Object(); //!! 編譯不能通過 一個本地變量聲明看起來像是一條語句,但是從技術(shù)上來說不是。 Java語言規(guī)范不允許一個本地變量聲明語句作為一條語句在for、while或do循環(huán)中重復(fù)執(zhí)行。 一個本地變量聲明作為一條語句只能直接出現(xiàn)在一個語句塊中(一個語句塊是由一對花 括號以及包含在這對花括號中的語句和聲明構(gòu)成的): for (int i = 0; i < 1; i++) { Object o = new Object(); // 編譯OK }
46.內(nèi)部類反射public class Outer { public class Inner { public String toString() { return "Hello world"; } } public void getInner() { try { // 普通方式創(chuàng)建內(nèi)部類實例 System.out.println(new Outer().new Inner());// Hello world //!! 反射創(chuàng)建內(nèi)部類,拋異常:java.lang.InstantiationException:Outer$Inner System.out.println(Inner.class.newInstance()); } catch (Exception e) { } } public static void main(String[] args) { new Outer().getInner(); } } 上面因為構(gòu)造內(nèi)部類時外部類實例不存在而拋異常。 一個非靜態(tài)的嵌套類的構(gòu)造器,在編譯的時候會將一個隱藏的參數(shù)作為它的第一個參數(shù),這個參數(shù)表示它的直接外圍實例。如果使用反射創(chuàng)建內(nèi)部類,則要傳遞個隱藏參數(shù)的唯一方法就是使用java.lang.reflect.Constructor: Constructor c = Inner.class.getConstructor(Outer.class);//獲取帶參數(shù)的內(nèi)部類構(gòu)造函數(shù) System.out.println(c.newInstance(Outer.this));//反射時還需傳進外圍類
新聞熱點
疑難解答