亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Java 封裝

上一小節中,我們已經對類和對象有了一個基本的認識。不止于 Java,在各個面向對象語言的書籍資料中,都會提到面向對象的三大特征:封裝、繼承、多態。本小節我們就從封裝開始,探討面向對象的特征。本小節我們將學習什么是封裝、為什么需要封裝,最后也會以一個 NBA 球員類的案例來實現封裝。

1. 概念和特點

類的基本作用就是封裝代碼。封裝將類的一些特征和行為隱藏在類內部,不允許類外部直接訪問。

封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。

我們可以通過類提供的方法來實現對隱藏信息的操作和訪問。隱藏了對象的信息,留出了訪問的接口。

在我們日常生活中,封裝與我們息息相關,智能手機就是一個擁有良好封裝的例子,我們不需要關心其內部復雜的邏輯電路設計,可以通過手機的屏幕、按鍵、充電口、耳機接口等等外部接口來對手機進行操作和使用。復雜的邏輯電路以及模塊被封裝在手機的內部,而留出的這些必要接口,讓我們更加簡便地使用手機的同時也保護了手機的內部細節。

封裝有兩個特點:

  1. 只能通過規定的方法訪問數據;
  2. 隱藏類的實例細節,方便修改和實現。

2. 為什么需要封裝

封裝具有以下優點:

  • 封裝有利于提高類的內聚性,適當的封裝可以讓代碼更容易理解與維護;
  • 良好的封裝有利于降低代碼的耦合度;
  • 一些關鍵屬性只允許類內部可以訪問和修改,增強類的安全性;
  • 隱藏實現細節,為調用方提供易于理解的接口;
  • 當需求發生變動時,我們只需要修改我們封裝的代碼,而不需要到處修改調用處的代碼。

3. 實現封裝

在 Java 語言中,如何實現封裝呢?需要 3 個步驟。

  1. 修改屬性的可見性為private;
  2. 創建公開的 getter 和 setter 方法,分別用于屬性的讀寫;
  3. 在 getter 和 setter 方法中,對屬性的合法性進行判斷。

我們來看一個 NBA 球員類NBAPlayer

class NBAPlayer {
    // 姓名
    String name;
    // 年齡
    int age;
}

類內部(即類名后面{}之間的區域)定義了成員屬性nameage,我們知道,在類外部調用處可以對其屬性進行修改:

NBAPlayer player = new NBAPlayer();
player.age = -1;

如下是實例代碼:

實例演示
預覽 復制
復制成功!
public class NBAPlayer {
    // 姓名
    String name;
    // 年齡
    int age;

    public static void main(String[] args) {
        NBAPlayer player = new NBAPlayer();
        player.age = -1;
        System.out.println("球員年齡為:" + player.age);
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結果:

球員年齡為:-1

我們通過對象名.屬性名的方式對age賦值為 -1,顯然,球員的年齡為-1是反常理的。

下面我們對NBAPlayer類進行封裝。

  1. 我們可以使用私有化訪問控制符修飾類內部的屬性,讓其只在類的內部可以訪問:
// 用private修飾成員屬性,限定只能在當前類內部可以訪問
private String name;
private int age;

private關鍵字限定了其修飾的成員只能在類內部訪問,這樣之后就無法在類外部使用player.age =-1這樣的賦值方式進行賦值了。

  1. 創建公開的(public) gettersetter方法:
// 通常以get+屬性名的方式命名 getter,返回對應的私有屬性
public String getName() {
  	return name;
}

// 通常以set+屬性名的方式命名 setter,給對應屬性進行賦值
public void setName(String name) {
  	this.name = name;
}

public int getAge() {
  	return age;
}

public void setAge(int age) {
  	this.age = age;
}

顧名思義,getter就是取屬性值,setter就是給屬性賦值,這樣在類的外部就可以通過調用其方法對屬性進行操作了。

  1. 對屬性進行邏輯判斷,以age屬性的setter方法為例:
public void setAge(int age) {
  	// 判斷參數age的合法性
  	if(age < 0) {
      	this.age = 0;
    } else {
	  	this.age = age;
  	}
}

setAge方法中,我們將參數age小于 0 的情況進行了處理,如果小于 0,直接將age賦值為0。除了給默認值的方式,我們也可以拋出異常,提示調用方傳參不合法。

在類外部對屬性進行讀寫:

NBAPlayer player = new NBAPlayer();
// 對屬性賦值:
player.setName("詹姆斯");
player.setAge(35);
// 獲取屬性:
System.out.println("姓名:" + player.getName());
System.out.println("年齡:" + player.getAge());

試想,如果在類外部,有很多地方都會操作屬性值,當屬性值讀寫邏輯發生改變時,我們只需修改類內部的邏輯。

另外,對于有參構造方法中,對屬性賦值時,直接調用其setter方法。無需再寫重復的邏輯判斷,提高代碼復用性:

public NBAPlayer(int age) {
  	this.setAge(age);
}

如下是實現封裝后完整實例代碼:

實例演示
預覽 復制
復制成功!
public class NBAPlayer {
    // 姓名
    private String name;
    // 年齡
    private int age;

    // 無參構造方法
    public NBAPlayer() {

    }

    // 單參構造方法
    public NBAPlayer(int age) {
        this.setAge(age);
    }

    // 全參構造方法
    public NBAPlayer(String name, int age) {
        this.setName(name);
        this.setAge(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        // 判斷參數age的合法性
        if(age < 0) {
            this.age = 0;
        }
        this.age = age;
    }

    public static void main(String[] args) {
        NBAPlayer james = new NBAPlayer();
        // 對屬性賦值:
        james.setName("詹姆斯");
        james.setAge(35);
        // 打印james實例屬性
        System.out.println("姓名:" + james.getName());
        System.out.println("年齡:" + james.getAge());
        System.out.println("-------------");
        // 實例化一個新的對象
        NBAPlayer jordan = new NBAPlayer("喬丹", 60);
        // 打印jordan對象實例屬性
        System.out.println("姓名:" + jordan.getName());
        System.out.println("年齡:" + jordan.getAge());
    }
}
運行案例 點擊 "運行案例" 可查看在線運行效果

運行結果:

姓名:詹姆斯
年齡:35
-------------
姓名:喬丹
年齡:60

4. 小結

面向對象的三大特征:封裝、繼承、多態

封裝隱藏了對象的信息,并且留出了訪問的接口。