面向對象主要有三大特性:繼承和多態、封裝。java
在瞭解抽象類以前,先來了解一下抽象方法。抽象方法是一種特殊的方法:它只有聲明,而沒有具體的實現。抽象方法的聲明格式爲:編程
abstract void fun();
抽象方法必須用abstract關鍵字進行修飾。若是一個類含有抽象方法,則稱這個類爲抽象類,抽象類必須在類前用abstract關鍵字修飾。由於抽象類中含有無具體實現的方法,因此不能用抽象類建立對象。抽象類的聲明格式以下:安全
public abstract class ClassName { abstract void fun(); }
下面要注意一個問題:在《JAVA編程思想》一書中,將抽象類定義爲「包含抽象方法的類」,可是後面發現若是一個類不包含抽象方法,只是用abstract修飾的話也是抽象類。也就是說抽象類不必定必須含有抽象方法。我的以爲這個屬於鑽牛角尖的問題吧,由於若是一個抽象類不包含任何抽象方法,爲什麼還要設計爲抽象類?因此暫且記住這個概念吧,沒必要去深究爲何。函數
在面向對象領域因爲抽象的概念在問題領域沒有對應的具體概念,因此用以表徵抽象概念的抽象類是不能實例化的。同時,抽象類體現了數據抽象的思想,是實現多態的一種機制。它定義了一組抽象的方法,至於這組抽象方法的具體表現形式由派生類來實現。同時抽象類提供了繼承的概念,它的出發點就是爲了繼承,不然它沒有存在的任何意義。對於一個父類,若是它的某個方法在父類中實現出來沒有任何意義,必須根據子類的實際需求來進行不一樣的實現,那麼就能夠將這個方法聲明爲abstract方法,此時這個類也就成爲abstract類了。this
使用抽象類時應注意一下幾點:spa
一、包含抽象方法的類稱爲抽象類,但並不意味着抽象類中只能有抽象方法,它和普通類同樣,一樣能夠擁有成員變量和普通的成員方法.net
二、若是一個非抽象類繼承了抽象類,則非抽象類必須實現抽象父類的全部抽象方法設計
三、子類中的抽象方法不能與父類的抽象方法同名3d
四、抽象類不能建立實體,由於抽象類存在抽象方法,而抽象方法沒有實體,建立對象後,抽象對象調用抽象方法是沒有意義的code
五、抽象類中必定有構造函數。主要爲了初始化抽象類中的屬性。一般由子類實現
六、final和abstract是否能夠同時修飾一個方法,由於用final修飾後,修飾類表明不能夠繼承,修飾方法不可重寫,abstract修飾類就是用來被繼承的,修飾方法就是用來被重寫的
abstract不能與private修飾同一個方法,由於privte成員對外是不可見的,只能在本類中使用,這樣子類就沒法重寫抽象方法
abstract不能與static修飾同一個方法,static修飾的方法能夠用類名調用,而對於abstract修飾的方法沒有具體的方法實現,全部不能直接調用
接口,英文稱做interface,在軟件工程中,接口泛指供別人調用的方法或者函數。從這裏,咱們能夠體會到Java語言設計者的初衷,它是對行爲的抽象,而沒有具體的實現,接口自己不是類。同時實現該接口的實現類必需要實現該接口的全部方法,經過使用implements關鍵字,他表示該類在遵循某個或某組特定的接口,同時也表示着「interface只是它的外貌,可是如今須要聲明它是如何工做的」。
接口是抽象類的延伸,java爲了了保證數據安全是不能多重繼承的,也就是說繼承只能存在一個父類,可是接口不一樣,一個類能夠同時實現多個接口,無論這些接口之間有沒有關係,因此接口彌補了抽象類不能多重繼承的缺陷,可是推薦繼承和接口共同使用,由於這樣既能夠保證數據安全性又能夠實現多重繼承。接口聲明形式以下:
public interface InterfaceName { }
在使用接口過程當中須要注意以下幾個問題:
一、一個Interface的方全部法訪問權限自動被聲明爲public。確切的說只能爲public,固然你能夠顯示的聲明爲protected、private,可是編譯會出錯!
二、接口中定義的全部變量默認是public static final的,即靜態常量既然是常量,那麼定義的時候必須賦值,能夠經過接口名直接訪問:ImplementClass.name。
三、接口中定義的方法不能有方法體。接口中定義的方法默認添加public abstract
四、有抽象函數的不必定是抽象類,也能夠是接口類。
五、因爲接口中的方法默認都是抽象的,因此接口不能被實例化。
六、類實現接口經過implements實現,實現接口的非抽象類必需要實現該接口的全部方法,抽象類能夠不用實現。
七、若是實現類要訪問接口中的成員,不能使用super關鍵字。由於二者之間沒有顯示的繼承關係,何況接口中的成員成員屬性是靜態的
八、接口沒有構造方法。
九、不能使用new操做符實例化一個接口,但能夠聲明一個接口變量,該變量必須引用(refer to)一個實現該接口的類的對象。可使用 instanceof 檢查一個對象是否實現了某個特定的接口。
例如:if(anObject instanceof Comparable){}。
十、在實現多接口的時候必定要避免方法名的重複。
一、語法層面上的區別
1)抽象類能夠提供成員方法的實現細節(即普通方法),而接口中只能存在public abstract 方法;
2)抽象類中的成員變量能夠是各類類型的,而接口中的成員變量只能是public static final類型的;
3)接口中不能含有靜態代碼塊以及靜態方法,而抽象類能夠有靜態代碼塊和靜態方法;
4)一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口,Java是單繼承,多實現。
二、設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。
2)抽象類所體現的是一種繼承關係,而繼承是一個 "is-a"的關係,而 接口 實現則是 "has-a"的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現則是有沒有、具有不具有的關係。好比:將鳥設計爲一個類Bird,可是不能將 飛行 這個特性也設計爲類,所以它只是一個行爲特性,並非對一類事物的抽象描述。此時能夠將 飛行 設計爲一個接口Fly,包含方法fly( ),對於不一樣種類的鳥直接繼承Bird類便可,而鳥是否能飛(或者是否具有飛行這個特色),能飛行則能夠實現這個接口,不能飛行就不實現這個接口。
3)設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。對於抽象類,若是須要添加新的方法,能夠直接在抽象類中添加具體的實現,子類能夠不進行變動;而對於接口則不行,若是接口進行了變動,則全部實現這個接口的類都必須進行相應的改動。
繼承是使用已存在的類的定義做爲基礎創建新類的技術,新類的定義能夠增長新的數據或新的功能,也能夠用父類的功能,但不能選擇性地繼承父類。經過使用繼承咱們可以很是方便地複用之前的代碼,可以大大的提升開發的效率。
繼承的特色:
一、子類擁有父類非private的屬性和方法
二、子類能夠擁有本身屬性和方法,即子類能夠對父類進行擴展
三、子類能夠用本身的方式實現父類的方法(方法重寫)
四、構造函數不能被繼承
五、繼承使用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」。
封裝是指利用抽象數據類型將數據和基於數據的操做封裝在一塊兒,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,儘量地隱藏內部的細節,只保留一些對外接口使之與外部發生聯繫。系統的其餘對象只能經過包裹在數據外面的已經受權的操做來與這個封裝的對象進行交流和交互。也就是說用戶是無需知道對象內部的細節(固然也無從知道),但能夠經過該對象對外的提供的接口來訪問該對象。
使用封裝有四大好處:
一、良好的封裝可以減小耦合。
二、類內部的結構能夠自由修改。
三、能夠對成員進行更精確的控制。
四、隱藏信息,實現細節。