本章節(jié)目標(biāo):
理解為什么要進行封裝?封裝有什么好處?封裝的代碼怎么實現(xiàn)?
Java封裝
封裝是面向?qū)ο蟮娜筇卣髦唬裁词欠庋b?封裝有什么好處?怎么封裝,代碼怎么寫?這是大家這一章節(jié)要學(xué)習(xí)的內(nèi)容。
封裝從字面上來理解就是包裝的意思,專業(yè)點就是信息隱藏,是指利用抽象數(shù)據(jù)類型將數(shù)據(jù)和基于數(shù)據(jù)的操作封裝在一起,使其構(gòu)成一個不可分割的獨立實體,數(shù)據(jù)被保護在抽象數(shù)據(jù)類型的內(nèi)部,盡可能地隱藏內(nèi)部的細(xì)節(jié),只保留一些對外接口使之與外部發(fā)生聯(lián)系。系統(tǒng)的其他對象只能通過包裹在數(shù)據(jù)外面的已經(jīng)授權(quán)的操作來與這個封裝的對象進行交流和交互。也就是說用戶是無需知道對象內(nèi)部的細(xì)節(jié),但可以通過該對象對外提供的接口來訪問該對象。
在現(xiàn)實世界當(dāng)中我們可以看到很多事物都是封裝好的,比如“鼠標(biāo)”,外部有一個殼,將內(nèi)部的原件封裝起來,至于鼠標(biāo)內(nèi)部的細(xì)節(jié)是什么,我們不需要關(guān)心,只需要知道鼠標(biāo)對外提供了左鍵、右鍵、滾動滑輪這三個簡單的操作。對于用戶來說只要知道左鍵、右鍵、滾動滑輪都能完成什么功能就行了。為什么鼠標(biāo)內(nèi)部的原件要在外部包裝一個“殼”呢,起碼內(nèi)部的原件是安全的,不是嗎。再如“數(shù)碼相機”,外部也有一個殼,將內(nèi)部復(fù)雜的結(jié)構(gòu)包裝起來,對外提供簡單的按鍵,這樣每個人都可以很快的學(xué)會照相了,因為它的按鍵很簡單,另外照相機內(nèi)部精密的原件也受到了殼兒的保護,不容易壞掉。
根據(jù)以上的描述,可以得出封裝有什么好處呢?封裝之后就形成了獨立實體,獨立實體可以在不同的環(huán)境中重復(fù)使用,顯然封裝可以降低程序的耦合度,提高程序的擴展性,以及重用性或復(fù)用性,例如“鼠標(biāo)”可以在A電腦上使用,也可以在B電腦上使用。另外封裝可以隱藏內(nèi)部實現(xiàn)細(xì)節(jié),站在對象外部是看不到內(nèi)部復(fù)雜結(jié)構(gòu)的,對外只提供了簡單的安全的操作入口,所以封裝之后,實體更安全了。
我們來看一段代碼,在不進行封裝的前提下,存在什么問題:
public class MobilePhone {
//電壓:手機正常電壓在3~5V
double voltage;
}
public class MobilePhoneTest {
public static void main(String[] args) {
MobilePhone phone = new MobilePhone();
phone.voltage = 3.7;
System.out.println("手機電壓 = " + phone.voltage);
phone.voltage = 100;
System.out.println("手機電壓 = " + phone.voltage);
}
}
運行結(jié)果如下圖所示:
圖10-1:未進行封裝的程序測試
以上程序MobilePhone類未進行封裝,其中的電壓屬性voltage對外暴露,在外部程序當(dāng)中可以對MobilePhone對象的電壓voltage屬性進行隨意訪問,導(dǎo)致了它的不安全,例如手機的正常電壓是3~5V,但是以上程序已經(jīng)將手機電壓設(shè)置為100V,這個時候顯然是要出問題的,但這個程序編譯以及運行仍然是正常的,沒有出現(xiàn)任何問題,這是不對的。
為了保證內(nèi)部數(shù)據(jù)的安全,這個時候就需要進行封裝了,封裝的第一步就是將應(yīng)該隱藏的數(shù)據(jù)隱藏起來,起碼在外部是無法隨意訪問這些數(shù)據(jù)的,怎么隱藏呢?我們可以使用java語言中的private修飾符,private修飾的數(shù)據(jù)表示私有的,私有的數(shù)據(jù)只能在本類當(dāng)中訪問。請看程序:
public class MobilePhone {
//電壓:手機正常電壓在3~5V
private double voltage;
}
public class MobilePhoneTest {
public static void main(String[] args) {
MobilePhone phone = new MobilePhone();
phone.voltage = 3.7;
System.out.println("手機電壓 = " + phone.voltage);
phone.voltage = 100;
System.out.println("手機電壓 = " + phone.voltage);
}
}
以上程序編譯報錯了,請看下圖:
圖10-2:private修飾的數(shù)據(jù)無法在外部程序中直接訪問
通過以上的測試,手機對象的電壓屬性確實受到了保護,在外部程序中無法訪問了。但從當(dāng)前情況來看,voltage屬性有點兒太安全了,一個對象的屬性無法被外部程序訪問,自然這個數(shù)據(jù)就沒有存在的價值了。所以這個時候就需要進入封裝的第二步了:對外提供公開的訪問入口,讓外部程序統(tǒng)一通過這個入口去訪問數(shù)據(jù),我們可以在這個入口處設(shè)立關(guān)卡,進行安全控制,這樣對象內(nèi)部的數(shù)據(jù)就安全了。
對于“一個”屬性來說,我們對外應(yīng)該提供幾個訪問入口呢?通常情況下我們訪問對象的某個屬性,不外乎讀取(get)和修改(set),所以對外提供的訪問入口應(yīng)該有兩個,這兩個方法通常被稱為set方法和get方法(請注意:set和get方法訪問的都是某個具體對象的屬性,不同的對象調(diào)用get方法獲取的屬性值不同,所以set和get方法必須有對象的存在才能調(diào)用,這樣的方法定義的時候不能使用static關(guān)鍵字修飾,被稱為實例方法。實例方法必須使用“引用”的方式調(diào)用。還記得之前我們接觸的方法都是被static修飾的,這些方法直接采用“類名”的方式調(diào)用,而不需要創(chuàng)建對象,在這里顯然是不行的)。請看以下代碼:
public class MobilePhone {
//電壓:手機正常電壓在3~5V
private double voltage;
public MobilePhone(){
}
public void setVoltage(double _voltage){
if(_voltage < 3 || _voltage > 5){
//當(dāng)電壓低于3V或者高于5V時拋出異常,程序則終止
throw new RuntimeException("電壓非法,請愛護手機!");
}
//程序如果能執(zhí)行到此處說明以上并沒有發(fā)生異常,電壓值合法
voltage = _voltage;
}
public double getVoltage(){
return voltage;
}
}
public class MobilePhoneTest {
public static void main(String[] args) {
MobilePhone phone = new MobilePhone();
phone.setVoltage(3.7);
System.out.println("手機電壓 :" + phone.getVoltage());
phone.setVoltage(100);
System.out.println("手機電壓 :" + phone.getVoltage());
}
}
運行結(jié)果如下圖所示:
圖10-3:對封裝之后的測試
通過以上程序,可以看出MobilePhone的voltage屬性不能在外部程序中隨意訪問了,只能調(diào)用MobilePhone的setVoltage()方法來修改電壓,調(diào)用getVoltage()方法來讀取電壓,在setVoltage()方法中編寫了安全控制代碼,當(dāng)電壓低于3V,或者高于5V的時候,程序拋出了異常,不允許修改電壓值,程序結(jié)束了。只有合法的時候,才允許程序修改電壓值。(異常機制在后續(xù)的內(nèi)容中會學(xué)到,不要著急。)
總之,在java語言中封裝的步驟應(yīng)該是這樣的:需要被保護的屬性使用private進行修飾,給這個私有的屬性對外提供公開的set和get方法,其中set方法用來修改屬性的值,get方法用來讀取屬性的值。并且set和get方法在命名上也是有規(guī)范的,規(guī)范中要求set方法名是set + 屬性名(屬性名首字母大寫),get方法名是get + 屬性名(屬性名首字母大寫)。其中set方法有一個參數(shù),用來給屬性賦值,set方法沒有返回值,一般在set方法內(nèi)部編寫安全控制程序,因為畢竟set方法是修改內(nèi)部數(shù)據(jù)的,而get方法不需要參數(shù),返回值類型是該屬性所屬類型(先記住,以后講:另外set方法和get方法都不帶static關(guān)鍵字,不帶static關(guān)鍵字的方法稱為實例方法,這些方法調(diào)用的時候需要先創(chuàng)建對象,然后通過“引用”去調(diào)用這些方法,實例方法不能直接采用“類名”的方式調(diào)用。),例如以下代碼:
public class Product {
private int no;
private String name;
private double price;
public Product(){
}
public Product(int _no , String _name , double _price){
no = _no;
name = _name;
price = _price;
}
public int getNo() {
return no;
}
public void setNo(int _no) {
no = _no;
}
public String getName() {
return name;
}
public void setName(String _name) {
name = _name;
}
public double getPrice() {
return price;
}
public void setPrice(double _price) {
price = _price;
}
}
public class ProductTest {
public static void main(String[] args) {
Product p1 = new Product(10000 , "小米5S" , 2000.0);
System.out.println("商品編號:" + p1.getNo());
System.out.println("商品名稱:" + p1.getName());
System.out.println("商品單價:" + p1.getPrice());
p1.setNo(70000);
p1.setName("小米6");
p1.setPrice(2100.0);
System.out.println("商品編號:" + p1.getNo());
System.out.println("商品名稱:" + p1.getName());
System.out.println("商品單價:" + p1.getPrice());
}
}
運行結(jié)果如下圖所示:
圖10-4:set和get方法測試
有的讀者可能會有這樣的疑問:構(gòu)造方法中已經(jīng)給屬性賦值了,為什么還要提供set方法呢?注意了同學(xué)們,這是兩個完全不同的時刻,構(gòu)造方法中給屬性賦值是在創(chuàng)建對象的時候完成的,當(dāng)對象創(chuàng)建完畢之后,屬性可能還是會被修改的,后期要想修改屬性的值,這個時候就必須調(diào)用set方法了。