面向對象主要有三大特性:繼承和多態、封裝。
在了解抽象類之前,先來了解一下抽象方法。抽象方法是一種特殊的方法:它只有聲明,而沒有具體的實現。抽象方法的聲明格式為:
abstract void fun();
抽象方法必須用abstract關鍵字進行修飾。如果一個類含有抽象方法,則稱這個類為抽象類,抽象類必須在類前用abstract關鍵字修飾。因為抽象類中含有無具體實現的方法,所以不能用抽象類創建對象。抽象類的聲明格式如下:
public abstract class ClassName { abstract void fun(); }
下面要注意一個問題:在《JAVA編程思想》一書中,將抽象類定義為“包含抽象方法的類”,但是後面發現如果一個類不包含抽象方法,只是用abstract修飾的話也是抽象類。也就是說抽象類不一定必須含有抽象方法。個人覺得這個屬於鑽牛角尖的問題吧,因為如果一個抽象類不包含任何抽象方法,為何還要設計為抽象類?所以暫且記住這個概念吧,不必去深究為什麼。
在面向對象領域由於抽象的概念在問題領域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能實例化的。同時,抽象類體現了數據抽象的思想,是實現多態的一種機制。它定義了一組抽象的方法,至於這組抽象方法的具體表現形式由派生類來實現。同時抽象類提供了繼承的概念,它的出發點就是為了繼承,否則它沒有存在的任何意義。對於一個父類,如果它的某個方法在父類中實現出來沒有任何意義,必須根據子類的實際需求來進行不同的實現,那麼就可以將這個方法聲明為abstract方法,此時這個類也就成為abstract類了。
使用抽象類時應注意一下幾點:
1、包含抽象方法的類稱為抽象類,但並不意味著抽象類中只能有抽象方法,它和普通類一樣,同樣可以擁有成員變量和普通的成員方法
2、如果一個非抽象類繼承了抽象類,則非抽象類必須實現抽象父類的所有抽象方法
3、子類中的抽象方法不能與父類的抽象方法同名
4、抽象類不能創建實體,因為抽象類存在抽象方法,而抽象方法沒有實體,創建對象後,抽象對象調用抽象方法是沒有意義的
5、抽象類中一定有構造函數。主要為了初始化抽象類中的屬性。通常由子類實現
6、final和abstract是否可以同時修飾一個方法,因為用final修飾後,修飾類代表不可以繼承,修飾方法不可重寫,abstract修飾類就是用來被繼承的,修飾方法就是用來被重寫的
abstract不能與private修飾同一個方法,因為privte成員對外是不可見的,只能在本類中使用,這樣子類就無法重寫抽象方法
abstract不能與static修飾同一個方法,static修飾的方法可以用類名調用,而對於abstract修飾的方法沒有具體的方法實現,所有不能直接調用
接口,英文稱作interface,在軟件工程中,接口泛指供別人調用的方法或者函數。從這裡,我們可以體會到Java語言設計者的初衷,它是對行為的抽象,而沒有具體的實現,接口本身不是類。同時實現該接口的實現類必須要實現該接口的所有方法,通過使用implements關鍵字,他表示該類在遵循某個或某組特定的接口,同時也表示著“interface只是它的外貌,但是現在需要聲明它是如何工作的”。
接口是抽象類的延伸,java為了了保證數據安全是不能多重繼承的,也就是說繼承只能存在一個父類,但是接口不同,一個類可以同時實現多個接口,不管這些接口之間有沒有關系,所以接口彌補了抽象類不能多重繼承的缺陷,但是推薦繼承和接口共同使用,因為這樣既可以保證數據安全性又可以實現多重繼承。接口聲明形式如下:
public interface InterfaceName { }
在使用接口過程中需要注意如下幾個問題:
1、一個Interface的方所有法訪問權限自動被聲明為public。確切的說只能為public,當然你可以顯示的聲明為protected、private,但是編譯會出錯!
2、接口中定義的所有變量默認是public static final的,即靜態常量既然是常量,那麼定義的時候必須賦值,可以通過接口名直接訪問:ImplementClass.name。
3、接口中定義的方法不能有方法體。接口中定義的方法默認添加public abstract
4、有抽象函數的不一定是抽象類,也可以是接口類。
5、由於接口中的方法默認都是抽象的,所以接口不能被實例化。
6、類實現接口通過implements實現,實現接口的非抽象類必須要實現該接口的所有方法,抽象類可以不用實現。
7、如果實現類要訪問接口中的成員,不能使用super關鍵字。因為兩者之間沒有顯示的繼承關系,況且接口中的成員成員屬性是靜態的
8、接口沒有構造方法。
9、不能使用new操作符實例化一個接口,但可以聲明一個接口變量,該變量必須引用(refer to)一個實現該接口的類的對象。可以使用 instanceof 檢查一個對象是否實現了某個特定的接口。
例如:if(anObject instanceof Comparable){}。
10、在實現多接口的時候一定要避免方法名的重復。
1、語法層面上的區別
1)抽象類可以提供成員方法的實現細節(即普通方法),而接口中只能存在public abstract 方法;
2)抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的;
3)接口中不能含有靜態代碼塊以及靜態方法,而抽象類可以有靜態代碼塊和靜態方法;
4)一個類只能繼承一個抽象類,而一個類卻可以實現多個接口,Java是單繼承,多實現。
2、設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是接口卻是對類局部(行為)進行抽象。
2)抽象類所體現的是一種繼承關系,而繼承是一個 "is-a"的關系,而 接口 實現則是 "has-a"的關系。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而接口實現則是有沒有、具備不具備的關系。比如:將鳥設計為一個類Bird,但是不能將 飛行 這個特性也設計為類,因此它只是一個行為特性,並不是對一類事物的抽象描述。此時可以將 飛行 設計為一個接口Fly,包含方法fly( ),對於不同種類的鳥直接繼承Bird類即可,而鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實現這個接口,不能飛行就不實現這個接口。
3)設計層面不同,抽象類作為很多子類的父類,它是一種模板式設計。而接口是一種行為規范,它是一種輻射式設計。對於抽象類,如果需要添加新的方法,可以直接在抽象類中添加具體的實現,子類可以不進行變更;而對於接口則不行,如果接口進行了變更,則所有實現這個接口的類都必須進行相應的改動。
繼承是使用已存在的類的定義作為基礎建立新類的技術,新類的定義可以增加新的數據或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。通過使用繼承我們能夠非常方便地復用以前的代碼,能夠大大的提高開發的效率。
繼承的特點:
1、子類擁有父類非private的屬性和方法
2、子類可以擁有自己屬性和方法,即子類可以對父類進行擴展
3、子類可以用自己的方式實現父類的方法(方法重寫)
4、構造函數不能被繼承
5、繼承使用extends關鍵字實現
子類重寫父類的函數的時候,返回值類型必須是父類函數的返回值類型或該返回值類型的子類,不能返回比父類更大的數據類型;
如果子類的對象調用方法,默認先使用this進行查找,如果當前對象沒有找到屬性或方法,找當前對象中維護的super關鍵字指向的對象,如果還沒有找到編譯報錯,找到直接調用。
所有的重載函數必須在同一個類中
多態存在的三個必要條件
多態弊端: 提高擴展性,但是只能使用父類引用指向父類成員。
注意:
在多態的情況下,字符類存在同名的成員(成員變量和成員函數)時,訪問的是父類的成員,只有是同名的非靜態成員函數時,才訪問子類的成員函數;
多態用於形參類型時,可以接受多個類型的數據;
多態用於返回類型時,可以返回多個類型的數據,使用了多態的方法,定義的變量類型要與返回的類型一致。
以下面例子來分析多態
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
首先我們先看一句話:當超類對象引用變量引用子類對象時,被引用對象的類型而不是引用變量的類型決定了調用誰的成員方法,但是這個被調用的方法必須是在超類中定義過的,也就是說被調用的方法必須是被子類重寫的方法。這句話對多態進行了一個概括。其實在繼承鏈中對象方法的調用存在一個優先級:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
對於前半句的意思就是:當父類變量引用子類對象時,在調用成員函數時,應該調用向子類的成員函數,但前提是此函數時被子類重寫的函數。
A B C D的繼承關系如下:
分析:
對於1和2,B和C屬於A的子類,調用a1.show(b),a1.show(b),可以找到A.show(A boj),因為多態情況下,父類做形參時,可以接受其子類的實參。
對於3,直接就可以找到A.show(D odj)。
對於4,本來由於a2引用的是其子類B的一個對象,因此調用的成員函數應為B.show(B obj),但是由於B.show(B obj)不是重寫的函數,因此不會調用B.show(B obj)。故將按照優先級,先看this.show(O),而類A裡面沒有找到show(B obj)方法,於是到A的super(超類)找,而A沒有超類,因此轉到第三優先級this.show((super)O),this仍然是a2,這裡O為B,(super)O即(super)B即A,因此它到類A裡面找show(A obj)的方法,類A有這個方法,但是由於a2引用的是類B的一個對象,且B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(A obj),輸出為"B and A”。
對於5,同樣將按照優先級,先看this.show(O),而類A裡面沒有找到show(C obj)方法,於是到A的super(超類)找,而A沒有超類,因此轉到第三優先級this.show((super)O),this仍然是a2,這裡O為C,由於A是C的超類,因此它到類A裡面找show(A obj)的方法,類A有這個方法,但是由於a2引用的是類B的一個對象,且B覆蓋了A的show(A obj)方法,因此最終鎖定到類B的show(A obj),輸出為"B and A”。
對於6,同樣將按照優先級,先看this.show(O),而類A裡面剛好找到了show(D obj)方法,輸出為"D and A”.
對於7,可以直接調用this.show(O)。
對於8,同樣將按照優先級,先看this.show(O),而類B裡面沒有找到show(C obj)方法,於是到B的super(超類)找,而類A裡面沒有找到show(C obj)方法,因此轉到第三優先級this.show((super)O),this仍然是b,這裡O為C,由於B是C的超類,因此它到類B裡面找show(B obj)的方法,因此輸出為"B and B”。
對於9,同樣將按照優先級,先看this.show(O),而類B裡面沒有找到show(D obj)方法,於是到B的super(超類)找,而類A裡面找到了show(D obj)方法,因此輸出為"A and D”。
封裝是指利用抽象數據類型將數據和基於數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。系統的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互。也就是說用戶是無需知道對象內部的細節(當然也無從知道),但可以通過該對象對外的提供的接口來訪問該對象。
使用封裝有四大好處:
1、良好的封裝能夠減少耦合。
2、類內部的結構可以自由修改。
3、可以對成員進行更精確的控制。
4、隱藏信息,實現細節。