本章節目標:
理解為什么要進行封裝?封裝有什么好處?封裝的代碼怎么實現?
Java封裝
封裝是面向對象的三大特征之一,什么是封裝?封裝有什么好處?怎么封裝,代碼怎么寫?這是大家這一章節要學習的內容。
封裝從字面上來理解就是包裝的意思,專業點就是信息隱藏,是指利用抽象數據類型將數據和基于數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。系統的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互。也就是說用戶是無需知道對象內部的細節,但可以通過該對象對外提供的接口來訪問該對象。
在現實世界當中我們可以看到很多事物都是封裝好的,比如“鼠標”,外部有一個殼,將內部的原件封裝起來,至于鼠標內部的細節是什么,我們不需要關心,只需要知道鼠標對外提供了左鍵、右鍵、滾動滑輪這三個簡單的操作。對于用戶來說只要知道左鍵、右鍵、滾動滑輪都能完成什么功能就行了。為什么鼠標內部的原件要在外部包裝一個“殼”呢,起碼內部的原件是安全的,不是嗎。再如“數碼相機”,外部也有一個殼,將內部復雜的結構包裝起來,對外提供簡單的按鍵,這樣每個人都可以很快的學會照相了,因為它的按鍵很簡單,另外照相機內部精密的原件也受到了殼兒的保護,不容易壞掉。
根據以上的描述,可以得出封裝有什么好處呢?封裝之后就形成了獨立實體,獨立實體可以在不同的環境中重復使用,顯然封裝可以降低程序的耦合度,提高程序的擴展性,以及重用性或復用性,例如“鼠標”可以在A電腦上使用,也可以在B電腦上使用。另外封裝可以隱藏內部實現細節,站在對象外部是看不到內部復雜結構的,對外只提供了簡單的安全的操作入口,所以封裝之后,實體更安全了。
我們來看一段代碼,在不進行封裝的前提下,存在什么問題:
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);
}
}
運行結果如下圖所示:
圖10-1:未進行封裝的程序測試
以上程序MobilePhone類未進行封裝,其中的電壓屬性voltage對外暴露,在外部程序當中可以對MobilePhone對象的電壓voltage屬性進行隨意訪問,導致了它的不安全,例如手機的正常電壓是3~5V,但是以上程序已經將手機電壓設置為100V,這個時候顯然是要出問題的,但這個程序編譯以及運行仍然是正常的,沒有出現任何問題,這是不對的。
為了保證內部數據的安全,這個時候就需要進行封裝了,封裝的第一步就是將應該隱藏的數據隱藏起來,起碼在外部是無法隨意訪問這些數據的,怎么隱藏呢?我們可以使用java語言中的private修飾符,private修飾的數據表示私有的,私有的數據只能在本類當中訪問。請看程序:
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修飾的數據無法在外部程序中直接訪問
通過以上的測試,手機對象的電壓屬性確實受到了保護,在外部程序中無法訪問了。但從當前情況來看,voltage屬性有點兒太安全了,一個對象的屬性無法被外部程序訪問,自然這個數據就沒有存在的價值了。所以這個時候就需要進入封裝的第二步了:對外提供公開的訪問入口,讓外部程序統一通過這個入口去訪問數據,我們可以在這個入口處設立關卡,進行安全控制,這樣對象內部的數據就安全了。
對于“一個”屬性來說,我們對外應該提供幾個訪問入口呢?通常情況下我們訪問對象的某個屬性,不外乎讀?。╣et)和修改(set),所以對外提供的訪問入口應該有兩個,這兩個方法通常被稱為set方法和get方法(請注意:set和get方法訪問的都是某個具體對象的屬性,不同的對象調用get方法獲取的屬性值不同,所以set和get方法必須有對象的存在才能調用,這樣的方法定義的時候不能使用static關鍵字修飾,被稱為實例方法。實例方法必須使用“引用”的方式調用。還記得之前我們接觸的方法都是被static修飾的,這些方法直接采用“類名”的方式調用,而不需要創建對象,在這里顯然是不行的)。請看以下代碼:
public class MobilePhone {
//電壓:手機正常電壓在3~5V
private double voltage;
public MobilePhone(){
}
public void setVoltage(double _voltage){
if(_voltage < 3 || _voltage > 5){
//當電壓低于3V或者高于5V時拋出異常,程序則終止
throw new RuntimeException("電壓非法,請愛護手機!");
}
//程序如果能執行到此處說明以上并沒有發生異常,電壓值合法
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());
}
}
運行結果如下圖所示:
圖10-3:對封裝之后的測試
通過以上程序,可以看出MobilePhone的voltage屬性不能在外部程序中隨意訪問了,只能調用MobilePhone的setVoltage()方法來修改電壓,調用getVoltage()方法來讀取電壓,在setVoltage()方法中編寫了安全控制代碼,當電壓低于3V,或者高于5V的時候,程序拋出了異常,不允許修改電壓值,程序結束了。只有合法的時候,才允許程序修改電壓值。(異常機制在后續的內容中會學到,不要著急。)
總之,在java語言中封裝的步驟應該是這樣的:需要被保護的屬性使用private進行修飾,給這個私有的屬性對外提供公開的set和get方法,其中set方法用來修改屬性的值,get方法用來讀取屬性的值。并且set和get方法在命名上也是有規范的,規范中要求set方法名是set + 屬性名(屬性名首字母大寫),get方法名是get + 屬性名(屬性名首字母大寫)。其中set方法有一個參數,用來給屬性賦值,set方法沒有返回值,一般在set方法內部編寫安全控制程序,因為畢竟set方法是修改內部數據的,而get方法不需要參數,返回值類型是該屬性所屬類型(先記住,以后講:另外set方法和get方法都不帶static關鍵字,不帶static關鍵字的方法稱為實例方法,這些方法調用的時候需要先創建對象,然后通過“引用”去調用這些方法,實例方法不能直接采用“類名”的方式調用。),例如以下代碼:
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());
}
}
運行結果如下圖所示:
圖10-4:set和get方法測試
有的讀者可能會有這樣的疑問:構造方法中已經給屬性賦值了,為什么還要提供set方法呢?注意了同學們,這是兩個完全不同的時刻,構造方法中給屬性賦值是在創建對象的時候完成的,當對象創建完畢之后,屬性可能還是會被修改的,后期要想修改屬性的值,這個時候就必須調用set方法了。