一文詳解Java中的封裝是什麼?怎麼實現封裝?
封裝是面向物件的三大特徵之一,什麼是封裝?封裝有什麼好處?怎麼封裝,程式碼怎麼寫?這是這篇文章學習的內容。
什麼是封裝?
封裝從字面上來理解就是包裝的意思,專業點就是資訊隱藏,是指利用抽象資料型別將資料和基於資料的操作封裝在一起,使其構成一個不可分割的獨立實體,資料被保護在抽象資料型別的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯絡。系統的其他物件只能通過包裹在資料外面的已經授權的操作來與這個封裝的物件進行交流和互動。也就是說使用者是無需知道物件內部的細節,但可以通過該物件對外提供的介面來訪問該物件。
在現實世界當中我們可以看到很多事物都是封裝好的,比如“滑鼠”,外部有一個殼,將內部的原件封裝起來,至於滑鼠內部的細節是什麼,我們不需要關心,只需要知道滑鼠對外提供了左鍵、右鍵、滾動滑輪這三個簡單的操作。對於使用者來說只要知道左鍵、右鍵、滾動滑輪都能完成什麼功能就行了。為什麼滑鼠內部的原件要在外部包裝一個“殼”呢,起碼內部的原件是安全的,不是嗎。
再如“數碼相機”,外部也有一個殼,將內部複雜的結構包裝起來,對外提供簡單的按鍵,這樣每個人都可以很快的學會照相了,因為它的按鍵很簡單,另外照相機內部精密的原件也受到了殼兒的保護,不容易壞掉。
面向物件的三個特性:1、封裝
2、繼承
3、多型
封裝之後就形成了獨立實體,獨立實體可以在不同的環境中重複使用,顯然封裝可以降低程式的耦合度,提高程式的擴充套件性,以及重用性或複用性,例如“滑鼠”可以在A電腦上使用,也可以在B電腦上使用。
另外封裝可以隱藏內部實現細節,站在物件外部是看不到內部複雜結構的,對外只提供了簡單的安全的操作入口,所以封裝之後,實體更安全了。
總體來說,好處就是以下幾點:1、提高了安全性
2、提高了複用性
3、隱藏了實現細節
-
第一步:屬性私有化
-
第二步:1個屬性對外提供兩個set和get方法。外部程式只能通過set方法修改,只能通過get方法讀取,可以在set方法中設立關卡來保證資料的安全性。
在強調一下: set和get方法都是例項方法,不能帶static。
不帶static的方法稱為例項方法,例項方法的呼叫必須先new物件。
set和get方法寫的時候有嚴格的規範要求:(大家要按照規矩來)
public void set+屬性名首字母大寫(1個引數){ xxx = 1個引數; } get方法長這個樣子: public 返回值型別 get+屬性名首字母大寫(無參){ return xxx; }
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); } }
執行結果如下圖所示:
圖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); } }
以上程式編譯報錯了,請看下圖:
圖2:private修飾的資料無法在外部程式中直接訪問
通過以上的測試,手機物件的電壓屬性確實受到了保護,在外部程式中無法訪問了。但從當前情況來看,voltage屬性有點兒太安全了,一個物件的屬性無法被外部程式訪問,自然這個資料就沒有存在的價值了。所以這個時候就需要進入封裝的第二步了:對外提供公開的訪問入口,讓外部程式統一通過這個入口去訪問資料,我們可以在這個入口處設立關卡,進行安全控制,這樣物件內部的資料就安全了。
對於“一個”屬性來說,我們對外應該提供幾個訪問入口呢?通常情況下我們訪問物件的某個屬性,不外乎讀取(get)和修改(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()); } }
執行結果如下圖所示:
圖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()); } }
執行結果如下圖所示:
圖4:set和get方法測試
有的小夥伴可能會有這樣的疑問:構造方法中已經給屬性賦值了,為什麼還要提供set方法呢?
因為,這是兩個完全不同的時刻,構造方法中給屬性賦值是在建立物件的時候完成的,當物件建立完畢之後,屬性可能還是會被修改的,後期要想修改屬性的值,這個時候就必須呼叫set方法了。