Java 封裝
上一小節(jié)中,我們已經(jīng)對(duì)類和對(duì)象有了一個(gè)基本的認(rèn)識(shí)。不止于 Java,在各個(gè)面向?qū)ο笳Z言的書籍資料中,都會(huì)提到面向?qū)ο蟮娜筇卣鳎?strong>封裝、繼承、多態(tài)。本小節(jié)我們就從封裝開始,探討面向?qū)ο蟮奶卣鳌1拘」?jié)我們將學(xué)習(xí)什么是封裝、為什么需要封裝,最后也會(huì)以一個(gè) NBA 球員類的案例來實(shí)現(xiàn)封裝。
1. 概念和特點(diǎn)
類的基本作用就是封裝代碼。封裝將類的一些特征和行為隱藏在類內(nèi)部,不允許類外部直接訪問。
封裝可以被認(rèn)為是一個(gè)保護(hù)屏障,防止該類的代碼和數(shù)據(jù)被外部類定義的代碼隨機(jī)訪問。
我們可以通過類提供的方法來實(shí)現(xiàn)對(duì)隱藏信息的操作和訪問。隱藏了對(duì)象的信息,留出了訪問的接口。
在我們?nèi)粘I钪校庋b與我們息息相關(guān),智能手機(jī)就是一個(gè)擁有良好封裝的例子,我們不需要關(guān)心其內(nèi)部復(fù)雜的邏輯電路設(shè)計(jì),可以通過手機(jī)的屏幕、按鍵、充電口、耳機(jī)接口等等外部接口來對(duì)手機(jī)進(jìn)行操作和使用。復(fù)雜的邏輯電路以及模塊被封裝在手機(jī)的內(nèi)部,而留出的這些必要接口,讓我們更加簡(jiǎn)便地使用手機(jī)的同時(shí)也保護(hù)了手機(jī)的內(nèi)部細(xì)節(jié)。
封裝有兩個(gè)特點(diǎn):
- 只能通過規(guī)定的方法訪問數(shù)據(jù);
- 隱藏類的實(shí)例細(xì)節(jié),方便修改和實(shí)現(xiàn)。
2. 為什么需要封裝
封裝具有以下優(yōu)點(diǎn):
- 封裝有利于提高類的內(nèi)聚性,適當(dāng)?shù)姆庋b可以讓代碼更容易理解與維護(hù);
- 良好的封裝有利于降低代碼的耦合度;
- 一些關(guān)鍵屬性只允許類內(nèi)部可以訪問和修改,增強(qiáng)類的安全性;
- 隱藏實(shí)現(xiàn)細(xì)節(jié),為調(diào)用方提供易于理解的接口;
- 當(dāng)需求發(fā)生變動(dòng)時(shí),我們只需要修改我們封裝的代碼,而不需要到處修改調(diào)用處的代碼。
3. 實(shí)現(xiàn)封裝
在 Java 語言中,如何實(shí)現(xiàn)封裝呢?需要 3 個(gè)步驟。
- 修改屬性的可見性為
private
; - 創(chuàng)建公開的 getter 和 setter 方法,分別用于屬性的讀寫;
- 在 getter 和 setter 方法中,對(duì)屬性的合法性進(jìn)行判斷。
我們來看一個(gè) NBA 球員類NBAPlayer
:
class NBAPlayer {
// 姓名
String name;
// 年齡
int age;
}
在類內(nèi)部(即類名后面{}
之間的區(qū)域)定義了成員屬性name
和age
,我們知道,在類外部調(diào)用處可以對(duì)其屬性進(jìn)行修改:
NBAPlayer player = new NBAPlayer();
player.age = -1;
如下是實(shí)例代碼:
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);
}
}
運(yùn)行結(jié)果:
球員年齡為:-1
我們通過對(duì)象名.屬性名的方式對(duì)age
賦值為 -1
,顯然,球員的年齡為-1
是反常理的。
下面我們對(duì)NBAPlayer
類進(jìn)行封裝。
- 我們可以使用私有化訪問控制符修飾類內(nèi)部的屬性,讓其只在類的內(nèi)部可以訪問:
// 用private修飾成員屬性,限定只能在當(dāng)前類內(nèi)部可以訪問
private String name;
private int age;
private
關(guān)鍵字限定了其修飾的成員只能在類內(nèi)部訪問,這樣之后就無法在類外部使用player.age =-1
這樣的賦值方式進(jìn)行賦值了。
- 創(chuàng)建公開的(public)
getter
和setter
方法:
// 通常以get+屬性名的方式命名 getter,返回對(duì)應(yīng)的私有屬性
public String getName() {
return name;
}
// 通常以set+屬性名的方式命名 setter,給對(duì)應(yīng)屬性進(jìn)行賦值
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
顧名思義,getter
就是取屬性值,setter
就是給屬性賦值,這樣在類的外部就可以通過調(diào)用其方法對(duì)屬性進(jìn)行操作了。
- 對(duì)屬性進(jìn)行邏輯判斷,以
age
屬性的setter
方法為例:
public void setAge(int age) {
// 判斷參數(shù)age的合法性
if(age < 0) {
this.age = 0;
} else {
this.age = age;
}
}
在setAge
方法中,我們將參數(shù)age
小于 0 的情況進(jìn)行了處理,如果小于 0,直接將age
賦值為0。除了給默認(rèn)值的方式,我們也可以拋出異常,提示調(diào)用方傳參不合法。
在類外部對(duì)屬性進(jìn)行讀寫:
NBAPlayer player = new NBAPlayer();
// 對(duì)屬性賦值:
player.setName("詹姆斯");
player.setAge(35);
// 獲取屬性:
System.out.println("姓名:" + player.getName());
System.out.println("年齡:" + player.getAge());
試想,如果在類外部,有很多地方都會(huì)操作屬性值,當(dāng)屬性值讀寫邏輯發(fā)生改變時(shí),我們只需修改類內(nèi)部的邏輯。
另外,對(duì)于有參構(gòu)造方法中,對(duì)屬性賦值時(shí),直接調(diào)用其setter
方法。無需再寫重復(fù)的邏輯判斷,提高代碼復(fù)用性:
public NBAPlayer(int age) {
this.setAge(age);
}
如下是實(shí)現(xiàn)封裝后完整實(shí)例代碼:
public class NBAPlayer {
// 姓名
private String name;
// 年齡
private int age;
// 無參構(gòu)造方法
public NBAPlayer() {
}
// 單參構(gòu)造方法
public NBAPlayer(int age) {
this.setAge(age);
}
// 全參構(gòu)造方法
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) {
// 判斷參數(shù)age的合法性
if(age < 0) {
this.age = 0;
}
this.age = age;
}
public static void main(String[] args) {
NBAPlayer james = new NBAPlayer();
// 對(duì)屬性賦值:
james.setName("詹姆斯");
james.setAge(35);
// 打印james實(shí)例屬性
System.out.println("姓名:" + james.getName());
System.out.println("年齡:" + james.getAge());
System.out.println("-------------");
// 實(shí)例化一個(gè)新的對(duì)象
NBAPlayer jordan = new NBAPlayer("喬丹", 60);
// 打印jordan對(duì)象實(shí)例屬性
System.out.println("姓名:" + jordan.getName());
System.out.println("年齡:" + jordan.getAge());
}
}
運(yùn)行結(jié)果:
姓名:詹姆斯
年齡:35
-------------
姓名:?jiǎn)痰?年齡:60
4. 小結(jié)
面向?qū)ο蟮娜筇卣鳎?strong>封裝、繼承、多態(tài)。
封裝隱藏了對(duì)象的信息,并且留出了訪問的接口。