【Java基本功】一文了解Java中繼承、封裝、多型的細節
本節主要介紹Java面向物件三大特性:繼承 封裝 多型,以及其中的原理。
本文會結合虛擬機器對引用和物件的不同處理來介紹三大特性的原理。
繼承
Java中的繼承只能單繼承,但是可以通過內部類繼承其他類來實現多繼承。
public class Son extends Father{public void go () {System.out.println("son go");}public void eat () {System.out.println("son eat");}public void sleep() {System.out.println("zzzzzz");}public void cook() {//匿名內部類實現的多繼承new Mother().cook();//內部類繼承第二個父類來實現多繼承Mom mom = new Mom();mom.cook();}private class Mom extends Mother {@Overridepublic void cook() {System.out.println("mom cook");}}}
封裝
封裝主要是因為Java有訪問許可權的控制。public > protected > package = default > private。封裝可以保護類中的資訊,只提供想要被外界訪問的資訊。
類的訪問範圍
A、public 包內、包外,所有類中可見B、protected 包內所有類可見,包外有繼承關係的子類可見(子類物件可呼叫)C、(default)表示預設,不僅本類訪問,而且是同包可。D、private 僅在同一類中可見
多型
多型一般可以分為兩種,一個是重寫,一個是過載。
-
重寫是由於繼承關係中的子類有一個和父類同名同參數的方法,會覆蓋掉父類的方法。過載是因為一個同名方法可以傳入多個引數組合。
-
-
注意,同名方法如果引數相同,即使返回值不同也是不能同時存在的,編譯會出錯。
-
-
從jvm實現的角度來看,重寫又叫執行時多型,編譯時看不出子類呼叫的是哪個方法,但是執行時運算元棧會先根據子類的引用去子類的類資訊中查詢方法,找不到的話再到父類的類資訊中查詢方法。
-
-
而過載則是編譯時多型,因為編譯期就可以確定傳入的引數組合,決定呼叫的具體方法是哪一個了。
向上轉型和向下轉型:
-
public static void main(String[] args) {
-
Son son = new Son();
-
//首先先明確一點,轉型指的是左側引用的改變。
-
//father引用型別是Father,指向Son例項,就是向上轉型,既可以使用子類的方法,也可以使用父類的方法。
-
//向上轉型,此時執行father的方法
-
Father father = son;
-
father.smoke();
-
//不能使用子類獨有的方法。
-
// father.play();編譯會報錯
-
father.drive();
-
//Son型別的引用指向Father的例項,所以是向下轉型,不能使用子類非重寫的方法,可以使用父類的方法。
-
-
//向下轉型,此時運行了son的方法
-
Son son1 = (Son) father;
-
//轉型後就是一個正常的Son例項
-
son1.play();
-
son1.drive();
-
son1.smoke();
-
-
//因為向下轉型之前必須先經歷向上轉型。
-
//在向下轉型過程中,分為兩種情況:
-
-
//情況一:如果父類引用的物件如果引用的是指向的子類物件,
-
//那麼在向下轉型的過程中是安全的。也就是編譯是不會出錯誤的。
-
//因為執行期Son例項確實有這些方法
-
Father f1 = new Son();
-
Son s1 = (Son) f1;
-
s1.smoke();
-
s1.drive();
-
s1.play();
-
-
//情況二:如果父類引用的物件是父類本身,那麼在向下轉型的過程中是不安全的,編譯不會出錯,
-
//但是執行時會出現java.lang.ClassCastException錯誤。它可以使用instanceof來避免出錯此類錯誤。
-
//因為執行期Father例項並沒有這些方法。
-
Father f2 = new Father();
-
Son s2 = (Son) f2;
-
s2.drive();
-
s2.smoke();
-
s2.play();
-
-
//向下轉型和向上轉型的應用,有些人覺得這個操作沒意義,何必先向上轉型再向下轉型呢,不是多此一舉麼。其實可以用於方法引數中的型別聚合,然後具體操作再進行分解。
-
//比如add方法用List引用型別作為引數傳入,傳入具體類時經歷了向下轉型
-
add(new LinkedList());
-
add(new ArrayList());
-
-
//總結
-
//向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用型別來判斷使用哪個方法
-
//並且在傳入方法時會自動進行轉型(有需要的話)。執行期將引用指向例項,如果是不安全的轉型則會報錯。
-
//若安全則繼續執行方法。
-
-
}
-
public static void add(List list) {
-
System.out.println(list);
-
//在操作具體集合時又經歷了向上轉型
-
// ArrayList arr = (ArrayList) list;
-
// LinkedList link = (LinkedList) list;
-
}
總結: 向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用型別來判斷使用哪個方法。並且在傳入方法時會自動進行轉型(有需要的話)。執行期將引用指向例項,如果是不安全的轉型則會報錯,若安全則繼續執行方法。
編譯期的靜態分派
其實就是根據引用型別來呼叫對應方法。
-
public static void main(String[] args) {
-
Father father = new Son();
-
靜態分派 a= new 靜態分派();
-
-
//編譯期確定引用型別為Father。
-
//所以呼叫的是第一個方法。
-
a.play(father);
-
//向下轉型後,引用型別為Son,此時呼叫第二個方法。
-
//所以,編譯期只確定了引用,執行期再進行例項化。
-
a.play((Son)father);
-
//當沒有Son引用型別的方法時,會自動向上轉型呼叫第一個方法。
-
a.smoke(father);
-
//
-
-
-
}
-
public void smoke(Father father) {
-
System.out.println("father smoke");
-
}
-
public void play (Father father) {
-
System.out.println("father");
-
//father.drive();
-
}
-
public void play (Son son) {
-
System.out.println("son");
-
//son.drive();
-
}
方法過載優先順序匹配
-
public static void main(String[] args) {
-
方法過載優先順序匹配 a = new 方法過載優先順序匹配();
-
//普通的過載一般就是同名方法不同引數。
-
//這裡我們來討論當同名方法只有一個引數時的情況。
-
//此時會呼叫char引數的方法。
-
//當沒有char引數的方法。會呼叫int型別的方法,如果沒有int就呼叫long
-
//即存在一個呼叫順序char -> int -> long ->double -> ..。
-
//當沒有基本型別對應的方法時,先自動裝箱,呼叫包裝類方法。
-
//如果沒有包裝類方法,則呼叫包裝類實現的介面的方法。
-
//最後再呼叫持有多個引數的char...方法。
-
a.eat('a');
-
a.eat('a','c','b');
-
}
-
public void eat(short i) {
-
System.out.println("short");
-
}
-
public void eat(int i) {
-
System.out.println("int");
-
}
-
public void eat(double i) {
-
System.out.println("double");
-
}
-
public void eat(long i) {
-
System.out.println("long");
-
}
-
public void eat(Character c) {
-
System.out.println("Character");
-
}
-
public void eat(Comparable c) {
-
System.out.println("Comparable");
-
}
-
public void eat(char ... c) {
-
System.out.println(Arrays.toString(c));
-
System.out.println("...");
-
}
-
-
// public void eat(char i) {
-
// System.out.println("char");
-
// }