文章目錄
1.繼承文法
當建立一個類時,要麼顯示的指定它繼承的類,例如A extends B,不然它會隱式的繼承根類Object。
至於繼承的具體文法,這裡我就不贅述了,這裡我要寫的是關於繼承中的許可權問題:
以A extends B為例,B中的方法必須為public的,這一點非常重要。請記住,如果沒有加任何許可權修飾詞,那麼成員的預設存取權限是包存取權限,也就是說包裡面的成員都可以訪問其方法。
但是,其他包中的成員比如C extends B,就只能訪問B中public成員。
2.初始化基類(父類,超類)
在每個子類的構造器()有參數或者無參數)的第一行都會預設的調用基類的無參數構造器。
對於情況1:子類A的構造器的第一行不論寫不寫上super()(不寫的話,系統會預設添上),系統都會先調用super()。
對於情況2:子類A的構造器的第一行如果寫上super(對應的參數),會調用基類B相應的構造器;如果不寫上super(對應的參數),系統都會先調用super()。
對於情況3:子類A的構造器的第一行必須寫上super(對應的參數),不然編譯會出錯,因為你不寫上super(對應的參數)的話,系統會先調用super(),可是現在基類中沒有無參數的構造器。
總結:調用基類(父類)構造器必須是你在匯出類(子類)構造器中要做的第一件事情。
補充一點:new A()時,先預設調用super(),這也就是說按初始化順序(可見我的相關博文)先先初始化B類。
For Example:
class Component1 {
public Component1(String s) { System.out.println("Component1"); }
}
class Component2 {
public Component2(String s) { System.out.println("Component2"); }
}
class Component3 {
public Component3(String s) { System.out.println("Component3"); }
}
class Root {
Component1 c1 = new Component1("q");
Component2 c2 = new Component2("a");
Component3 c3 = new Component3("z");
public Root(String s) { System.out.println("Root"); }
}
class Stem extends Root {
Component1 c1 = new Component1("w");
Component2 c2 = new Component2("s");
//Component3 c3 = new Component3();
public Stem(String s) {
super(s);
System.out.println("Stem"); }
}
public class Test {
public static void main(String args[]) {
new Stem("x");
}
}
結果:
3.protected關鍵字
在實際項目中,經常會想要境某些事物儘可能對這個世界隱藏起來,但仍然允許匯出類或者其他位於同一個包內的類來說,它卻是可以訪問的。(protected也提供了包內存取權限)
4.向上轉型(有意思喔)
繼承技術中最重要的方面是用來表現新類和基類之間的關係。這種關係可以用“新類是現有類的一種類型”這句話加以概括。
例如:
class Instrument{
void play(){System.out.println("play");
}
static void tune(Instrument i){i.play();
}
}
public class AB extends Instrument {
public static void main(String args[]) {
AB ab =new AB();
Instrument.tune(ab);
}
}
結果:play
在此例中,tune()方法可以接受Instrument引用,但是在AB.main方法中,傳遞給tune()的參數是一個AB的引用。這意味著我們可以準確定義的說AB對象也是一種類型的Instrument。在tune()中,程式碼可以對Instrument和它所有的匯出類起作用,這種將AB引用轉換為Instrument引用的動作,我們稱之為向上轉型。
再例如,麻雀是鳥類的一種(鳥類的子類),而鳥類則是動物中的一種(動物的子類)。我們現實中也經常這樣說:麻雀是鳥。這兩種說法實際上就是所謂的向上轉型,通俗地說就是子類轉型成父類。來看下面的代碼:
package a.b;
public class A {
public void a1() {
System.out.println("Superclass");
}
}
A的子類B:
package a.b;
public class B extends A {
public void a1() {
System.out.println("Childrenclass"); //覆蓋父類方法
}
public void b1(){} //B類定義了自己的新方法
}
C類:
package a.b;
public class C {
public static void main(String[] args) {
A a = new B(); //向上轉型
a.a1();
}
}
如果運行C,輸出的是Superclass 還是Childrenclass?不是你原來預期的Superclass,而是Childrenclass。這是因為a實際上指向的是一個子類對象。當然,你不用擔心,Java虛擬機器會自動準確地識別出究竟該調用哪個具體的方法。不過,由於向上轉型,a對象會遺失和父類不同的方法,例如b1()。
package a.b;
public class A {
public void a1() {
System.out.println("Superclass");
}
}
A的子類B:
package a.b;
public class B extends A {
//public void a1() {
// System.out.println("Childrenclass"); //覆蓋父類方法
//}
public void b1(){} //B類定義了自己的新方法
}
C類:
package a.b;
public class C {
public static void main(String[] args) {
A a = new B(); //向上轉型
a.a1();
}
}
如果這樣修改代碼,結果是Superclasss。(先在子類找尋那個方法,沒有的話就調用基類的。)
有人可能會提出疑問:這不是多此一舉嗎?我們完全可以這樣寫:
B a = new B();
a.a1();
確實如此!但這樣就喪失了面向抽象的編程特色,降低了可擴充性。其實,不僅僅如此,向上轉型還可以減輕編程工作量。來看下面的顯示器類Monitor:
package a.b;
public class Monitor{
public void displayText() {}
public void displayGraphics() {}
}
液晶顯示器類LCDMonitor是Monitor的子類:
package a.b;
public class LCDMonitor extends Monitor {
public void displayText() {
System.out.println("LCD display text");
}
public void displayGraphics() {
System.out.println("LCD display graphics");
}
}
陰極射線管顯示器類CRTMonitor自然也是Monitor的子類:
package a.b;
public class CRTMonitor extends Monitor {
public void displayText() {
System.out.println("CRT display text");
}
public void displayGraphics() {
System.out.println("CRT display graphics");
}
}
電漿顯示器PlasmaMonitor也是Monitor的子類:
package a.b;
public class PlasmaMonitor extends Monitor {
public void displayText() {
System.out.println("Plasma display text");
}
public void displayGraphics() {
System.out.println("Plasma display graphics");
}
}
現在有一個MyMonitor類。假設沒有向上轉型,MyMonitor類代碼如下:
package a.b;
public class MyMonitor {
public static void main(String[] args) {
run(new LCDMonitor());
run(new CRTMonitor());
run(new PlasmaMonitor());
}
public static void run(LCDMonitor monitor) {
monitor.displayText();
monitor.displayGraphics();
}
public static void run(CRTMonitor monitor) {
monitor.displayText();
monitor.displayGraphics();
}
public static void run(PlasmaMonitor monitor) {
monitor.displayText();
monitor.displayGraphics();
}
}
可能你已經意識到上述代碼有很多重複代碼,而且也不易維護。有了向上轉型,代碼可以更為簡潔:
package a.b;
public class MyMonitor {
public static void main(String[] args) {
run(new LCDMonitor()); //向上轉型
run(new CRTMonitor()); //向上轉型
run(new PlasmaMonitor()); //向上轉型
}
public static void run(Monitor monitor) { //父類執行個體作為參數
monitor.displayText();
monitor.displayGraphics();
}
}
向下轉型
子類轉型成父類是向上轉型,反過來說,父類轉型成子類就是向下轉型。但是,向下轉型可能會帶來一些問題:我們可以說麻雀是鳥,但不能說鳥就是麻雀。來看下面的例子:
A類:
package a.b;
public class A {
void aMthod() {
System.out.println("A method");
}
}
A的子類B:
package a.b;
public class B extends A {
void bMethod1() {
System.out.println("B method 1");
}
void bMethod2() {
System.out.println("B method 2");
}
}
C類:
package a.b;
public class C {
public static void main(String[] args) {
A a1 = new B(); // 向上轉型
a1.aMthod(); // 調用父類aMthod(),a1遺失B類方法bMethod1()、bMethod2()
B b1 = (B) a1; // 向下轉型,編譯無錯誤,運行時無錯誤
b1.aMthod(); // 調用父類A方法
b1.bMethod1(); // 調用B類方法
b1.bMethod2(); // 調用B類方法
A a2 = new A();
B b2 = (B) a2; // 向下轉型,編譯無錯誤,運行時將出錯
b2.aMthod();
b2.bMethod1();
b2.bMethod2();
}
}
從上面的代碼我們可以得出這樣一個結論:向下轉型需要使用強制轉換。運行C程式,控制台將輸出:
Exception in thread "main" java.lang.ClassCastException: a.b.A cannot be cast to a.b.B at
a.b.C.main(C.java:14)
A method
A method
B method 1
B method 2
其實黑體部分的向下轉型代碼後的注釋已經提示你將發生執行階段錯誤。為什麼前一句向下轉型代碼可以,而後一句代碼卻出錯?這是因為a1指向一個子類B的對象,所以子類B的執行個體對象b1當然也可以指向a1。而a2是一個父類對象,子類對象b2不能指向父類對象a2。那麼如何避免在執行向下轉型時發生運行時ClassCastException異常?使用5.7.7節學過的instanceof就可以了。我們修改一下C類的代碼:
A a2 = new A();
if (a2 instanceof B) {//試a2是否是B類的執行個體,返回boolean類型的資料
B b2 = (B) a2;
b2.aMthod();
b2.bMethod1();
b2.bMethod2();
}
這樣處理後,就不用擔心類型轉換時發生ClassCastException異常了。