Java 面向對象概述


本文部分摘自 On Java 8程序員


面向對象編程

在說起面向對象時,不得不提到另外一個概念:抽象。編程的最終目的是爲了解決某個問題,問題的複雜度直接取決於抽象的類型和質量。早期的彙編語言經過對底層機器做輕微抽象,到後來的 C 語言又是對彙編語言的抽象。儘管如此,它們的抽象原理依然要求咱們着重考慮計算機的底層結構,而非問題自己編程

面向對象編程(Object-Oriented Programming OOP)是一種編程思惟方式和編碼架構。不一樣於傳統的面向過程編程,面向對象編程把問題空間(實際要解決的問題)中的元素以及它們在解決方案空間中的表示以一種具備廣泛性的方式表達出來,這種表示被稱做對象(Object)。對於一些在問題空間沒法對應的對象,咱們還能夠添加新的對象類型,以配合解決特定的問題。總而言之,OOP 容許咱們根據問題來描述問題,而不是根據解決問題的方案。每一個對象都有本身的狀態,而且能夠進行特定的操做,換言之,它們都有本身的特徵和行爲架構

根據面向對象編程的概念,能夠總結出五大基本特徵:編程語言

  • 萬物皆對象
  • 程序是一組對象,能夠互相傳遞消息以告知彼此應該作什麼
  • 每一個對象都有本身的存儲空間,以容納其餘對象
  • 每一個對象都有一種類型
  • 同一個類的全部對象能接收相同的消息

對對象更簡潔的描述是:一個對象具備本身的狀態、行爲和標識,這意味着對象有本身的內部數據(狀態)、方法(行爲),並彼此區分(每一個對象在內存中都有惟一的地址)函數


接口

全部對象都是惟一的,但同時也是具備相同的特徵和行爲的對象所歸屬的類的一部分。這種思想被應用於面向對象編程,並在程序中使用基本關鍵字 class 來表示類型,每一個類都有本身的通用特徵和行爲。建立好一個類後,可生成許多對象,這些對象做爲要解決問題中存在的元素進行處理。事實上,當咱們在進行面向對象程序設計時,面臨的最大的一項挑戰就是:如何在問題空間與方案空間的元素之間創建理想的一對一映射關係?工具

若是沒法創建有效的映射,對象也就沒法作真正有用的工做,必須有一種方法能向對象發出請求,令其解決一些實際的問題,好比完成一次交易、打開一個開關等。每一個對象僅能接收特定的請求,咱們向對象發出的請求是經過它的接口定義的編碼

好比有一個電燈類 Light,咱們能夠向 Light 對象發出的請求包括打開 on、關閉 off,所以在 Light 類咱們須要定義兩個方法 on() 和 off(),而後建立一個 Light 類型的對象,並調用其接口方法設計

也行你已經發現了,對象經過接受請求並反饋結果,所以咱們能夠將對象當作是某項服務的提供者,也就是說你的程序將爲用戶提供服務,而且它能還能經過調用其餘對象提供的服務來實現這一點。咱們的最終目標是開發或調用工具庫中已有的一些對象,提供理想的服務來解決問題對象

更進一步,咱們須要將問題進行分解,將其抽象成一組服務,並有組織地劃分出每個功能單1、做用明確且緊密的模塊,避免將太多功能塞進一個對象裏。這樣的程序設計能夠提升代碼的複用性,同時也方便別人閱讀和理解咱們的代碼繼承


封裝

咱們把編程的側重領域劃分爲研發和應用兩塊。應用程序員調用研發程序員構建的基礎工具類作快速開發,研發程序員開發一個工具類,該工具類僅嚮應用程序員公開必要的內容,並隱藏內部實現的細節,這樣能夠有效避免工具類被錯誤使用和更改。顯然,咱們須要某些方法來保證工具類的正確使用,只有設定訪問控制,才能從根本上解決這個問題

所以,使用訪問控制的緣由有如下兩點:

  1. 讓應用程序員不要觸碰他們不該該觸碰的部分
  2. 類庫的建立者能夠在不影響他人使用的狀況下完善和更新工具庫

Java 提供了三個顯式關鍵字來設置類的訪問權限,分別是 public(公開)、private(私有)和 protected(受保護),這些訪問修飾符決定了誰能使用它們修飾的方法、變量或類

  1. public

    表示任何人均可以訪問和使用該元素

  2. private

    除了類自己和類內部的方法,外界沒法直接訪問該元素

  3. protected

    被 protected 修飾的成員對於本包和其子類可見。這句話有點太籠統了,更具體的歸納應該是:

    • 基類的 protected 是包內可見的
    • 若子類與基類不在同一包中,那麼子類實例能夠訪問從基類繼承而來的 protected 方法,而不能訪問基類實例的 protected 方法
  4. default

    若是不使用前面三者,默認就是 default 權限,該權限下的資源能夠被同一包中其餘類的成員訪問


複用

代碼和設計方案的複用性是面向對象的優勢之一,咱們能夠經過重複使用某個類的對象來實現複用性,例如,將一個類的對象的做爲另外一個類的成員變量使用。所以,新構成的類能夠是由任意數量和任意類型的其餘對象構成,這裏涉及到了組合和聚合兩個概念:

  • 組合(Composition)常常用來表示擁有關係(has-a relationship)例如,汽車擁有引擎。組合關係中,整件擁有部件的生命週期,因此整件刪除時,部件必定會跟着刪除
  • 聚合(Aggregation)表示動態的組合。聚合關係中,整件不會擁有部件的生命週期,因此整件刪除時,部件不會刪除

使用組合能夠爲咱們的程序帶來極大的靈活性。一般新建的類中,成員對象會使用 private 訪問權限,這樣應用程序員沒法對其直接訪問,咱們就能夠在不影響客戶代碼的前提下,從容地修改那些成員。咱們也能夠在運行時改變成員對象,從而動態地改變程序的行爲。下面提到的繼承並不具有這種靈活性,由於編譯器對經過繼承建立的類進行了限制


繼承

對象的概念爲編程帶來便利,它容許咱們將各式各樣的數據和功能封裝到一塊兒,這樣能夠恰當表達問題空間的概念,而不用受制於必須使用底層機器語言

經過 class 關鍵字,能夠造成編程語言中的基本單元。遺憾的是,這樣作仍是有不少問題:在建立一個類以後,即便另外一個新類與其具備類似的功能,你仍是不得不從新建立一個新類。若是咱們能利用現有的數據類型,對其進行克隆,再根據狀況進行添加和修改,那就方便許多了。繼承正是爲此而設計,但繼承並不等價於克隆。在繼承過程當中,如原始類(基類、父類)發生了變化,修改過的克隆類(子類、派生類)也會反映出這種變化

基類通常會有多個派生類,幷包含派生自它的類型之間共享的全部特徵和行爲。後者可能比前者包含更多的特徵,並能夠處理更多消息(或者以不一樣的方式處理它們)

使用繼承,你將構建一個類型層次結構,來表示你試圖解決的某種類型的問題。常見的例子是形狀,每一個形狀都有大小、顏色、位置等等,每一個形狀能夠繪製、擦除、移動等,還能夠派生出具體類型的形狀,如圓形、正方形、三角形等等。派生出的每一個形狀均可以具備附加的特徵和行爲,例如,某些形狀能夠翻轉,計算形狀面積的公式互不相同等等

類型層次結構體現了形狀之間的類似性和差別性,你不須要在問題描述和解決方案描述之間創建許多中間模型。從現有類型繼承並建立新類型,新類型不只包含現有類型的全部成員(儘管私有成員被隱藏起來並不可訪問),更重要的是它複製了基類的接口。也就是說,基類對象能接收的消息派生類對象也能接收。若是基類不能知足你的需求,你能夠在派生類添加更多的方法,甚至改變現有基類方法的行爲(覆蓋),只需在派生類從新定義這個方法便可


多態

在處理類的層次結構時,一般把一個對象當作是它所屬的基類,而不是把它當成具體類,經過這種方式,咱們能夠編寫出不侷限於特定類型的代碼。例如上述形狀的例子,方法操縱的是通用的形狀,而不關心具體是圓仍是三角形什麼的。全部形狀均可以被繪製、擦除和移動。所以方法向其中任何表明形狀的對象發送消息都沒必要擔憂對象如何處理信息

這種能力改善了咱們的設計,減小了軟件的維護代價。若是咱們把派生對象類型統一當作是它自己的基類,編譯器在編譯時就沒法準確地獲知具體是哪一個形狀被繪製,那一種車正在行駛,這正是關鍵所在:當程序接受這種消息時,程序員並不關心哪段代碼會被執行,繪圖方法能夠平等地應用到每種可能的形狀上,形狀會依據自身的具體類型執行恰當的代碼

所以,咱們就能添加一個新的不一樣執行方式的子類而不須要更改調用它的方法,更利於程序擴展。那麼編譯器如何肯定該執行哪部分的代碼呢?通常來講,編譯器不能進行函數調用,對於非 OOP 編譯器產生的函數調用會引發所謂的早期綁定,這意味着編譯器生成對特定函數名的調用,該調用會被解析爲將執行的代碼的絕對地址。而面嚮對象語言使用了一種後期綁定的概念,當向對象發送信息時,被調用的代碼直到運行時才肯定,編譯器要作的只是確保方法存在,並對參數和返回值執行類型檢查,但並不知道要執行的確切代碼

爲了執行後期綁定,Java 使用一個特殊的代碼位來代替絕對調用,這段代碼使用對象中存儲的信息來計算方法主體的地址。所以,每一個對象的行爲根據特定代碼位的內容而不一樣。當你向對象發送消息時,對象知道該如何處理這條消息。在某些語言如 C++ 必須顯式地授予方法後期綁定屬性的靈活性,而在 Java 中,動態綁定是默認行爲,不須要額外的關鍵字來實現多態性

發送消息給對象時,若是程序不知道接收的具體類型是什麼,但最終執行是正確的,這就是對象的多態性。面向對象的程序設計語言是經過動態綁定的方式來實現對象的多態性的,編譯器和運行時系統會負責控制全部的細節。咱們只須要知道要作什麼,以及如何利用多態性更好地設計程序


對象建立與生命週期

在使用對象時要注意的一個關鍵問題就是對象的建立和銷燬方式。每一個對象的生存都須要資源,尤爲是內存。當對象再也不被使用時,咱們應該及時釋放資源,清理內存

然而,實際的情形每每要複雜許多。咱們怎麼知道什麼時候清理這些對象呢?也許一個對象在某一系統中處理完成,但在其餘系統可能還沒處理完成。另外,對象的數據在哪?如何控制它的生命週期?在 C++ 設計中採用的觀點是效率第一,它將這些問題的選擇權交給了程序員。程序員在編寫程序時,經過將對象放在棧或靜態存儲區域中來肯定內存佔用和生存空間。相對的,咱們也犧牲了程序的靈活性

Java 使用動態內存分配,在堆內存中動態地建立對象。在這種方式下,直到程序運行咱們才能知道建立的對象數量、生存類型和時間。在堆內存上開闢空間所需的時間可能比棧內存要長(但並不必定),但動態分配論認爲:對象一般是複雜的,相比於對象建立的總體開銷,尋找和釋放內存空間的開銷微不足道。對於對象的生命週期問題,在 C++ 中你必須以編程方式肯定什麼時候銷燬對象,而 Java 的垃圾收集機制能自動發現再也不被使用的對象並釋放相應的內存空間,使得 Java 的編碼過程比用 C++ 要簡單許多

相關文章
相關標籤/搜索