thinking in java (二) ----- 多型
面向物件的三大特性:封裝,繼承,多型。
封裝
封裝將類的內部資訊隱藏,不允許外部直接訪問(private),通過該類提供的方法(get(),set())來訪問操作類隱藏的資訊。隱藏了類的資訊,留出了訪問操作的介面。
繼承
為了複用父類的程式碼,兩個類如果有is-a關係,子類就可以繼承父類。
多型
多型就是指:方法中定義的引用變數(形參reference)指向的具體型別,和通過該reference呼叫的方法在程式設計的時候並不確定,要到了程式執行的時候才會確定。簡而言之:引用變數指向的物件,和相應的方法呼叫,必須在程式執行時才能確定。
因為在程式執行的時候才能確定具體的類,這樣,不用修改原始碼,就可以讓引用變數繫結在不同的類實現
舉一個實際的例子:你去抽獎轉轉盤,獎品有A,B,C三種(轉盤指標最終指向的獎品不確定(引用變數指向的具體物件不確定))。轉完了盤你才知道你中什麼獎(程式執行時,才確定呼叫的方法)。這就是多型表現形式:
具體瞭解多型之前,需要了解向上轉型(UpCasting)
向上轉型(Upcasting)
轉型必須是在有繼承關係之間的類中進行,Upcasting簡而言之就是:父類引用指向子類物件。表現形式為
Father father = new Son();
在Upcasting中,指向子類物件的父類reference,只能訪問父類中擁有的方法和屬性
多型的實現
實現條件
java實現多型必須有三個條件:繼承,重寫,向上轉型。
繼承:在多型中必須存在有繼承關係的子類和父類。
重寫:子類對父類中某些方法進行重新定義,在呼叫這些方法時就會呼叫子類的方法。
向上轉型:在多型中需要將子類的引用賦給父類物件,只有這樣該引用才能夠具備技能呼叫父類的方法和子類的方法。
只有滿足了上述三個條件,我們才能夠在同一個繼承結構中使用統一的邏輯實現程式碼處理不同的物件,從而達到執行不同的行為。
對於Java而言,它多型的實現機制遵循一個原則:當父類物件引用變數指向子類物件時,子類物件的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在父類中定義過的,也就是被子類覆蓋的方法。
實現形式
在java中有兩種形式可以實現多型,介面和繼承。
基於繼承實現的多型
基於繼承機制和主要表現在父類和子類對方法的重寫,對一個方法表現的不同行為
public class Wine {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wine(){
}
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return null;
}
}
public class JNC extends Wine{
public JNC(){
setName("JNC");
}
/**
* 重寫父類方法,實現多型
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
public class JGJ extends Wine{
public JGJ(){
setName("JGJ");
}
/**
* 重寫父類方法,實現多型
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重寫toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
public class Test {
public static void main(String[] args) {
//定義父類陣列
Wine[] wines = new Wine[2];
//定義兩個子類
JNC jnc = new JNC();
JGJ jgj = new JGJ();
//父類引用子類物件
wines[0] = jnc;
wines[1] = jgj;
for(int i = 0 ; i < 2 ; i++){
System.out.println(wines[i].toString() + "--" + wines[i].drink());
}
System.out.println("-------------------------------");
}
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------
在上面的程式碼中JNC、JGJ繼承Wine,並且重寫了drink()、toString()方法,程式執行結果是呼叫子類中方法,輸出JNC、JGJ的名稱,這就是多型的表現。不同的物件可以執行相同的行為,但是他們都需要通過自己的實現方式來執行,這就要得益於向上轉型了。
基於介面實現的多型
//汽車介面
interface Car{
//要求 介面中有:汽車名稱和售價
String getName();
int getPrice();
}
//寶馬類
class BMW implements Car{
@Override
public String getName() {
// TODO Auto-generated method stub
//return null;
return "寶馬";
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
//return 0;
return 300000;
}
}
//奇瑞QQ
class CheryQQ implements Car{
@Override
public String getName() {
// TODO Auto-generated method stub
return "奇瑞QQ";
}
@Override
public int getPrice() {
// TODO Auto-generated method stub
return 40000;
}
}
//汽車出售店
class CarShop{
//收入
private int money=0;
//賣出一部汽車
public void sellCar(Car car){
System.out.println("車型:"+car.getName()+"價格:"+car.getPrice());
//增加賣出車售價的收入
money+=car.getPrice();
}
//售車總收入
public int getMoney(){
return money;
}
}
//測試類
public class jieKouDemo {
public static void main(String[]args){
CarShop shop=new CarShop();
//賣出一輛寶馬
shop.sellCar(new BMW());
//賣出一輛奇瑞QQ
shop.sellCar(new CheryQQ());
System.out.println("總收入:"+shop.getMoney());
}
}
介面是通過實現介面並覆蓋介面中同一方法的幾不同的類體現的。
繼承都是單繼承,只能為一組相關的類提供一致的服務介面。但是介面可以是多繼承多實現,它能夠利用一組相關或者不相關的介面進行組合與擴充,能夠對外提供一致的服務介面。所以它相對於繼承來說有更好的靈活性。
一個較為複雜的多型例項
首先需要了解的是多型的優先順序注意:優先順序從高到低:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
分析結果
第一行:,a1是A的例項物件,this指向A,但是A中沒有this.show(b),所以到父類找,但是A沒有父類(object不算),於是尋找下一個優先順序,this.show(super b),B的父類是A,所以這裡是this.show(a).結果為 A and A,
第二行:同上
第三行:直接this.show(d)
第四行:a2是指向B的例項化物件,輸出B裡面重寫的
第五行:a2是B類的引用物件,型別為A,所以this指向A類,然後在A類裡面找this.show(C)方法,沒有找到,所以到了super.show(C)方法,由於A類沒有超類,所以到了this.show(super C),C的超類是B,所以在A類裡面找show(B),同樣沒有找到,發現B還有超類,即A,所以還繼續在A類裡面找show(A)方法,找到了,但是由於a2是一個類B的引用物件,而B類裡面覆蓋了A類的show(A)方法,所以最終執行的是B類裡面的show(A)方法,即輸出B and A;
第六行:略
第七行:
第八行:b是B類的一個例項化物件,首相執行this.show(C),在B類裡面找show(C)方法,沒有找到,所以到了super.show(c),B的超類是A,所以在A類中找show(C)方法,沒有找到,於是到了this.show(super C),C的超類是B,所以在B類中找show(B)f方法,找到了,所以執行B類中的show(B)方法輸出B and B;
第九行:b是B類的一個例項化物件,首相執行this.show(D),在B類裡面找show(D)方法,沒有找到,於是到了super.show(D),B的超類是A類,所以在A類裡面找show(D)方法,找到了,輸出A and D;