多態(tài)就是對(duì)象的多種形態(tài),即相同的消息使得不同的類做出不同的響應(yīng)。
多態(tài)存在的三個(gè)必要條件:繼承、重寫、父類變量引用子類對(duì)象(向上轉(zhuǎn)型)。
實(shí)現(xiàn)多態(tài)的技術(shù)稱為動(dòng)態(tài)綁定,是指在執(zhí)行期間判斷所引用對(duì)象的實(shí)際類型,根據(jù)其實(shí)際的類型調(diào)用其相應(yīng)的方法。
Java中多態(tài)的實(shí)現(xiàn)方式:接口實(shí)現(xiàn),繼承父類進(jìn)行方法重寫,同一個(gè)類中進(jìn)行方法重載
對(duì)象和對(duì)象引用
new關(guān)鍵字
創(chuàng)建一個(gè)Java對(duì)象需要三部:聲明引用變量、實(shí)例化、初始化對(duì)象實(shí)例。
①聲明引用變量
聲明一個(gè)變量來指向一個(gè)對(duì)象,即引用
要聲明一個(gè)變量,需要寫:Animal animal;
這將告訴編譯器你將使用animal引用一個(gè)Animal類型的對(duì)象。用一個(gè)原始變量,這個(gè)聲明也保留了適當(dāng)?shù)膬?nèi)存量的變量。
②實(shí)例化
new運(yùn)算符實(shí)例化一個(gè)類對(duì)象,通過給這個(gè)對(duì)象分配內(nèi)存并返回一個(gè)指向該內(nèi)存的引用。
“實(shí)例化一個(gè)類的對(duì)象”的意思就是“創(chuàng)建對(duì)象”。創(chuàng)建對(duì)象時(shí),你正在創(chuàng)造一個(gè)類的“實(shí)例”,因而“實(shí)例化”一個(gè)類的對(duì)象。
③初始化對(duì)象實(shí)例
就是調(diào)用構(gòu)造方法,對(duì)類的實(shí)例數(shù)據(jù)賦初值。構(gòu)造函數(shù)的名稱提供了需要實(shí)例化類的名稱。
對(duì)象
對(duì)象是類的一個(gè)實(shí)例,創(chuàng)建對(duì)象的過程也叫做類的實(shí)例化。
如人類是一個(gè)類,那么我們每一個(gè)具體的人就是這個(gè)類的對(duì)象。
對(duì)象引用
我們知道new關(guān)鍵字是一個(gè)java運(yùn)算符,它用來創(chuàng)建對(duì)象
Animal animal; ?//聲明一個(gè)變量來指向一個(gè)對(duì)象,即引用
animal = new A animal();
如上代碼,創(chuàng)建對(duì)象的語句是new Animal(),那么animal又是什么呢?animal只是一個(gè)引用,是指向一個(gè)對(duì)象的引用,這個(gè)對(duì)象指向Animal類。也就是說,Animal animal 這句話只是聲明了一個(gè)Animal類的引用,它可以指向任何Animal類的實(shí)例。
一個(gè)引用可以指向多個(gè)對(duì)象,而一個(gè)對(duì)象也可以被多個(gè)引用所指。
引用多態(tài)
父類的引用可以指向本類的對(duì)象 ? Animal animal=new Animal();
父類的引用可以指向子類的對(duì)象 ?Animal animal=new Dog();
注意:父類的引用指向子類的對(duì)象時(shí),子類中特有的方法和屬性就不能使用了。
方法多態(tài)
創(chuàng)建本類對(duì)象時(shí),調(diào)用的方法為本類方法
創(chuàng)建子類對(duì)象時(shí),調(diào)用的方法為子類重寫的方法或者繼承的方法(不能為子類特有的)
類型轉(zhuǎn)換
多態(tài)中的類型轉(zhuǎn)換是基于存在繼承關(guān)系的對(duì)象,有向上類型轉(zhuǎn)換和向下類型轉(zhuǎn)換。
向上轉(zhuǎn)型:父類的引用可以指向子類的對(duì)象
如 Animal animal=new Dog(); ?//Animal類是Dog類的父類
Dog對(duì)象實(shí)例被向上轉(zhuǎn)型成了animal,但Dog對(duì)象實(shí)例本質(zhì)上還是Dog類型,只不過能力被暫時(shí)削弱了
向下轉(zhuǎn)型:向上轉(zhuǎn)型之后子類特有的方法和屬性就不能被調(diào)用了,有時(shí)我們想用這些方法和屬性,就需要將向上轉(zhuǎn)型后的子類對(duì)象再轉(zhuǎn)成子類,調(diào)用子類的方法
如 Dog dog=(Dog)animal;
animal的引用仍然是Animal類型,只不過功能增強(qiáng)了,然后交給dog引用
注:不能直接將父類對(duì)象強(qiáng)制轉(zhuǎn)換為子類類型,只能將向上轉(zhuǎn)型后的子類對(duì)象在此轉(zhuǎn)為子類類型。
向上轉(zhuǎn)型就好比把茶杯里的水往茶壺中倒,是安全的;向下轉(zhuǎn)型就是把茶壺里的水往茶杯里倒,會(huì)存在風(fēng)險(xiǎn),instanceof運(yùn)算符就可以避免類型轉(zhuǎn)換的安全問題了。
instanceof就是去判斷向下轉(zhuǎn)型的對(duì)象將要轉(zhuǎn)換的類型是不是這個(gè)對(duì)象的類型
如:Animal animal = new Dog();//父類引用指向了子類的對(duì)象
?????? Dog dog = (Dog)animal;//將子類對(duì)象的引用類型轉(zhuǎn)成子類,用一個(gè)子類類型的引用來接收
?????? Cat cat = (Cat)animal;//編譯器不報(bào)錯(cuò),但是animal是Dog類的對(duì)象,在運(yùn)行時(shí)會(huì)報(bào)錯(cuò),此時(shí)需要用instanceof去判斷一下
instanceof運(yùn)算符用法:左邊的操作元是一個(gè)對(duì)象,右邊是一個(gè)類。如果左邊的對(duì)象是右邊類的實(shí)例,該運(yùn)算符運(yùn)算的結(jié)果是true,否則是false
注意:一個(gè)類的實(shí)例包括本身的實(shí)例,以及所有直接或間接子類的實(shí)例
? ? ? ? ? instanceof左邊對(duì)象的類型(注意是它引用的對(duì)象的類型,不是變量的類型)與右邊必須是同種類或是繼承關(guān)系
class Animal{ }
class Dog extends Animal{ }
public class Test {
? ?public static void main(String[] args) {
? ? ? ?Animal animal=new Dog(); //向上轉(zhuǎn)型
? ? ? ?if(animal instanceof Dog){
? ? ? ? ? ?Dog dog=(Dog)animal; //向下轉(zhuǎn)型
? ? ? ?}
? ? ? ?else
? ? ? ? ? ?System.out.println("不能轉(zhuǎn)換");
? ?}
}
一個(gè)經(jīng)典的例子
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));
??? }
}
運(yùn)行結(jié)果:
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
抽象類和接口
1.抽象類
當(dāng)父類只知道子類的方法而不知道子類的方法如何實(shí)現(xiàn)時(shí)可以將子類定義為抽象類,若多個(gè)類都有一個(gè)共同的方法,則可以將它抽象成一個(gè)抽象類
抽象方法是一種特殊的方法,它只有聲明,沒有具體的實(shí)現(xiàn)(即方法體),以分號(hào)結(jié)束
如:abstract void fun();
如果一個(gè)類含有抽象方法,那這個(gè)類就是抽象類
抽象類中可以有普通方法和抽象方法
抽象類和抽象方法前面必須都有abstract修飾,因?yàn)槌橄箢愔袥]有具體實(shí)現(xiàn)的方法,所以抽象類不能被實(shí)例化,實(shí)例化的工作交由子類去完成,它只需要有一個(gè)引用即可
抽象類就是為了繼承實(shí)現(xiàn)的,如果一個(gè)類繼承了抽象類,那么子類必須實(shí)現(xiàn)父類的抽象方法,如果子類沒有繼承父類的抽象方法,那么子類就必須也定義為抽象類
抽象方法必須為public或protected,如果沒有訪問控制符的話,默認(rèn)為public
如果子類也是一個(gè)抽象類的話,可以不用實(shí)現(xiàn)父類的抽象方法,如果子類不是一個(gè)抽象類的話,就必須實(shí)現(xiàn)父類的抽象方法
2.接口
一個(gè)類只能extends一個(gè)父類,但可以implements多個(gè)接口,java通過接口的概念來實(shí)現(xiàn)c++中的多繼承。一個(gè)接口可以同時(shí)extends多個(gè)接口,卻不能implements任何接口,因而,Java中的接口是支持多繼承的。
接口是由全局常量和公共的抽象方法所組成。
接口是要被繼承實(shí)現(xiàn)的,所以不能用private和protected修飾,一般用public
接口中可以含有變量和方法,但接口中的變量會(huì)被隱式的指定為public static final(并且只能是public static final),因?yàn)榻涌谥械膶傩允浅A?。方法?huì)被隱式的指定為public abstract(并且只能是public abstract),因?yàn)榻涌谥兴械姆椒ú荒苡芯唧w的實(shí)現(xiàn),即這些方法必須都為抽象方法
允許一個(gè)類遵循多個(gè)接口
如果一個(gè)非抽象類遵循了某個(gè)接口,則這個(gè)類必須實(shí)現(xiàn)接口中的所有方法,如果一個(gè)抽象類遵循了某個(gè)接口。則可以不實(shí)現(xiàn)該接口中的方法
3.抽象類和接口的區(qū)別
抽象類中可以有普通的方法,接口中只能有抽象方法
抽象類中的成員變量可以使各種類型的,而接口中必須是public static final型的
接口中不能含有靜態(tài)代碼塊以及靜態(tài)方法,抽象類中可以有
一個(gè)類只能繼承一個(gè)抽象類,而一個(gè)類可以遵循多個(gè)接口