1. 程式人生 > >Java學習(二)

Java學習(二)

弄清楚類與物件的本質與基本特徵,是進一步學習面向物件程式語言的基本要求。面向物件程式設計與面向過程程式設計在思維上存在著很大差別,改變一種思維方式並不是一件容易的事情。

一、面向物件程式設計

程式由物件組成,物件包含對使用者公開的特定功能部分,和隱藏在其內部的實現部分。從設計層面講,我們只關心物件能否滿足要求,而無需過多關注其功能的具體實現。面對規模較小的問題時,面向過程的開發方式是比較理想的,但面對解決規模較大的問題時,面向物件的程式設計往往更加合適。

物件是對客觀事物的抽象,類是對物件的抽象,是構建物件的模板。由類構造(construct)物件的過程稱為建立類的例項(instance)或類的例項化。

封裝是將資料和行為組合在一個包中,並對使用者隱藏資料的實現方式。物件中的資料稱為例項域(instance field)或屬性、成員變數,操縱資料的過程稱為方法(method)。物件一般有一組特定的例項域值,這些值的集合就是物件當前的狀態。封裝的關鍵在於不讓類中的方法直接的訪問其他類的例項域,程式僅通過物件的方法與物件資料進行互動。封裝能夠讓我們通過簡單的使用一個類的介面即可完成相當複雜的任務,而無需瞭解具體的細節實現。

物件的三個主要特徵

  1. 物件的行為(behavior):可以對物件施加哪些操作,通過方法(method)實現。
  2. 物件的狀態(state):儲存物件的特徵資訊,通過例項域(instance field)實現。
  3. 物件的標識(identity):辨別具有不同行為與狀態的不同物件。

設計類

傳統的面向過程的程式設計,必須從頂部的 main 入口函式開始編寫程式。面向物件程式設計沒有所謂的頂部,我們要從設計類開始,然後再往每個類中新增方法。那麼我們該具體定義什麼樣的類?定義多少個?每個類又該具備哪些方法呢?這裡有一個簡單的規則可以參考 —— “找名詞與動詞”原則。

我們需要在分析問題的過程中尋找名詞和動詞,這些名詞很有可能成為類,而方法對應著動詞。當然,所謂原則,只是一種經驗,在建立類的時候,哪些名詞和動詞是重要的,完全取決於個人的開發經驗(抽象能力)。

類之間的關係

最常見的關係有:依賴(use-a)、聚合(has-a)、繼承(is-a)。可以使用UML(unified modeling language)繪製類圖,用來視覺化的描述類之間的關係。

二、預定義類與自定義類

在 Java 中沒有類就無法做任何事情,Java 標準類庫中提供了很多類,這裡稱其為預定義類,如 Math 類。要注意的是:並非所有類都具有面向物件的特徵(如 Math 類),它只封裝了功能,不需要也不必要隱藏資料,由於沒有資料,因此也不必擔心生成以及初始化例項域的相關操作。

要使用物件,就必須先構造物件,並指定其初始狀態。我們可以使用構造器(constructor)構造新例項,本質上,構造器是一種特殊的方法,用以構造並初始化物件。構造器的名字與類名相同。如需構造一個類的物件,需要在構造器前面加上 new 操作符,如new Date()。通常,希望物件可以多次使用,因此,需要將物件存放在一個變數中,不過要注意,一個物件變數並沒有實際包含一個物件,而僅僅是引用一個物件。

訪問器與修改器 我們把只訪問物件而不修改物件狀態的方法稱為 訪問器方法(accessor method)。如果方法會對物件本身進行修改,我們稱這樣的方法稱為 更改器方法(mutator method)。

使用者自定義類

要想建立一個完成的程式,應該將若干類組合在一起,其中只有一個類有 main 方法。其它類( workhorse class)沒有 main 方法,卻有自己的例項域和例項方法,這些類往往需要我們自己設計和定義。

一個原始檔中,最多隻能有一個公有類(訪問級別為public),但可以有任意數目的非公有類。儘管一個原始檔可以包含多個類,但還是建議將每一個類存在一個單獨的原始檔中。 不提倡用public標記例項域(即物件的屬性),public 資料域允許程式中的任何方法對其進行讀取和修改。當例項域設定為 private 後,如果需要對其進行讀取和修改,可以通過定義公有的域訪問器或修改器來實現。這裡要注意:不要編寫返回引用可變物件的訪問器方法,如:

class TestClass{
    private Date theDate;
    public getDate(){
        return theDate; // Bad
    }
}

上面的訪問器返回的是對例項屬性 theDate 的引用,這導致在後續可以隨意修改當前例項的 theDate 屬性,比如執行x.getDate().setTime(y),破壞了封裝性!如果要返回一個可變物件的引用,應該首先對他進行克隆,如下:

class TestClass{
    private Date theDate;
    public getDate(){
        return (Date) theDate.clone(); // Ok
    }
}

構造器

構造器與類同名,當例項化某個類時,構造器會被執行,以便將例項域初始化為所需的狀態。構造器總是伴隨著 new 操作符的呼叫被執行,不能對一個已經存在的物件呼叫構造器來重置例項域。

  1. 構造器與類同名
  2. 每個類可以有多個構造器
  3. 構造器可以有 0 個或多個引數
  4. 構造器沒有返回值
  5. 構造器總是伴隨著 new 操作一起呼叫

基於類的訪問許可權

方法可以訪問所屬類的所有物件的私有資料。[*]

在實現一個類時,應將所有的資料域都設定為私有的。多數時候我們把方法設計為公有的,但有時我們希望將一個方法劃分成若干個獨立的輔助方法,通常這些輔助方法不應該設計成為公有介面的一部分,最好將其標記為 private 。只要方法是私有的,類的設計者就可以確信:他不會被外部的其他類操作呼叫,可以將其刪去,如果是公有的,就不能將其刪除,因為其他的程式碼可能依賴它。

final 例項域

在構建物件時必須對宣告的 final 例項域進行初始化,就是說必須確保在構造器執行之後,這個域的值被設定,並且在後面的操作中,不能夠再對其進行修改。final 修飾符大都用於基本型別,或不可變類的域。

靜態域和靜態方法

靜態域和靜態方法,是屬於類且不屬於物件的變數和函式。

通過 static 修飾符,可以標註一個域為靜態的,靜態域屬於類,而不屬於任何獨立的物件,但是每個物件都會有一份這個靜態域的拷貝。靜態方法是一種不能對物件施加操作的方法,它可以訪問自身類的靜態域,類的物件也可以呼叫類的靜態方法,但更建議直接使用類名呼叫靜態方法。

使用靜態方法的場景 : 一個方法不需要訪問物件狀態,其所需引數都是通過顯式引數提供;一個方法只需要訪問類的靜態域。

靜態方法還有另外一種常見用途,作為工廠方法用以構造物件。之所已使用工廠方法,兩個原因:一是無法命名構造器,因為構造器必須與類名相同;二是當時用構造器時無法改變構造的物件型別。

程式入口 main 方法就是一個典型的靜態方法,其不對任何物件進行操作。在啟動程式時還沒有任何一個物件,靜態的 main 方法將執行並建立程式所需要的物件。每個類都可以有一個 main 方法,作為一個小技巧,我們可以通過這個方法對類進行單元測試。

三、方法引數

Java 中的方法引數總是按值呼叫,也就是說,方法得到的是所有引數的值的一個拷貝,特別是,方法不能修改傳遞給它的任何引數變數的內容。然而,方法引數有兩種型別:基本資料型別和物件引用。

四、物件構造

如果在構造器中沒有顯式的為域賦值,那麼域會被自動的賦予預設值:數值為 0、布林之為 false、物件引用為 null。在類沒有提供任何構造器的時候,系統會提供一個預設的構造器。

有些類有多個構造器,這種特徵叫做過載(overloading)。如果多個方法有相同的名字、不同的引數,便產生了過載。 Java 中允許過載任何方法,而不僅是構造器方法。要完整的描述一個方法,需要指出方法名以及其引數型別,這個描述被稱作方法的簽名。

通過過載類的構造器方法,可以採用多種形式設定類的例項的初始狀態。當存在多個構造器的時候,也可以在構造器內部通過 this 呼叫另一個構造器,要注意的是這個呼叫必須在當前構造器的第一行:

class Test{
    Test(int number) {
        this(number, (String)number);   // 位於當前構造器的第一行
    }

    Test(int number, String str) {
        _number = number;
        _string = str;
    }
}

初始化塊

在一個類的宣告中,可以包含多個程式碼塊。只要構造類的物件,這些塊就會被執行。例如:

class Test{
    private int number;
    private String name;

    /**
     * 初始化塊
     */
    {
        number = 5;
    }

    Test(){
        name = 'Kelsen'
    }

    public void pring(){
        System.out.println(name + "-" + number);
    }
}

執行順序為,首先執行初始化塊,然後再執行構造器的主體部分。這種機制不是必須的,也不常見。通常會直接將初始化程式碼放在構造器中。

Java 中不支援析構器,它有自動的垃圾回收器,不需要人工進行記憶體回收。但,如果某個資源需要在使用完畢後立刻被關閉,那麼就需要人工來管理。物件用完時可以應用一個 close 方法來完成相應的清理操作。

五、包

藉助於包,可以方便的組織我們的類程式碼,並將自己的程式碼與別人提供的程式碼庫區分管理。標準的 Java 類庫分佈在多個包中,包括 java.lang、java.util 和 java.net 等。標準的 Java 包具有一個層次結構。如同硬碟檔案目錄巢狀一樣,也可以使用巢狀層次組織包。所有的標準 Java 包都處於 javajavax 包層次中。從編譯器角度看,巢狀的包之間沒有任何關係,每一個都擁有獨立的類集合。

一個類可以使用所屬包中的所有類,以及其他包中的公有類(pbulic class)。 import 語句是一種引用包含在包中的類的簡明描述。packageimport 語句類似 C++ 中的 namespaceusing 指令。

import 語句還可以用來匯入類的靜態方法和靜態域。

如果要將一個類放入包中,就必須將包的名字放在原始檔的開頭,包中定義類的程式碼之前。如:


package com.kelsem.learnjava;

public class Test{
    // ...
}

如果沒有在原始檔中放置 package 語句,這個原始檔中的類就被放置在一個預設包中。

包作用域

標記為 private 的部分只能被定義他們的類訪問,標記為 public 的部分可以被任何類訪問;如果沒有指定訪問級別,這個部分(類/方法/變數)可以被同一個包中的所有方法訪問。

類路徑

類儲存在檔案系統的目錄中,路徑與包名匹配。另外,類檔案也可以儲存在 JAR 檔案中。為了使類能夠被多個程式共享,通常把類放到一個目錄中,將 JAR 檔案放到一個目錄中,然後設定類路徑。類路徑是所有包含類檔案的路徑的集合,設定類路徑時,首選使用 -calsspath 選項設定,不建議通過設定 CLASSPATH 這個環境變數完成該操作。

六、文件註釋

JDK 包含一個非常有用的工具,叫做 javadoc 。它通過分析我們的程式碼檔案註釋,自動生成 HTML 文件。每次修原始碼後,通過執行 javadoc 就可以輕鬆更新程式碼文件。Javadoc 功能包括:Javadoc搜尋,支援生成HTML5輸出,支援模組系統中的文件註釋,以及簡化的Doclet API。詳細使用說明可參考 https://docs.oracle.com/en/java/javase/11/javadoc/javadoc.html

七、類的設計

一定要保證資料私有 務必確保封裝性不被破壞。

一定要對資料初始化 Java 不會對區域性變數進行初始化,但會對物件的例項域進行初始化。最好不要依賴於系統預設值,而是顯式的對例項域進行初始化。

不要在類中使用過多的基本型別 通過定義一個新的類,來代替多個相關的基本型別的使用。

不是所有的域都需要獨立的域訪問器和域更改器

將職責過多的類進行分解 如果明顯的可以將一個複雜的類分解為兩個更簡單的類,就應該將其分解。

類名和方法名要能夠體現他們的職責 對於方法名,建議:訪問器以小寫 get 開頭,修改器以小寫 set 開頭;對於類名,建議類名是採用一個名詞(Order)、前面有形容詞修飾的名詞(RushOrder)或動名詞(ing字尾)修飾名詞(BillingAddress)。

優先使用不可變的類 要儘可能讓類是不可變的,當然,也並不是所有類都應當是不可變的。