Java基礎之:OOP——多型
多型(polymorphic)即多種形態,是程式基於封裝和繼承之後的另外一種應用。
首先我們先看一個案例,瞭解為什麼要使用多型。
實現一個應用 : 1.小范既是兒子 也是 父親 (多種形態),2.兒子用錢買糖 , 父親賣報紙給商家賺錢
package polymorphic_ClassTest; public class PolyTest { public static void main(String[] args) { Father father = new Father("父親(小范)",40); Son son = new Son("兒子(小范)",20); Candy candy = new Candy("買糖果"); Newspaper newspaper = new Newspaper("賣報紙"); tarding(son,candy); //兒子買糖果 tarding(father,newspaper); //父親賣報紙 //通過多型引入,我們也可以體現 兒子賣報紙 , 父親買糖果 tarding(father,candy); tarding(son, newspaper); } //試想如果 父親也要買糖果,那麼就又需要重寫一個tarding方法..... //會有很多的組合方式,為了不重寫tarding方法。引入多型的概念 //使用以前的封裝+繼承方法實現: public static void tarding(Son son,Candy candy) { System.out.println(son.getName() + "買" + candy.getName()); } public static void tarding(Father father,Newspaper newspaper) { System.out.println(father.getName() + "賣" + newspaper.getName()); } //使用多型來實現: public static void tarding(Person person,Goods goods) { //實現原理: Son和Father繼承於Person類,Candy和Newspaper繼承於Goods類 System.out.println(person.getName() + " 交易 " + goods.getName()); } } class Person{ private String name; public Person(String name) { super(); this.name = name; } public Person() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Son extends Person{ private int age; public Son(String name, int age) { super(name); this.age = age; } } class Father extends Person{ private int age; public Father(String name, int age) { super(name); this.age = age; } } class Goods{ //商品 private String name; public Goods(String name) { super(); this.name = name; } public Goods() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Candy extends Goods{ public Candy(String name) { super(name); } } class Newspaper extends Goods{ public Newspaper(String name) { super(name); } }
執行結果
多型的具體體現
重寫與過載
public class PolyOverLoad { public static void main(String[] args) { //方法過載體現多型 T t = new T(); t.say(100); t.say("tom"); } } class T { public void say(String name) { System.out.println("hi " + name); } public void say(int num) { System.out.println("hello" + num); } } //======================================================= public class PolyOverride { public static void main(String[] args) { AA a = new AA(); a.hi("jack"); BB b = new BB(); b.hi("tom"); } } class AA { public void hi(String name) { System.out.println("AA " + name); } } class BB extends AA { @Override public void hi(String name) { //子類hi 重寫 父類的 hi System.out.println("BB " + name); } }
物件的多型(編譯型別與執行型別)
package polymorphic; public class PolyTest { public static void main(String[] args) { /* * 語法:父類名 物件 = new 子類構造器;(父類引用指向子類物件) * 此時animal實際存在兩種型別:1.編譯型別 ;2.執行型別 * 編譯型別:編譯器識別時的型別,即等號左邊的型別。這裡animal的編譯型別就是Animal * 在程式設計師編譯時,只能訪問編譯型別有的 方法和屬性 * 對於物件的編譯型別而言,是不變的。 * 執行型別:JVM執行時的型別,即等號右邊的型別。這裡animal的執行型別就是Dog * 對於物件的執行型別而言,是可變的。 */ //向上轉型 語法:父類型別 父類物件 = new 子類型別(); Animal animal = new Dog(); animal.eat(); // animal.run(); //報錯 :The method run() is undefined for the type Animal animal = new Cat();//改變了animal的執行型別,但編譯型別不變 animal.eat(); animal.show(); //1.首先尋找在Cat中的show 2.若沒有則向上尋找父類Animal的show //向下轉型 語法: 子類型別 子類物件 = (子類型別)父類物件 Cat cat = (Cat)animal;//這裡是建立了一個Cat引用,讓cat 指向 animal指向的那個堆地址空間 // Dog dog = (dog)animal;//要想這樣向下強行轉換型別,必須滿足 animal堆空間中的型別就是cat cat.drink(); } } class Animal{ public void eat() { System.out.println("eat......"); } public void show() { System.out.println("show.........."); } } class Dog extends Animal{ @Override public void eat() { System.out.println("Dog eat......"); } public void run() { System.out.println("run........"); } } class Cat extends Animal{ @Override public void eat() { System.out.println("Cat eat......"); } public void drink() { System.out.println("drink........"); } // public void show() { // System.out.println("Cat show.........."); // } }
結合實際案例理解編譯型別與執行型別
對於編譯型別和執行型別,通過這樣一個現實的例子來理解。
大家都知道披著羊皮的狼,那可能也會有披著羊皮的人。
那麼這裡的羊皮就是編譯型別,羊皮是始終不變的,不管是誰披上它,它都是羊皮。
而我們可以把編譯器看成一個很"膚淺"的傢伙,它只會看到表面的東西,所以如果在編譯型別中沒有的方法,不可以通過物件進行呼叫(即使在執行型別中確實存在此方法)。
這裡的狼和人就是執行型別,執行型別是可變的。(羊皮可能被任何東西給披上)
相對於編譯器而言JVM就顯得有"內涵"一些了,執行型別就是在JVM執行程式時,物件實際的型別,所以執行型別可以呼叫在執行型別中有的方法。
就好比,披著羊皮的狼,你以為它是吃草的,編譯器也認為它是吃草的。但它實際上是吃肉的,JVM在執行時也認為它是吃肉的。
案例說明(向上轉型與向下轉型)
向上轉型 語法:父類型別 父類物件 = new 子類型別();
對於向上轉型而言,就是將父類引用指向子類物件。
向上轉型可以通過改變執行型別的方式,通過一個父類引用訪問多個子類物件。
例如:
Animal animal = new Dog();
animal = new Cat();
向下轉型 語法: 子類型別 子類物件 = (子類型別)父類物件;
對於向下轉型而言,就是將父類物件強制轉換為子類物件。
所以要做到向下轉型,前提條件就是父類物件原本的執行型別就是子類型別。
例如:
Animal animal = new Dog();
Dog dog = (dog)animal;
但要注意的是,向下轉型是將一個子類引用Dog指向了原來在堆空間建立的那個Dog物件。
而animal同樣指向堆空間中的Dog物件,所以向下轉型之後 animal (父類引用)本身不受影響。
屬性多型
對於型別的屬性而言,沒有編譯型別與執行型別的說法。
即屬性只認編譯型別,通過多型聲明後,訪問屬性時,也只會返回編譯型別中的屬性對應的值。
public class PolyProperties { public static void main(String[] args) { Base base = new Base(); System.out.println(base.n); // 200 Base base2 = new Sub(); System.out.println(base2.n); // 屬性沒有重寫之說!屬性的值看編譯型別 } } class Base { public int n = 200; } class Sub extends Base { public int n = 300; }
instanceOf關鍵字
instanceOf關鍵字用於比較 物件的型別 是否是指定型別或其子類
public class InstanceOfTest { public static void main(String[] args) { AA bb = new BB(); //instanceOf 比較操作符,用於判斷某個物件的執行型別是否為XX型別或XX型別的子型別 System.out.println(bb instanceof BB); // T System.out.println(bb instanceof AA); // T System.out.println(bb instanceof Object); // T Object obj = new Object(); System.out.println(obj instanceof AA);// F } } class AA{ } class BB extends AA{ }
Java的動態繫結機制
-
當呼叫物件方法的時候,該方法會和該物件的記憶體地址/執行型別繫結。
-
當呼叫物件屬性時,沒有動態繫結機制,哪裡宣告,哪裡使用。
class A { public int i = 10; public int sum() { return getI() + 10; } public int sum1() { return i + 10; } public int getI() { return i; } } class B extends A { public int i = 20; // public int sum() {//登出? // return i + 20; //} public int getI() { return i; } // public int sum1() {//登出? // return i + 10; // } } public class Test{ public static void main(String args[]){ A a = new B(); //不登出 登出 System.out.println(a.sum()); //40 =》 30 System.out.println(a.sum1()); //30 =》 20 //這裡要注意,getI()方法是動態繫結在B物件上的,所以在呼叫A類的sum()方法時,getI()仍然會返回B類中的I的值。 } }
多型應用案例
應用例項:現有一個繼承結構如下:要求建立五個年齡不等的Person1、Student [2]和Teacher[2]物件。
呼叫子類特有的方法,比如Teacher 有一個 teach , Student 有一個 study怎麼呼叫
提示 : [實現在多型陣列呼叫各個物件的方法]遍歷+instanceof + 向下轉型
package polymorphic_PolyArrays; public class PolyArrays { public static void main(String[] args) { Person[] persons = {new Person("jack", 10), new Student("tom",20, 78), new Student("king",21, 68) , new Teacher("老王", 50, 10000), new Teacher("老李", 45, 20000)}; Traverse(persons); } public static void Traverse(Person[] person) { for (int i = 0; i < person.length; i++) { if(person[i] instanceof Student) { // ((Student)person[i]).study(); //這種方式更好 Student stu = (Student)person[i]; stu.study(); }else if(person[i] instanceof Teacher) { // ((Teacher)person[i]).teach(); //這種方式更好 Teacher tea = (Teacher)person[i]; tea.teach(); }else { System.out.println(person[i].say()); } } } } class Person { private String name; private int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String say() { return "資訊 name= " + name + " age= " + age; } } class Student extends Person { private double score; public double getScore() { return score; } public void setScore(double score) { this.score = score; } public Student(String name, int age, double score) { super(name, age); this.score = score; } public void study() { System.out.println("學生 " + getName() + " is studying java..."); } } class Teacher extends Person { private double salary; public Teacher(String name, int age, double salary) { super(name, age); this.salary = salary; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public void teach() { System.out.println("老師 " + getName() + " is teaching java "); } }