本節主要介紹Java面向對象三大特性:繼承 封裝 多態,以及其中的原理。java
本文會結合虛擬機對引用和對象的不一樣處理來介紹三大特性的原理。數組
Java中的繼承只能單繼承,可是能夠經過內部類繼承其餘類來實現多繼承。安全
<pre>jvm
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 {
@Override
public void cook() {
System.out.println("mom cook");
}
}
}
封裝主要是由於Java有訪問權限的控制。public > protected > package = default > private。封裝能夠保護類中的信息,只提供想要被外界訪問的信息。ide
類的訪問範圍spa
A、public 包內、包外,全部類中可見
B、protected 包內全部類可見,包外有繼承關係的子類可見
(子類對象可調用)
C、(default)表示默認,不只本類訪問,並且是同包可。
D、private 僅在同一類中可見
</pre>code
多態通常能夠分爲兩種,一個是重寫,一個是重載。對象
重寫是因爲繼承關係中的子類有一個和父類同名同參數的方法,會覆蓋掉父類的方法。重載是由於一個同名方法能夠傳入多個參數組合。
注意,同名方法若是參數相同,即便返回值不一樣也是不能同時存在的,編譯會出錯。
從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;
}
總結: 向上轉型和向下轉型都是針對引用的轉型,是編譯期進行的轉型,根據引用類型來判斷使用哪一個方法。而且在傳入方法時會自動進行轉型(有須要的話)。運行期將引用指向實例,若是是不安全的轉型則會報錯,若安全則繼續執行方法。blog
其實就是根據引用類型來調用對應方法。繼承
<pre>
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();
}
</pre>
<pre>
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");
// }