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