1. 程式人生 > >Java 中的多型,一次講個夠之繼承關係中的多型

Java 中的多型,一次講個夠之繼承關係中的多型

多型是繼封裝、繼承之後,面向物件的第三大特性。
現實事物經常會體現出多種形態,如學生,學生是人的一種,則一個具體的同學張三既是學生也是人,即出現兩種形態。	
Java作為面向物件的語言,同樣可以描述一個事物的多種形態。如Student類繼承了Person類,一個Student的物件便既是Student,又是Person。
Java中多型的程式碼體現在一個子類物件(實現類物件)既可以給這個子類(實現類物件)引用變數賦值,又可以給這個子類(實現類物件)的父類(介面)變數賦值。
如Student類可以為Person類的子類。那麼一個Student物件既可以賦值給一個Student型別的引用,也可以賦值給一個Person型別的引用。
最終多型體現為父類引用變數可以指向子類物件。

多型的前提是必須有子父類關係或者類實現介面關係,否則無法完成多型。 在使用多型後的父類引用變數呼叫方法時,會呼叫子類重寫後的方法。

文字再怎麼講,都不夠生動,直接用程式碼來體現

老爸要喝酒,那今天喝什麼酒呢,
public class Wine {

    public void drinkWine(){
        System.out.println("===今天我要喝什麼酒呢====");
        Wine();
    }

    public void Wine(){
        System.out.println("===看看俺今天能喝啥子喲====");
    }
}


public class JNC extends Wine {

    /**
     * @desc 子類過載父類方法
     *        父類中不存在該方法,向上轉型後,父類是不能引用該方法的
     * @param a
     * @return void
     */
    public void drinkWine(String a){
        System.out.println("======今天我要喝劍南春====");
        Wine();
    }

    /**
     * 子類重寫父類方法
     * 指向子類的父類引用呼叫Wine時,必定是呼叫該方法
     */
    public void Wine(){
        System.out.println("=====劍南春喝上啦,好開森=====");
    }
}
public class Test {

    public static void main(String[] args) {
        Wine a = new JNC();
        a.drinkWine();
        a.Wine();

        Wine b = new Wine();
        b.drinkWine();
        b.Wine();

        JNC c= new JNC();
        c.drinkWine("qq");
    }
}

 


先來看看這一段,

Wine a = new JNC();
a.drinkWine();
a.Wine();

子類劍南春中的drinkWine帶有引數,而父類中的drinkWine不帶有引數,即父類不存在這個方法

執行的時候,呼叫的是父類的drinkWine,先輸出了 

===今天我要喝什麼酒呢====

之後繼續呼叫Wine方法,這個時候是去了子類中,指向子類的父類引用呼叫Wine時,必定是呼叫子類中的方法,於是輸出了

=====劍南春喝上啦,好開森=====

 

上面的Wine和JNC中的方法,都沒有帶Static,如果加上Static呢,看一下程式碼和執行的結果

class Wine {

    public static void drinkWine() {
        System.out.println("===今天我要喝什麼酒呢====");
        Wine();
    }

    public static void Wine() {
        System.out.println("===看看俺今天能喝啥子喲====");
    }
}


class JNC extends Wine {

    public static void drinkWine(String a) {
        System.out.println("======今天我要喝劍南春====");
        Wine();
    }

    public static void Wine() {
        System.out.println("=====劍南春喝上啦,好開森=====");
    }
}

class Test {

    public static void main(String[] args) {
        Wine a = new JNC();
        a.drinkWine();
        a.Wine();
    }
}

可以看到,靜態方法,即使向上轉型,也只能呼叫自己的方法啦

 

上面比較的是子類和父類的方法,在非靜態方法和靜態方法,父類引用子類的方法,非靜態方法下可以呼叫子類同名的建構函式方法,不能呼叫不一樣的構造方法

靜態方法中,子類向上轉型後,父類引用都不能進行呼叫子類的方法

下面來給父類和子類一些變數,以及一些方法,方法都是非靜態的

父類,定義了一些姓名,年齡,興趣愛好等的變數

和一些say和hobby的方法

public class Father {

    private String fathername;
    private int fatherage;
    private String fahterhobby;

    public void say() {
        System.out.println("==我是你爸爸真偉大,養你這麼大==");
        myhobby();
    }

    public void myhobby() {
        System.out.println("==我是你爸爸真偉大,只要你媽媽==");
    }


    public Father() {
        super();
    }


    public Father(String fathername, int fatherage, String fahterhobby) {
        this.fathername = fathername;
        this.fatherage = fatherage;
        this.fahterhobby = fahterhobby;
    }


//省略getters and setters
    
    @Override
    public String toString() {
        return "Father{" +
                "fathername='" + fathername + '\'' +
                ", fatherage=" + fatherage +
                ", fahterhobby='" + fahterhobby + '\'' +
                '}';
    }

    public String toString(String fathername, int fatherage, String fahterhobby) {
        return "Father{" +
                "fathername='" + fathername + '\'' +
                ", fatherage=" + fatherage +
                ", fahterhobby='" + fahterhobby + '\'' +
                '}';
    }
}

子類和父類差不多,其實不應該定義一樣的變數,雖然名稱改了一下

public class Son extends Father {

    public void say(){
        System.out.println("==爸爸我要出去玩===");
    }

    public void say(String s){
        System.out.println("==爸爸我要出去玩===" +s);
    }


    public void myhobby(String aaa){
        System.out.println("==爸爸給我買這個玩具: " + aaa);
    }


    private String sonname;
    private int sonage;
    private String sonhobby;

    public Son(String sonname, int sonage, String sonhobby) {
        this.sonname = sonname;
        this.sonage = sonage;
        this.sonhobby = sonhobby;
    }

    public Son(String fathername, int fatherage, String fahterhobby, String sonname, int sonage, String sonhobby) {
        super(fathername, fatherage, fahterhobby);
        this.sonname = sonname;
        this.sonage = sonage;
        this.sonhobby = sonhobby;
    }

    //省略getters and setters

    @Override
    public String toString(String sonname, int sonage, String sonhobby) {
        return "Son{" +
                "sonname='" + sonname + '\'' +
                ", sonage=" + sonage +
                ", sonhobby='" + sonhobby + '\'' +
                '}';
    }
}

測試:

主要測試如下:

子類中,對say()無引數的方法進行了改寫,輸出內容不一致了,且有自己新建立的say(String s)帶有入參的方法

子類對myhobby也新增了,有了入參

子類中對toString方法也帶有入參的

例項化子類物件,父類引用,即向上轉型了,呼叫say(),這個方法子類中有; 呼叫myhobby(), 子類中沒有myhobby(), 只有myhobby(String aaa) 然後還要呼叫toString(), 有引數和無引數的

public class test {

    public static void main(String[] args) {
        Father father = new Son("張三",35,"LOL","張四",5,"Learn");
        System.out.println("例項化一個Son物件,用父親接收");
        father.say();
        father.myhobby();
        //程式碼報錯
        // father.myhobby("LOL驚奇娃娃");
        System.out.println(father.toString());
        System.out.println(father.toString("張三",35,"LOL"));

        System.out.println("\n");


        Son son = new Son("張四",5,"Learn");
        System.out.println("例項化一個Son物件,用Son接收");
        son.say();
        son.say("上海迪士尼");
        son.myhobby();
        son.myhobby("LOL驚奇娃娃");
        System.out.println(son.toString("張四",5,"Learn"));

    }
}

 執行結果如下:

1. 父類引用呼叫say(), 由於子類中有這個方法,呼叫的是子類的這個方法;

呼叫myhobby();, 由於子類中沒有這個方法,呼叫的是父類的這個方法;

呼叫子類中帶有引數的方法,father.myhobby("LOL驚奇娃娃");程式碼直接報錯了

呼叫toString(),分別是無引數和有引數,因為子類中只有三個有引數的,沒有無引數的,就無引數返回的是父類的,有引數返回的是子類的

 

2. 子類引用指向子類物件,呼叫say()無引數的和有引數的,由於子類中都有,都是子類自己的方法進行返回

呼叫myobby()無引數的和有引數的,由於子類中沒有無引數的,就去爸爸那兒找了找,返回了爸爸的愛好,子類中有帶引數的,就返回了子類自己的

呼叫toString(3個引數略),就返回了自己的方法,如果呼叫不帶引數的toString(),就是返回一個父親中的方法了。。。

 

總結: 父類引用指向子類,呼叫返回的時候,看看自己家有沒有啊,有啊,哦,不管了,先去兒子家找找,兒子有啊,兒子用你家的,兒子沒有啊,回家用自己的方法吧

子類引用指向子類,呼叫返回的時候,先去自己(即兒子)方法中看看,我自己沒有呀,去父親方法中看看吧

上面看的繼承中的父子關係是,爸爸有,兒子有,兒子有新的

下面繼續看繼承,論父子之間的關係之,爸爸沒有,兒子有;

員工物件,只有一個mailCheck()方法,定義了一些變數

public class Employee {

    private String name;
    private String address;
    private int number;

    public Employee(String name, String address, int number) {
        //System.out.println("Employee 建構函式");
        this.name = name;
        this.address = address;
        this.number = number;
    }

    public void mailCheck() {
        System.out.println("郵寄支票給: " + this.name + " " + this.address);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", number=" + number +
                '}';
    }

    //省略getters and setters
Salary物件繼承了父類
public class Salary extends Employee{

    private double yearsalary; // 全年工資

    public double getSalary() {
        return yearsalary;
    }

    public void setSalary(double salary) {
        if(salary >= 0.0)
        this.yearsalary = salary;
    }

    public double computePay() {
        System.out.println("計算工資,付給:" + getName());
        return yearsalary/12;
    }

    public Salary(String name, String address, int number, double yearsalary) {
        super(name, address, number);
        setSalary(yearsalary);
    }

    public void mailCheck() {
        System.out.println("Salary 類的 mailCheck 方法 ");
        System.out.println("郵寄支票給:" + getName()+ " ,工資為:" + yearsalary);
    }
}

測試如下

public class Demo {

    public static void main(String [] args) {
        Salary s = new Salary("員工 A", "北京", 3, 360000.00);
        s.mailCheck();
        double sa = s.computePay();
        System.out.println(sa);

        System.out.println("\n");

        Employee e = new Salary("員工 B", "上海", 2, 240000.00);
        e.mailCheck();
        double salary = ((Salary) e).computePay();
        System.out.println(salary);
    }
}

必要要強轉  ((Salary) e).computePay(); 即必須向下轉型,父親引用轉化為子類的,再去呼叫子類的方法

即回答:論父子之間的關係之,爸爸沒有,兒子有;

本來是爸爸型別的引用,將爸爸型別的引用向下轉型,然後呼叫

 

問題來啦,我是父親,我有兩個兒子或者多個兒子呢,鬧啥啊, 爸爸沒有這個方法,兒子們都有呢,咋辦咧

public class Animal {

    void eat() {
        System.out.println("Animal");
    }
}

public class Cat extends Animal {

    public void eat() {
        System.out.println("===我是貓咪我要吃魚");
    }
    public void work() {
        System.out.println("===我是貓咪我負責抓老鼠");
    }
}

public class Dog extends Animal {

    public void eat() {
        System.out.println("====我是小狗我要吃骨頭");
    }
    public void work() {
        System.out.println("====我是小狗我負責看家");
    }
}
public class Test {
    public static void main(String[] args) {
        show(new Cat());  // 以 Cat 物件呼叫 show 方法
        System.out.println("\n");
        show(new Dog());  // 以 Dog 物件呼叫 show 方法
        System.out.println("\n");


        Animal a = new Cat();  // 向上轉型
        a.eat();               // 呼叫的是 Cat 的 eat
        Cat c = (Cat)a;        // 向下轉型
        c.work();        // 呼叫的是 Cat 的 work
    }

    public static void show(Animal animal)  {
        animal.eat();
        // 型別判斷
        if (animal instanceof Cat)  {  // 貓做的事情
            Cat c = (Cat)animal;
            c.work();
        } else if (animal instanceof Dog) { // 狗做的事情
            Dog c = (Dog)animal;
            c.work();
        }
    }
}

 不如咱直接將動物類改成抽象類吧,

public abstract class Animal {

    abstract void eat() ;

}


public class Cat extends Animal {

    public void eat() {
        System.out.println("===我是貓咪我要吃魚");
    }
    public void work() {
        System.out.println("===我是貓咪我負責抓老鼠");
    }
}

public class Dog extends Animal {

    public void eat() {
        System.out.println("====我是小狗我要吃骨頭");
    }
    public void work() {
        System.out.println("====我是小狗我負責看家");
    }
}

 

歡迎繼續關注下一篇,在介面實現中的實現