Java 封裝
上一小節中,我們已經對類和對象有了一個基本的認識。不止于 Java,在各個面向對象語言的書籍資料中,都會提到面向對象的三大特征:封裝、繼承、多態。本小節我們就從封裝開始,探討面向對象的特征。本小節我們將學習什么是封裝、為什么需要封裝,最后也會以一個 NBA 球員類的案例來實現封裝。
1. 概念和特點
類的基本作用就是封裝代碼。封裝將類的一些特征和行為隱藏在類內部,不允許類外部直接訪問。
封裝可以被認為是一個保護屏障,防止該類的代碼和數據被外部類定義的代碼隨機訪問。
我們可以通過類提供的方法來實現對隱藏信息的操作和訪問。隱藏了對象的信息,留出了訪問的接口。
在我們日常生活中,封裝與我們息息相關,智能手機就是一個擁有良好封裝的例子,我們不需要關心其內部復雜的邏輯電路設計,可以通過手機的屏幕、按鍵、充電口、耳機接口等等外部接口來對手機進行操作和使用。復雜的邏輯電路以及模塊被封裝在手機的內部,而留出的這些必要接口,讓我們更加簡便地使用手機的同時也保護了手機的內部細節。
封裝有兩個特點:
- 只能通過規定的方法訪問數據;
- 隱藏類的實例細節,方便修改和實現。
2. 為什么需要封裝
封裝具有以下優點:
- 封裝有利于提高類的內聚性,適當的封裝可以讓代碼更容易理解與維護;
- 良好的封裝有利于降低代碼的耦合度;
- 一些關鍵屬性只允許類內部可以訪問和修改,增強類的安全性;
- 隱藏實現細節,為調用方提供易于理解的接口;
- 當需求發生變動時,我們只需要修改我們封裝的代碼,而不需要到處修改調用處的代碼。
3. 實現封裝
在 Java 語言中,如何實現封裝呢?需要 3 個步驟。
- 修改屬性的可見性為
private
; - 創建公開的 getter 和 setter 方法,分別用于屬性的讀寫;
- 在 getter 和 setter 方法中,對屬性的合法性進行判斷。
我們來看一個 NBA 球員類NBAPlayer
:
class NBAPlayer {
// 姓名
String name;
// 年齡
int age;
}
在類內部(即類名后面{}
之間的區域)定義了成員屬性name
和age
,我們知道,在類外部調用處可以對其屬性進行修改:
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
類進行封裝。
- 我們可以使用私有化訪問控制符修飾類內部的屬性,讓其只在類的內部可以訪問:
// 用private修飾成員屬性,限定只能在當前類內部可以訪問
private String name;
private int age;
private
關鍵字限定了其修飾的成員只能在類內部訪問,這樣之后就無法在類外部使用player.age =-1
這樣的賦值方式進行賦值了。
- 創建公開的(public)
getter
和setter
方法:
// 通常以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
就是給屬性賦值,這樣在類的外部就可以通過調用其方法對屬性進行操作了。
- 對屬性進行邏輯判斷,以
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. 小結
面向對象的三大特征:封裝、繼承、多態。
封裝隱藏了對象的信息,并且留出了訪問的接口。