計算機是頭腦延伸的工具,是一種不一樣類型的表達媒體。本文以背景性的和補充性的材料,介紹包括開發方法概述在內的面向對象程序設計(Object-oriented Programming,OOP)的基本概念。
本文經過概念+代碼的方式,來幫助讀者瞭解面向對象程序設計的全貌。java
機器模型:位於解空間
內,是對問題建模的地方;能夠這樣理解,彙編語言和命令式語言,在解決問題時要基於計算機的架構;所以架構限定了解決方案,因此說機器模型是解空間。程序員
實際待解決問題:問題空間
,是問題存在的地方編程
抽象的類型和質量,決定了人們所可以解決的問題的複雜性。抽象的類型指的是「所抽象的是什麼」。一種是在機器模型和實際待解決問題的模型之間創建聯繫的抽象;另外一種是隻針對待解決問題建模。而面向對象則是向程序員提供表示問題空間中元素的工具,咱們將問題空間中的元素及其在解空間中的表示稱爲「對象」。微信
什麼是對象?對象具備狀態、行爲和標識。每個對象均可以擁有內部數據和方法,而且能夠惟一的與其餘對象區分開來
對象:具備狀態、行爲和標識的實體。如銀行存款帳戶是一個類,那麼具體的每一個人的銀行存款帳戶就是這個類目下的對象。多線程
類:能夠看做類型來考慮。好比說鳥類,是動物中的其中一種類型。架構
全部的對象都是惟一的,但同時具備相同的特性和行爲的對象也都歸屬於某個特定的類。
類在Java中用關鍵詞class表示。每一個類的對象都具備某種共性和個性,如銀行存款帳戶,每一個帳戶中都有餘額的屬性,但每一個帳戶中的餘額又不一樣。在實際中,面向對象程序設計語言都用class關鍵字來表示數據類型,換而言之,每個類都是一個數據類型。程序員能夠自由地添加新的類(數據類型)來擴展編程語言,對實際問題進行處理。併發
面向對象的挑戰之一,就是在問題空間的元素和解空間的對象之間建立一對一的映射。編程語言
獲取有用對象,必須以某種方式對對象進行請求,使對象完成各類任務。類型決定接口,而接口決定對象能知足的請求。就好比鳥類型,其提供的接口有飛翔,所以其能知足飛翔的請求。在接口肯定了某一特定對象可以發出的請求後,接口的實現掌控着請求的具體行爲的展示方式。在類型中,每個可能的請求都有一個方法與之關聯,當向對象發送請求時,與之關聯的方法就會被調用。函數
如下代碼是獲取一個對象並調用其中的方法實例(你能夠暫時不用理解,只須要知道形式便可,後面再反過來看就好)工具
public class Light { //開燈 public void on() { System.out.println("Light is on!"); } //關燈 public void off() { System.out.println("Light is off!"); } //這裏是獲取一個對象並調用其中方法的實例 public static void main(String [] args) { //這裏是核心代碼,開燈操做 Light lt = new Light(); lt.on(); } }
### 對象是服務提供者
程序經過調用其餘對象提供的服務來向用戶提供服務。程序員的目標就是去建立(或者最好是從現有的代碼庫中尋找)可以提供解決問題所需服務的一系列對象。
爲何要把對象看做是服務提供者呢?
將程序開發人員按照角色劃分爲類建立者
和客戶端程序員
。類建立者建立新的數據類型,而客戶端程序員在其應用中使用類建立者建立的新數據類型。如此一來,客戶端程序員的主要目的就是收集各類用來實現快速應用開發的類;而類建立者的目的則是構建類,並向客戶端程序員暴露必須的部分。
爲何類建立者須要對類的某些部分進行隱藏呢?或者說,爲何須要進行訪問權限控制呢?
修飾符 | 類內部 | 同包 | 子類 | 任何地方 |
---|---|---|---|---|
private | √ | × | × | × |
無 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
代碼複用是面向對象程序設計語言所提供的最了不得的優勢之一。
代碼複用的基本方式:
關係是指事物之間存在單向或者相互的做用力或者影響力的狀態。
在兩個類之間存在有關係和不要緊兩種狀況,在有關係的狀況下,其關係包括如下六種類型
類關係 | 英文名 | 描述 | 強權方 | UML圖表示 | 示例說明 |
---|---|---|---|---|---|
繼承 | extends | 父類與子類之間的關係:is-a | 父類 | 空心三角+實線,空心三角指向父類 | 鳥是動物 |
實現 | implements | 接口與實現類之間的關係:can-do | 接口 | 空心三角+虛線,空心三角指向接口 | 鳥實現了飛翔的接口 |
組合 | composition | 比聚合更強的關係:contains-a | 總體 | 實心菱形+實線,實心菱形指向總體 | 人類的頭和身體是強組合關係 |
聚合 | aggregation | 暫時組裝的關係:has-a | 組裝方 | 空心菱形+實線,空心菱形指向組裝方 | 狗和牽狗的繩子是聚合關係 |
依賴 | dependency | 一個類依賴於另外一個類:depends-a | 被依賴方 | 箭頭+虛線,箭頭指向被依賴方 | 人喂小狗,小狗是喂這個動做的被依賴方 |
關聯 | association | 類與類之間存在互相平等的使用關係:links-a | 平等 | 實線 | 人與信用卡的關係,人用信用卡,信用卡能夠讀取我的信息 |
以現有類爲基礎,複製它,而後經過添加和修改這個副原本建立新類。當源類發生變化時,被修改的副本也會反應出這種變更。生物學中對科目的定義,用於解釋繼承關係再恰當不過。
父類:又稱源類、基類、超類;
子類:又稱導出類、繼承類;
父類和子類之間的類型層次結構同時體現了他們之間的類似性和差別性。當繼承現有類型時,也就創造了新的類型,同時子類又歸屬於父類的類型。這個新的類型不只包括現有類型的全部成員,並且更重要的是它複製了父類的接口,這意味着全部對父類對象的調用同時可能夠對子類對象發起,這遵循了編程原則之一的里氏替換原則。
若是隻是簡單地繼承一個類而不作其餘任何事情,那麼在父類接口中的方法將會直接繼承到子類中。當須要使父類和子類產生差別時,有如下兩種方式:
那麼繼承是否應該只覆寫父類的方法呢?
若是繼承只覆寫了父類的方法,那麼子類對象能夠徹底替代父類對象,這一般稱之爲替代原則
,在這種狀況下的類關係稱爲is-a
;但有時又的確須要在子類中添加新的接口,這種狀況下父類沒法訪問新添加的接口,這種狀況下類關係爲is-like-a
,這時這種父類與子類之間的關係,被視爲非存粹替代
在處理類層次關係的時候,若是把任意一個特定類型的對象能夠看成其基類對象來對待,就使得人們能夠編寫出不依賴於特定類型的代碼。
前期綁定:編譯器將產生對一個具體函數名字的調用,而在運行時須要將這個調用解析到將要被執行的代碼的絕對地址(意味着運行前就須要知道具體代碼的位置)。
後期綁定:編譯器只確保調用的方法存在,並且調用參數和返回值類型正確;在運行時,經過特殊代碼,解析具體將要執行的代碼的具體位置。
經過導出新的子類而輕鬆擴展設計的能力,是對改動進行封裝的基本方式之一。
在試圖將子類對象看成其基類對象來看待時,須要解決的一個問題是:編譯器沒法精確地瞭解哪一段代碼將會被執行。在OOP程序設計中,程序直到運行時纔可以肯定代碼的位置。
OOP程序設計語言使用了後期綁定的概念:編譯器確保調用方法的存在,並對調用參數和返回值執行類型檢查,但並不知道將被執行的確切代碼。Java使用一小段特殊的代碼來替代絕對地址調用,這段特殊代碼
用來計算方法體的具體位置。Java默認是動態綁定的。
把子類對象看做父類對象的過程,稱做向上轉型
。緣由是在類圖中,父類老是位於類圖的頂部,把子類對象視爲父類對象,即將子類類型向上推導。
Java中全部的類最終都繼承自單一的基類:Object
單根繼承結構保證全部對象都具有某些功能。Object是任何類的默認父類,是在哲學方向上繼續寧的延伸思考。
一般來講,若是不知道在解決某個特定問題時須要多少對象,或者他們將存活多久,那麼就不可能知道如何存儲這些對象。
在Java標準類庫中提供了大量容器。不一樣的容器提供了不一樣類型的接口和外部行爲,同時對某些操做具備不一樣的效率。如List中的ArrayList和LinkedList因爲底層實現的不一樣,具有不一樣的應用場景。
因爲容器只存儲Object,因此將對象引入置入容器時,被向上轉型爲Object,在取出類型時會丟失其類型。在必定程度上可使用向下轉型的方式來獲取其實際類型,可是這樣作存在風險。
package a; import java.util.*; public class Container { public static void main(String [] args) { List list = new ArrayList(); list.add("Hello World!"); list.add(1); //程序運行到這裏是不會報錯的,可是執行下面這一步的時候,就會出現異常了 for(Object o : list) { //這一步會出現異常,由於List中存放的不只僅是String類型,還有Integer類型,向下轉型出現異常 String a = (String) o; System.out.println(a); } } }
那麼用什麼方式使容器記住這些對象究竟使什麼類型呢?解決方案稱爲參數化類型,在Java中也稱爲泛型。表示方法爲一對尖括號,中間包含類型信息。
List<String> list = new ArrayList<String>();
這樣一來,就限定了List中只能存放String類型的對象啦!固然,咱們仍是可以經過反射繞過這層驗證,畢竟在編譯後運行時,是去泛型的。
在使用對象時,最關鍵的問題之一即是他們的生成和銷燬方式。
new Constructor();:經過new關鍵詞向堆中申請內存,經過Constructor來講明類的建立方式。
Java採用動態內存分配
的方式。動態方式有個通常性假設:對象趨於變得複雜,因此查找和釋放內存空間的開銷不會對對象的建立形成重大沖擊。動態方式所帶來的更大的靈活性是解決通常化編程問題的要點。
Java提供了被稱爲垃圾收集器
的機制,用來處理內存釋放問題。垃圾收集器的運行基礎是單根繼承結構
和只能在堆上建立對象
的特性。
錯誤處理始終是編程的難題之一。
異常是一種對象,其從出錯點被拋出
,並被特定類型的異常處理器所捕獲
。異常處理就像與程序正常執行路徑並行的、在錯誤發生時執行的另外一條路徑。
異常不能被忽略,它保證必定會在某處獲得處理。異常提供了一種從錯誤狀態進行可靠恢復的途徑。Java一開始就內置了異常處理,並強制你必須使用它。它是惟一可接受的錯誤報告方式。
在計算機編程中,存在着在同一時刻處理多個任務的思想。這些彼此獨立運行的部分稱爲線程,同一時刻處理多個任務稱爲併發。
在單一處理器中,線程只是一種爲單一處理器分配執行時間的手段,換而言之,若是隻有一個處理器,那麼多線程程序的運行不過是多個任務競爭使用處理器的性能。在多處理器的狀況下,實現的纔是真正意義上的併發,多處理器並行計算。
多線程同時存在一個隱患,在存在共享資源的時候,可能會形成資源之間的競爭,進而形成死鎖。因此在多線程修改共享資源時,必然在共享資源使用期進行鎖定。
在Java中,JDK1.5後提供了concurrent包支持更好的併發特性。
以上是對象導論的一些基本概念,是繼續閱讀後面章節的非必要補充性材料。對於文中的一些代碼段或概念,暫時不理解的,能夠先放一放,等後面看完了,再回過來看就恍然大悟了。
下一節將講解對象並寫第一個Java程序。若是你對這些內容不感興趣,想看點更高難度的,歡迎關注個人博客:https://www.cnblogs.com/lurke...;歡迎關注個人微信公衆號JavaCorner