天天看點

2. Java面向對象

主要來自于《尚矽谷Java教程》

目錄

  • 類的屬性和方法
    • 面向過程與面向對象
    • 記憶體解析
    • 方法重載與可變形參
    • 方法值傳遞機制
      • 了解變量的指派
    • 四種權限修飾
    • 構造器
      • 屬性初始化順序
      • JavaBean
    • this的使用
      • 屬性與方法
    • package和import的使用
      • package的使用
      • JDK中主要的包
      • import關鍵字
    • 代碼塊的使用
      • 靜态代碼塊
      • 非靜态代碼塊
  • 繼承性
    • 繼承的使用
    • 繼承的特點
    • 方法的重寫
    • super關鍵字
      • super調用構造器
    • 子類對象執行個體化過程
      • 從結果上看(繼承性)
      • 從過程上看
    • Object類
      • equals方法
        • 與==運算符的差別
        • 重寫equals原則
      • toString方法
  • 多态性
    • 多态的使用
    • instanceof關鍵字
      • instanceof使用
      • 向下轉型與向上轉型
  • 單元測試
  • 包裝類
    • 基本資料類型和包裝類之間的轉換
      • 基本資料類型=>包裝類
      • 包裝類=>基本資料類型
      • 自動裝箱與與拆箱
      • 基本資料類型與包裝類<=>String
  • static關鍵字
    • static修飾屬性
    • static修飾方法
    • 單例設計模式
      • 設計模式
      • 單例設計模式概念
      • 單例模式實作
        • 餓漢式
        • 懶漢式
        • 二者對比
      • 單例模式優點
      • 單例模式應用場景
  • final關鍵字
  • 抽象類與抽象方法
    • 抽象類
    • 抽象方法
    • abstract關鍵字的使用
    • 抽象類的匿名子類對象
  • 接口
    • 接口的概述
    • 接口的使用
      • 注意事項
      • 實作接口
      • 多實作接口
      • 接口繼承接口
    • JDK8中接口的新特性
      • 靜态方法
      • 預設方法
  • 内部類
    • 成員内部類

二者之間的主要差別:

  • 面向過程(POP)強調的是功能行為,以函數為最小機關,考慮怎麼做。
  • 面向對象(OOP)将功能封裝進對象,強調具備了功能的對象,以類/對象為最小機關,考慮誰來做。

面向對象的三大特征:

  • 封裝(Encapsulation)
  • 繼承(Inheritance)
  • 多态(Polymorphism)

  • 堆(Heap),此記憶體區域的唯一目的就是存放對象執行個體,幾乎所有的對象執行個體都在這裡配置設定記憶體。這一點在Java虛拟機規範中的描述是:所有的對象執行個體以及數組都要在堆上配置設定。
  • 通常所說的棧(Stack),是指虛拟機棧。虛拟機棧用于存儲局部變量等。局部變量表存放了編譯期可知長度的各種基本資料類型(

    boolean

    byte

    char

    short

    int

    float

    long

    double

    )、對象引用(

    reference

    類型它不等同于對象本身,是對象在堆記憶體的首位址)。方法執行完,自動釋放。
  • 方法區(MethodArea),用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。

屬性(非

static

)加載在堆空間中,而局部變量加載在棧空間中。

方法的重載(overload):

  • 概念:在同一個類中,允許存在一個以上的同名算法,隻要他們的參數個數或者參數類型不同即可。
  • 特點:與傳回值類型無關,隻看參數清單,且參數清單必須不同(參數的個數或類型)。

JDK 5.0提供了Varargs(variable number of arguments)機制,允許直接定義能和多個實參相比對的形參。

  • 格式:

    String ... strs

  • 傳入的參數個數可以是:0, 1, 2, ...。
  • 可變個數形參的方法和與本類中的同名且形參不同方法構成重載。
  • 可變個數形參的方法和與本類中的同名且形參類型相同的數組之間不構成重載。
public static void show(String str){
    System.out.println(str);
}

// 構成重載
public static void show(String ... strs){
    for (String str : strs) {
        System.out.print(str + ' ');
    }
    System.out.println();
}

// 與可變個數形參沖突,不構成重載
//    public static void show(String[] strs){
//        for (String str : strs) {
//            System.out.print(str + ' ');
//        }
//        System.out.println();
//    }
           
  • 可變個數形參必須聲明在末尾。
  • 最多隻能聲明一個可變形參。

方法,必須由其所在類或對象調用才有意義。若方法含有參數:

  • 形參:方法聲明時的參數。
  • 實參:方法調用時實際傳給形參的參數值

Java裡方法的參數傳遞方式隻有一種:值傳遞。即将實際參數值的副本(複制品)傳入方法内,而參數本身不受影響。

  • 形參是基本資料類型:将實參基本資料類型變量的“資料值”傳遞給形參。
  • 形參是引用資料類型:将實參引用資料類型變量的“位址值”傳遞給形參。

  • 如果變量是基本資料類型,此時指派的是變量儲存的資料值。
  • 如果變量是引用資料類型,此時指派的是變量所儲存資料的位址。
public class ValueTransferTest {
    public static void main(String[] args){
        int a = 1;
        int b = a;  // 指派的是變量儲存的資料值1
        b = 2;

        Transfer tran1 = new Transfer();
        Transfer tran2 = tran1;  // 指派變量所儲存資料的位址
        tran2.num = 5;

        System.out.println("" + a + " " + b + " " + tran1.num + " " + tran2.num);
        // 1 2 5 5
    }
}

class Transfer {
    int num;
}
           

類似的:

  • 如果參數是基本資料類型,此時實參賦給形參的是實參真實存儲的資料值。
  • 如果參數是引用資料類型,此時實參賦給形參的是實參存儲資料的位址值。

Java規定的四種權限修飾(從小到大):

private

<預設<

protected

<

public

。四種權限都可以用來修飾累的内部結構:屬性、方法、構造器、内部類。通過權限修飾來展現封裝性。

修飾符 類内部 同一個包 不同包的子類 同一個工程

private

Yes
(預設)

protected

public

  • 對于

    class

    的修飾隻可以是

    public

    或預設。

構造器(constructor)也稱為構造方法,用來在建立對象、初始化對象。

  • 如果沒有顯式的定義類的構造器的話,則系統預設提供一個空參的構造器,是以一個類中,至少會有一個構造器。
  • 定義構造器的格式:權限修飾符類名(形參清單){ }。
  • 一個類中定義的多個構造器,彼此構成重載。
  • 一旦我們顯式的定義了類的構造器之後,系統就不再提供預設的空參構造器。

屬性初始化的先後順序如下:

  1. 預設初始化,例如

    private int age;

    預設初始化為
  2. 顯式初始化,例如

    private int age = 18;

    。或者代碼塊中指派,二者先後順序為聲明順序。
  3. 構造器中初始化。
  4. 通過方法給屬性指派,例如

    setAge(30);

JavaBean是一種Java語言寫成的可重用元件。所謂javaBean,是指符合如下标準的Java類:

  • 類是公共的。
  • 有一個無參的公共的構造器。
  • 有屬性,且有對應的get、set方法。

使用者可以使用JavaBean将功能、處理、值、資料庫通路和其他任何可以用Java代碼創造的對象進行打包,并且其他的開發者可以通過内部的JSP頁面、Servlet、其他JavaBean、applet程式或者應用來使用這些對象。使用者可以認為JavaBean提供了一種随時随地的複制和粘貼的功能,而不用關心任何改變。

關鍵字

this

可以用來修飾:屬性、方法、構造器。

  • this

    用來修飾屬性和方法時,

    this

    可以了解為目前對象,在類的方法中,可以通過

    this.

    來調用目前對象的屬性和方法。通常情況下,省略

    this.

    ,但當方法的形參和類的屬性同名時,必須顯式使用

    this.

    。例如:
public void setAge(int age) {
    this.age = age;
}
           

使用

this

來調用其他的構造器(不能調用自己)。例如:

public Person() {
    age = 18;
}

public Person(String name){
    this();
    this.name = name;
}

public Person(String name, String sex) {
    this(name);
    this.sex = sex;
}
           
  • 如果一個類中有n個構造器,最多有n-1個構造器通過

    this

    調用其他構造器。
  • this

    調用其他構造器必須聲明在構造器首行,且最多隻能聲明一個。

  1. 為了更好的實作項目中類的管理,提供包的概念
  2. 使用package聲明類或接口所屬的包,聲明在源檔案的首行
  3. 包屬于辨別符,遵循辨別符的命名規則、規範( xxxyyyzzz)、“見名知意”
  4. .

    一次,就代表一層檔案目錄,例如

    com.learnjava.arraytest

  1. java.lang

    :包含一些Java語言的核心類,如

    String

    Math

    Integer

    System

    Thread

    ,提供常用功能。
  2. java.net

    :包含執行與網絡相關的操作的類和接口。
  3. java.io

    :包含能提供多種輸入輸出功能的類。
  4. java.util

    :包含一些實用工具類,如定義系統特性、接口的集合架構類、使用與日期月曆相關的函數。
  5. java.text

    :包含了一些java格式化相關的類。
  6. java.sql

    :包含了java進行JDBC資料庫程式設計的相關類/接口。
  7. java.awt

    :包含了構成抽象視窗工具集(abstract window toolkits)的多個類,這些類被用來建構和管理應用程式的圖形使用者界面(GUI)。

  • 使用的類或接口是

    java.lang

    包,或者本包下定義的,則可以省略

    import

  • 如果源檔案中,使用了不同包下的同名類(如

    java.util.Date

    java.sql.Date

    ),則必須有一個要以全名的方式顯示,如下所示:
import java.util.Date;

public class ImportTest {
    public static void main(String[] args) {
        Date date = new Date();
        java.sql.Date date1 = new java.sql.Date(123123123123132L);  // 全名則不需要import
    }
}
           
  • 使用類似

    import java.util.*;

    的方式,可以調用包下的所有結構,但是如果使用的是其子包下的類,如

    java.util.zip.CRC32

    ,則仍需要顯式

    import

    ,如下所示:
import java.util.*;
import java.util.zip.CRC32;


public class ImportTest {
    public static void main(String[] args) {
        new CRC32();  // 需要單獨import
    }
}
           
  • import static

    來導入指定類或接口中的靜态結構,如下所示:
import static java.lang.Math.*;

public class ImportTest {
    public static void main(String[] args) {
        System.out.println(pow(PI, 2));  // 輸出pi的平方
    }
}
           

  • 代碼塊用來初始化類或對象,隻能用

    static

    修飾。
  • 根據是否用

    static

    修飾,分為靜态代碼塊和非靜态代碼塊。
import org.junit.Test;

public class BlockTest {
    @Test
    public void testBlock() {
        Person p1 = new Person();
        // Static block.
        // Block.

        Person p2 = new Person();
        // Block.

        Person.info();
        // I am a happy person!
    }
}

class Person {
    // 屬性
    String name;
    int age;
    static String describe = "I am a Person.";

    // 構造器
    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 靜态代碼塊
    static {
        System.out.println("Static block.");
        describe = "";
    }

    // 非靜态代碼塊
    {
        System.out.println("Block.");
        age = 1;
    }

    public void eat() {
        System.out.println("Person eat.");
    }

    public static void info() {
        System.out.println("I am a happy person!");
    }
}
           

  1. 内部可以有輸出語句。
  2. 随着類的加載而執行,而且隻執行一次。
  3. 作用:初始化類的資訊。
  4. 如果一個類中定義了多個靜态代碼塊,則按照聲明的先後順序執行。
  5. 靜态代碼塊的執行要優先于非靜态代碼塊的執行。
  6. 靜态代碼塊内隻能調用靜态的屬性、靜态的方法,不能調用非靜态的結構。

  1. 随着對象的建立而執行。每建立一個對象,就執行一次非靜态代碼塊。
  2. 作用:可以在建立對象時,對對象的屬性等進行初始化。
  3. 如果一個類中定義了多個非靜态代碼塊,則按照聲明的先後順序執行。
  4. 非靜态代碼塊内可以調用靜态的屬性、靜态的方法,或非靜态的屬性、非靜态的方法。

繼承(inheritance)的格式:

class A extends B{}

  • A:子類、派生類、subclass。
  • B:父類、超類、superclass。

一旦

A

繼承

B

之後,

A

就獲得了

B

中所有的屬性和方法。特别地,父類中聲明為

private

的屬性或方法,子類仍然能夠擷取,隻是因為封裝性的原因,子類無法直接調用父類的私有結構。

  1. Java中類的單繼承性:一個類可以被多個子類繼承,但一個類隻能有一個父類。
  2. 子類父類是相對的概念,子類直接繼承的父類稱為直接父類,間接繼承的父類稱為簡介父類。
  3. 子類繼承父類後,就擷取了直接父類和所有間接父類中聲明的屬性和方法。

重寫(override):在子類中根據需要對從父類中繼承的方法進行改造,在程式執行時,子類的方法将覆寫父類的方法。

public class OverrideTest {
    public static void main(String[] args) {
        new Person().eat();  // Person eat.
        new Student().eat();  // Student eat.
    }
}

class Person{
    public void eat() {
        System.out.println("Person eat.");
    }
}

class Student extends Person {
    @Override  // 告訴編譯器這是一個重寫的方法,需要檢查,如果不符合重寫規定(如寫成eat1)會報錯
    public void eat() {
        System.out.println("Student eat.");
    }
}
           
  • 子類重寫的方法必須和父類被重寫的方法具有相同的方法名稱、參數清單。
  • 子類重寫的方法的傳回值類型不能大于父類被重寫的方法的傳回值類型。
    • 如果父類被重寫的方法傳回值類型是

      void

      ,則子類重寫的方法也必須傳回

      void

    • A

      類,則子類重寫的方法也必須傳回

      A

      類或其子類。
    • 如果父類被重寫的方法傳回值類型是基本資料類型,如

      double

      ,則子類重寫的方法也必須傳回相同的資料類型,

      double

  • 子類重寫的方法使用的訪間權限不能小于父類被重寫的方法的訪間權限。
  • 子類不能重寫父類中聲明為

    private

    權限的方法。
  • 子類方法抛出的異常類型不能大于父類被重寫方法的異常類型。

最後需要注意,子類和父類中的同名同參數方法要麼都聲明為非

static

的(考慮重寫),要麼都聲明為

static

(不是重寫)。

super

了解為父類的,用來調用:屬性、方法、構造器。

super

的使用:

  1. 在子類的方法和構造器中,顯式調用父類中聲明的屬性或方法,但一般省略

    super.

  2. 特殊情況:當子類和父類定義了同名的屬性時,必須顯式調用父類中聲明的屬性。
  3. 特殊情況:當子類重寫了父類中的方法後,則需要顯式調用父類中被重寫的方法。

  1. 通過顯式的使用

    super()

    的方式來調用父類中聲明的指定構造器。
  2. super()

    必須聲明在子類構造器的首行,是以需要和

    this()

    二選一。
  3. 如果沒有顯示聲明

    super()

    this()

    ,則預設調用父類中空參數的構造器。

  • 子類繼承父類以後,就擷取了父類中聲明的屬性或方法。
  • 建立子類的對象,在堆空間中,就會加載所有父類中聲明的屬性。

  • 當我們通過子類的構造器建立子類對象時,我們一定會直接或間接的調用其父類的構造器,進而調用父類的父類的構造器,直到調用了

    java.lang.Object

    類中空參的構造器為止。
  • 正因為加載過所有的父類的結構,是以才可以看到記憶體中有父類中的結構,子類對象才可以考慮進行調用。

  1. 如果沒有顯式聲明一個類的父類,則此類繼承于

    java.lang.Object

    類。
  2. 所有的類都直接或間接繼承自

    java.lang.Object

    類(除了它本身),是以所有類都具有

    java.lang.Object

    類所聲明的功能。
  3. Object

    類隻聲明了一個空參的構造器。

首先,回顧

==

運算符的用法:

  • 如果比較的是基本資料類型變量:比較兩個變量儲存的資料是否相等(不一定類型要相同)。
double a = 1;
float b = 1;
int c = 1;
System.out.println(a == b);  // true
System.out.println(b == c);  // true
           
  • 如果比較的是引用資料類型變量:比較兩個對象的位址值是否相同,即兩個引用是否指向同一個對象實體。

equals

方法與

==

運算符的差別,主要注意的有:

  • equals

    是方法,而非運算符。
  • 隻适用于引用資料類型。
  • Object

    類中

    equals

    方法的定義和

    ==

    運算符相同。
public boolean equals(Object obj) {
    return (this == obj);
}
           
  • String

    Data

    File

    、包裝類等都重寫了

    Object

    類中的

    equals()

    方法,重寫後,比較的不是兩個引用的位址是否相同,而是比較兩個對象的“實體内容”是否相同。
  • 手動重寫

    equals

    方法,如下所示(由IDEA自動生成):
import java.util.Objects;

public class ObjectTest {
    public static void main(String[] args) {
        Animal a1 = new Animal("name1", 10);
        Animal a2 = new Animal("name1", 10);
        System.out.println(a1 == a2);  // false
        System.out.println(a1.equals(a2));  // true
    }
}

class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Animal animal = (Animal) o;
        return age == animal.age && Objects.equals(name, animal.name);
    }
}
           

上面的

Objects.equals(Object a, Object b)

方法源碼如下:

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}
           

  1. 對稱性:

    x.equals(y)

    y.equals(x)

    傳回結果相同。
  2. 自反性:

    x.equals(x)

    傳回

    true

  3. 傳遞性:如果

    x.equals(y)

    y.equals(z)

    都傳回

    true

    ,則

    x.equals(z)

    true

  4. 一緻性:隻要

    x

    y

    的内容不變,不管執行

    x.equals(y)

    多少次,傳回結果都應相同。
  5. 任何情況下,

    x.equals(null)

    false

    null.equals(x)

    false

  6. x.equals(和x不同的類的對象)

    false

  • 當我們輸出一個對象的引用時,實際上就是調用目前對象的

    toString

    方法。在

    Object

    toString

    方法輸出類名和位址值,其定義如下:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
           
  • String

    Data

    File

    Object

    toString()

    方法。

對象的多态性:父親的引用指向子類的對象。多态性隻适用于方法,不适用于屬性。

  • 在編譯器,隻能調用父類聲明的方法,但是在運作期,實際運作的是子類重寫父類的方法。

多态使用的前提:

  1. 需要有類的繼承關系。
  2. 方法的重寫。
public class OverrideTest {
    public static void main(String[] args) {
        Person p1 = new Student();
        Person p2 = new Teacher();

        p1.eat();  // Student eat.
        p2.eat();  // Teacher eat.
        // p1.study();  // 不能調用子類的方法,因為上面聲明了Person p1
    }
}

class Person{
    public void eat() {
        System.out.println("Person eat.");
    }
}

class Student extends Person {
    @Override
    public void eat() {
        System.out.println("Student eat.");
    }

    public void study() {
        System.out.println("Student study.");
    }
}

class Teacher extends Person {
    @Override
    public void eat() {
        System.out.println("Teacher eat.");
    }
}
           

a instanceof A

用來判斷對象

a

是否屬于指定類

A

  • 如果

    a instanceof A

    true

    B

    A

    的父類,則

    a instanceof B

    也傳回

    ture

父類(

Person

)為上,子類(

Student

)為下。

  • 向上轉型:即多态,如

    Person p1 = new Student();

  • 向下轉型:強制類型轉換,需要使用

    instanceof

    關鍵字進行判斷。

有了多态後,記憶體實際上加載了子類特有的屬性和方法,但是由于聲明為父類,編譯時無法調用。此時需要使用強制類型轉換符向下轉型。如下所示:

public class OverrideTest {
    public static void main(String[] args) {
        Person p1 = new Student();
        Person p2 = new Teacher();

        p1.eat();  // Student eat.
        p2.eat();  // Teacher eat.
        // p1.study();  // 不能調用子類的方法,因為上面聲明了Person p1
        
        if (p1 instanceof Student) {
            ((Student) p1).study();  // Student study.
        }

        if (p2 instanceof Student) {  // false
            ((Student) p2).study();
        }
    }
}
           

步驟:

  1. 建立Java類進行測試,要求:此類是

    public

    的,且提供公共的無參構造器。
  2. 在此類中聲明單元測試方法,要求:方法的權限是

    public

    的,沒有形參,沒有傳回值。
  3. 在單元測試方法上聲明注解

    @Test

    ,并在單元測試類中導入

    import org.junit.Test;

  4. 聲明好之後,在方法體内測試相關的代碼。
import org.junit.Test;
import java.util.Date;

public class JUnitTest {
    @Test
    public void testPerson() {
        Object s1 = "test";
        String s2 = "test";
        System.out.println(s1.equals(s2));  // Test passed
        
        // Date date = (Date) s1;  // 如果運作,則出現Tests failed
    }
}
           

包裝類(Wrapper)指針對8種基本資料類型定義的相應的引用類型,一般是基本資料類型首字母大寫,有2個例外:

Integer

Character

Integer i1 = new Integer(123);
Integer i2 = new Integer("55");

Float f1 = new Float(12.3);

Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("true");
Boolean b3 = new Boolean("true123");  // Boolean比較特殊,不會報錯
System.out.println(b3);  // false
           
  • 需要注意的是,如果類中有屬性是

    Boolean

    類型的,由于是引用類型,它的預設初始化值不再是

    false

    ,而是

    null

Integer i1 = new Integer(123);
int i = i1.intValue();
           

// 自動裝箱
int num = 10;
Integer i = num;

// 自動拆箱
int num2 = i;
           

  • 基本資料類型與包裝類轉換成

    String

    類型有以下兩種方法:
// 方式1:連接配接運算
int num = 20;
String str = num + "";

// 方式2:調用valueOf方法
float f1 = 13.5f;
String str1 = String.valueOf(f1);

Double d1 = 3.14;
String str2 = String.valueOf(d1);
           
  • String

    類型轉基本資料類型使用

    parseInt

    等方法(可能報錯):
int num = Integer.parseInt("12345");
// int num2 = Integer.parseInt("123.4");  // NumberFormatException
boolean b1 = Boolean.parseBoolean("true1");  // false
           

我們有時候希望無論産生了多少對象,某些特定的資料在記憶體空間裡隻有一份。

屬性按照是否被

static

修飾分為靜态屬性和執行個體變量。當建立了類的多個對象,多個對象共享一個靜态變量。

public class StaticTest {
    public static void main(String[] args) {
        Chinese c1 = new Chinese();
        Chinese c2 = new Chinese();

        c1.nation = "China";
        System.out.println(c2.nation);  // China
        System.out.println(Chinese.nation);  // China
    }
}

class Chinese {
    static String nation;
}
           
  • 靜态變量随着類的加載而加載,可以直接通過類名調用。
  • 靜态變量的加載早于對象的建立,由于類隻會加載一次,靜态變量在記憶體中也隻會存在一份:存在方法區的靜态域中。

  • 靜态方法中隻能調用靜态的屬性或方法。
  • 靜态方法内,不能使用

    this

    super

    關鍵字。

設計模式是在大量的實踐中總結和理論化之後優選的代碼結構、程式設計風格、以及解決問題的思考方式。

所謂類的單例(Singleton)設計模式,就是采取一定的方法保證在整個的軟體系統中:

  • 對某個類隻能存在一個對象執行個體,并且該類隻提供一個取得其對象執行個體的方法。
  • 首先必須将類的構造器的通路權限設定為

    private

    ,這樣,就不能用

    new

    操作符在類的外部産生類的對象了,但在類内部仍可以産生該類的對象。
  • 因為在類的外部開始還無法得到類的對象,隻能調用該類的某個靜态方法以傳回類内部建立的對象,靜态方法隻能通路類中的靜态成員變量,是以,指向類内部産生的該類對象的變量也必須定義成靜态的。

public class SingletonTest {
    public static void main(String[] args) {
        Bank b1 = Bank.getInstance();
        Bank b2 = Bank.getInstance();
        System.out.println(b1 == b2);  // true
    }
}

// 餓漢式
class Bank {
    // 1. 私有化類的構造器
    private Bank() {
    }

    // 2. 内部建立靜态的類的對象
    private static Bank instance = new Bank();

    // 3. 提供公共的靜态方法,傳回類的對象
    public static Bank getInstance() {
        return instance;
    }
}
           

public class SingletonTest {
    public static void main(String[] args) {
        Order o1 = Order.getInstance();
        Order o2 = Order.getInstance();
        System.out.println(o1 == o2);  // true
    }
}

// 懶漢式
class Order {
    // 1. 私有化類的構造器
    private Order() {
    }

    // 2. 内部聲明靜态的類的對象,不進行初始化
    private static Order instance = null;

    // 3. 提供公共的靜态方法,傳回類的對象
    public static Order getInstance() {
        if (instance == null) {
            instance = new Order();
        }
        return instance;
    }
}
           

  • 餓漢式:
    • 好處:線程安全。
    • 壞處:對象加載時間過長。
  • 懶漢式:
    • 好處:延遲對象的建立。
    • 壞處:線程不安全。

由于單例模式隻生成一個執行個體,減少了系統性能開銷,當一個對象的産生需要比較多的資源時,如讀取配置、産生其他依賴對象時,則可以通過在應用啟動時直接産生一個單例對象,然後永久駐留記憶體的方式來解決。

  • 網站的計數器,一般也是單例模式實作,否則難以同步。
  • 應用程式的日志應用,一般都使用單例模式實作,這一般是由于共享的日志檔案一直處于打開狀态,因為隻能有一個執行個體去操作,否則内容不好追加。
  • 資料庫連接配接池的設計一般也是采用單例模式,因為資料庫連接配接是一種資料庫資源。
  • 項目中,讀取配置檔案的類,一般也隻有一個對象。沒有必要每次使用配置檔案資料,都生成一個對象去讀取。
  • Application也是單例的典型應用。
  • Windows的TaskManager(任務管理器)就是很典型的單例模式。
  • Windows的RecycleBin(資源回收筒)也是典型的單例應用。在整個系統運作過程中,資源回收筒一直維護着僅有的一個執行個體。
  • 修飾類:此類不能被其他類繼承。
  • 修飾方法:此方法不能被重寫,如

    Object

    getClass()

  • 修飾屬性:此時的變量就是常量,考慮指派的位置有:顯示初始化、代碼塊中指派、構造器中初始化。
public class FinalTest {
    final int LEFT = 1;
    final int RIGHT;
    final int UP;

    {
        RIGHT = 2;
    }

    public FinalTest() {
        UP = 3;
    }
}
           
  • 修飾局部變量:尤其是使用

    final

    修飾形參時,表示形參是一個常量,可以在方法體内使用該形參,但不能進行重新指派。
public class FinalTest {
    @Test
    public void TestFinal() {
        int num = 10;
        new FinalTest().show(num);  // Final num = 10
    }

    public void show(final int num) {
        // num ++;  // 編譯報錯
        System.out.println("Final num = " + num);
    }
}
           
  • static final

    用來修飾全局常量,例如

    Math.PI

    的定義如下:
public static final double PI = 3.14159265358979323846;
           

abstract

修飾類,此類為抽象類:

  1. 此類不能執行個體化。
  2. 此類中有一定的構造器,便于子類執行個體化。
  3. 開發中,都會提供抽象類的子類,讓子類對象執行個體化,完成相關操作。

abstract

修飾方法,則為抽象方法:

  1. 抽象方法隻有方法的聲明,沒有方法體。
  2. 包含抽象方法的類,一定是一個抽象類,反之則不一定。
  3. 若子類重寫了父類的所有抽象方法,則該子類可以執行個體化;如果沒有,則此子類也需要用

    abstract

    聲明為抽象類。
import org.junit.Test;

public class AbstractTest {
    @Test
    public void testAbstract() {
        new Child("m", 20).info();  // Child: name = m, age = 20
        new Child().info();  // Child: name = null, age = 0
    }
}

// 抽象類
abstract class Human {
    String name;
    int age;

    public Human() {
    }

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 抽象方法
    public abstract void info();
}

class Child extends Human {
    public Child() {
        super();
    }

    public Child(String name, int age) {
        super(name, age);
    }

    @Override
    public void info() {
        System.out.println("Child: name = " + this.name + ", age = " + age);
    }
}

           

主要注意:

  1. abstract

    不能用來修飾:屬性、構造器等結構。
  2. abstract

    不能用來修飾私有方法、靜态方法、

    final

    的方法和類。

public class AbstractTest {
    @Test
    public void testAbstract() {
        // 可以直接用抽象父類Human的構造器(上面代碼中的Child不行)
        Human stu = new Human("M", 30) {
            @Override
            public void info() {
                System.out.println("Student: name = " + this.name + ", age = " + age);
            }
        };
        stu.info();  // Student: name = M, age = 30
    }
}
           

在Java中,類和接口是并列的概念。

Java的類不支援多重繼承,有了接口(interface),就能得到多重繼承的效果。接口的意義在于:

  • 有時需要從幾個類中抽取一些共同的行為特征,但他們有沒有is-a的關系,例如:滑鼠、鍵盤、手機都支援USB連接配接。
  • 接口定義的是一組規範,繼承是一個“是不是”的問題,接口實作則是一個“能不能”的問題。
  • 接口的本質是契約、标準、規範,實作後必須要遵守。

  • JDK7之前,隻能定義全局常量和抽象方法:
    • 全局常量:

      public static finanl

      (書寫時可以省略)。
    • 抽象方法:

      public abstract

  • JDK8中除了全局常量和抽象方法外,還能定義靜态方法、預設方法。
  • 接口不能定義構造器,這意味着接口不能執行個體化。
import org.junit.Test;

public class InterfaceTest {
    @Test
    public void testInterface() {
        System.out.println(Flyable.MIN_SPEED);  // 1
    }
}

interface Flyable {
    // 全局常量
    public static final int MAX_SPEED = 7900;
    int MIN_SPEED = 1;  // 省略了 public static final

    // 抽象方法
    public abstract void fly();
    void stop();  // 省略了 public abstract
}
           

接口通過讓類實作(

implements

)的方式來使用。

  • 如果實作類覆寫了接口中的所有抽象方法,則此實作類就可以執行個體化。
  • 如果沒有,則此類實作類仍為一個抽象類。
import org.junit.Test;

public class InterfaceTest {
    @Test
    public void testInterface() {
        new Plane().fly();  // Plane fly.
        new Plane().stop();  // Plane stop.
    }
}

class Plane implements Flyable {
    @Override
    public void fly() {
        System.out.println("Plane fly.");
    }

    @Override
    public void stop() {
        System.out.println("Plane stop.");
    }
}
           

Java通過實作多個接口彌補了類的單繼承性的局限性。

interface Attackable {
    void attack();
}

class Bullet implements Flyable, Attackable {
    @Override
    public void fly() {
    }

    @Override
    public void stop() {
    }

    @Override
    public void attack() {
    }
}
           

接口可以多繼承接口:

interface AA {
    void methodA();
}

interface BB {
    void methodB();
}

interface CC extends AA, BB {
    void methodC();
}

class DD implements CC {
    @Override
    public void methodA() {
    }

    @Override
    public void methodB() {
    }

    @Override
    public void methodC() {
    }
}
           

接口中定義的靜态方法,隻能通過接口來調用,代碼參考下一小節。

預設方法使用

default

關鍵字修飾,通過實作類對象來調用。JDK8 API中對

Collection

List

Comparator

等接口提供了豐富的預設方法。

  • 如果實作類重寫了接口的預設方法, 調用時調用的是重寫後的方法。
import org.junit.Test;

public class InterfaceTest2 {
    @Test
    public void testInterface2() {
        CompareA.info();  // CompareA interface.
        // SubClass.info():  // 編譯報錯
        // new SubClass().info();  // 編譯報錯

        new SubClass().method1();  // CompareA method1.
        new SubClass().method2();  // CompareA method2.  SubClass method2.
    }
}

interface CompareA {
    public static void info() {
        System.out.println("CompareA interface.");
    }

    public default void method1() {
        System.out.println("CompareA method1.");
    }

    public default void method2() {
        System.out.println("CompareA method2.");
    }
}

class SubClass implements CompareA {
    @Override
    public void method2() {
        CompareA.super.method2();  // 調用父類的方法
        System.out.println("SubClass method2.");
    }
}
           
  • 如果實作類(子類)繼承的父類和實作的接口中聲明了同名同參數的方法,在實作類(子類)沒有重寫此方法時,預設調用的是父類中同名同參數的方法。
  • 如果實作類實作了多個接口,而這多個接口定義了同名同參數的預設方法,當在實作類沒有重寫此方法時,會報接口沖突的錯誤,是以此時實作類就必須重寫此方法。
  • 調用父類方法,參考上述代碼。
  • 成員内部類:靜态、非靜态。
  • 局部内部類:方法内、代碼塊内、構造器内。

  • 可以調用外部類的結構。
  • 可以被

    static

  • 可以被4中不同的權限修飾。
  • 類内可以定義屬性、方法。
  • final

    修飾,表示此類不能被繼承。
  • abstract