Getter/Setter 在 Java 中被廣泛使用。看似簡單,但並非每個 Java 開發人員都能很好理解並正確實現 Getter/Setter 方法。因此,在這篇文章裏,我想深入討論 Java 中的 getter 和 setter 方法,請跟隨我一起來看看吧。
一個簡單的例子
下面的代碼展示了 Getter/Setter 方法的基本使用。
public class GetterAndSetterExample { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
可以看到,我們在類 GetterAndSetterExample 中聲明瞭一個私有變量 name。因爲 name 是私有的,所以我們無法在類外部直接訪問該變量。以下的代碼將無法編譯通過:
GetterAndSetterExample object = new GetterAndSetterExample(); object.name = "yanglbme"; // 編譯出錯 String name = object.name; // 編譯出錯
正確的“姿勢”是調用 getter getName() 和 setter setName() 來讀取或更新變量:
GetterAndSetterExample object = new GetterAndSetterExample(); object.setName("yanglbme"); String name = object.getName();
爲什麼我們需要 Getter/Setter ?
通過使用 Getter/Setter 方法,變量的訪問(get)和更新(set)將變得可控。考慮以下 Setter 方法的代碼:
public void setName(String name) { if (name == null || "".equals(name)) { throw new IllegalArgumentException(); } this.name = name; }
這樣可以確保 name 設置的值始終不爲空。倘若可以直接通過 . 操作符設置 name 的值,那麼調用方可以隨意爲 name 設置任何值,這就違反了 name 變量的非空約束。
也就是說,Getter/Setter 方法可以確保變量的值免受外界(調用方代碼)的意外更改。當變量被 private 修飾符隱藏並且只能通過 getter 和 setter 訪問時,它就被“封裝”起來了。封裝是面向對象編程(OOP)的基本特性之一,實現 Getter/Setter 是在程序代碼中強制執行封裝的方法之一。
Getter/Setter 方法的命名約束
Setter 和 Getter 的命名需要遵循 Java bean 的命名約定,如 setXxx() 和 getXxx(),其中 Xxx 是變量的名稱:
public void setName(String name) { } public String getName() { } // getter
而如果變量是 boolean 類型,那麼 getter 方法可以命名爲 isXxx() 或者 getXxx(),但首選使用前者進行命名:
private boolean single; public boolean isSingle() { } // getter
Getter/Setter 的常見錯誤實現
錯誤一:實現了 Getter/Setter 方法,但變量不做嚴格的範圍限制
如以下代碼片段所示:
public String name; // 使用public修飾 public void setName(String name) { this.name = name; } public String getName() { return name; }
變量 name 被聲明爲 public,因此我們可以直接在類外部使用點 . 操作符對其進行訪問,從而使 setter 和 getter 無效。這種情況的解決方法很簡單,直接使用更加“嚴格”的訪問修飾符,例如 protected 和 private。
錯誤二:在 Setter 中直接賦值一個對象引用
考慮以下 Setter 方法:
public class Student { private int[] scores; // setter public void setScores(int[] scores) { this.scores = scores; } public void showScores() { for (int score : scores) { System.out.print(score + " "); } System.out.println(); } }
是不是感覺沒毛病?我們再來看以下代碼:
int[] myScores = {100, 97, 99, 88, 69}; Student yang = new Student(); yang.setScores(myScores); yang.showScores(); 複製代碼
可以看到,整數數組 myScores 先進行了初始化並傳遞給 setScores() 方法,隨後對 scores 進行了簡單打印,產生了以下輸出:
100 97 99 88 69
現在,我們修改 myScores 數組中第 2 個元素的值,並再次打印 scores:
myScores[1] = 101; yang.showScores();
程序將會輸出如下:
100 101 99 88 69
而這樣就意味着我們可以在 Setter 方法之外修改數據,這顯然已經破壞了 Setter 封裝的目的。爲什麼會這樣呢?我們再來看一下 setScores() 方法:
public void setScores(int[] scores) { this.scores = scores; }
成員變量 scores 直接引用了一個參數變量 scores,這意味着兩個變量都指向了內存中同一個對象,即 myScores 數組對象。因此,對 myScores 變量所做的變更將直接導致成員變量 scores 被同步修改。這種情況下,解決辦法是:將方法參數 scores 拷貝一份賦值給成員變量 scores:
public void setScores(int[] scores) { this.scores = new int[scores.length]; System.arraycopy(scores, 0, this.scores, 0, scores.length); }
經驗總結:如果我們是將對象作爲參數傳遞給 setter 方法,不要直接使用簡單引用賦值的方式。相反,我們應該找到一些方法,將對象的值賦值到內部成員變量中,比如使用 System.arraycopy() 方法將元素從一個數組複製到另一個數組中。
錯誤三:直接返回對象的引用
考慮以下 Getter 方法的實現:
private int[] scores; public int[] getScores() { return scores; }
在程序中,我們調用 getScores() 方法,並修改其中某個元素的值:
int[] myScores = {100, 97, 99, 88, 69}; Student yang = new Student(); yang.setScores(myScores); yang.showScores(); int[] copyScores = yang.getScores(); copyScores[3] = 520; yang.showScores();
將會產生以下輸出:
100 97 99 88 69 100 97 99 520 69
正如你所看到的,數組第 4 個元素已經被修改爲 520。這是由於 Getter 方法直接返回了內部成員變量 scores 的引用,因此,外部代碼可以獲取到該引用並對元素進行修改。
這種情況的解決方法是:應該返回對象的副本,而不是直接返回引用:
public int[] getScores() { int[] copy = new int[this.scores.length]; System.arraycopy(this.scores, 0, copy, 0, copy.length); return copy; // 返回副本 }
經驗總結:不要在 Getter 方法中返回原始對象的引用。相反,它應該返回原始對象的副本。
實現基本類型的 Getter/Setter 方法
在 Java 中,基本類型有 int, float, double, boolean, char...,你可以直接自由設置或者返回值,因爲 Java 是將一個基本變量的值複製到另一個變量中,而不是複製對象的引用,因此,錯誤二、三都能夠輕鬆避免。
private float amount; public void setAmount(float amount) { this.amount = amount; } public float getAmount() { return amount; }
也就是說,對於基本數據類型,用不着一些正確實現 Getter/Setter 的特殊技巧。
實現對象類型的 Getter/Setter 方法
String 對象的 Getter/Setter 方法
String 是一種對象類型,但是它是不可變的,這意味着我們一旦創建了 String 對象,就無法更改其內容。換句話說,對 String 對象的每次更改都會導致新創建一個 String 對象。因此,像原始類型一樣,我們可以安全地爲 String 變量實現 Getter/Setter,就像這樣:
private String address; public void setAddress(String address) { this.address = address; } public String getAddress() { return address; }
Date 對象的 Getter/Setter 方法
java.util.Date 類實現了 Object 類中的 clone() 方法。clone() 方法返回對象的副本,因此我們可以將其用於 getter 和 setter,如以下代碼所示:
private Date birthDate; public void setBirthDate(Date birthDate) { this.birthDate = (Date) birthDate.clone(); } public Date getBirthDate() { return (Date) this.birthDate.clone(); }
clone() 方法返回一個 Object 類型的對象,因此我們必須將其強制轉換爲 Date 類型。
Collection 對象的 Getter/Setter 方法
對於 Collection 對象,正如上面錯誤二、三所描述,我們不能這樣簡單實現 Getter/Setter 方法。
private List<String> listTitles; public void setListTitles(List<String> titles) { this.listTitles = titles; } public List<String> getListTitles() { return listTitles; }
對於字符串集合,一種解決方法是使用一個構造函數,該構造函數接收另一個集合作爲參數。比如:
public void setListTitles(List<String> titles) { // 將titles傳遞給ArrayList的構造函數 this.listTitles = new ArrayList<String>(titles); } public List<String> getListTitles() { return new ArrayList<String>(this.listTitles); }
注意,上面的構造方法僅僅適用於字符串型的集合。但不適用於 Object 類型的集合。考慮以下示例,我們定義了類 CollectionGetterSetterObject 和 Person:
import java.util.*; public class CollectionGetterSetterObject { // 元素類型是Person的List集合 private List<Person> listPeople; public void setListPeople(List<Person> list) { this.listPeople = new ArrayList<Person>(list); } public List<Person> getListPeople() { return new ArrayList<Person>(this.listPeople); } } class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return name; } }
對於 String,每複製一個 String 對象都會爲之創建新對象,而其他 Object 類型的對象則不會,它們僅複製引用,因此這就是兩個集合不同但它們包含相同對象的原因。
查看 Collection API,我們發現 ArrayList、HashMap、HashSet 等實現了自己的 clone() 方法。這些方法返回淺表副本,這些淺表副本不會將元素從源 Collection 複製到目標。
比如,ArrayList 類的 clone() 方法的 Javadoc 描述如下:
/** * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The * elements themselves are not copied.) * * @return a clone of this <tt>ArrayList</tt> instance */ public Object clone() { }
因此,我們不能使用這些 Collection 類的 clone() 方法。解決方案是爲我們自己定義的對象(上例中的 Person 類)實現 clone() 方法。我們在 Person 類中重新實現 clone() 方法,如下所示:
public Object clone() { Person aClone = new Person(this.name); return aClone; }
listPeople 的 Setter 方法應該修改爲如下:
public void setListPeople(List<Person> list) { for (Person aPerson : list) { this.listPeople.add((Person) aPerson.clone()); } }
而相應地,Getter 方法也應該被修改,如下所示:
public List<Person> getListPeople() { List<Person> listReturn = new ArrayList<Person>(); for (Person aPerson : this.listPeople) { listReturn.add((Person) aPerson.clone()); } return listReturn; }
因此,新的 CollectionGetterSetterObject 類代碼應該是這樣的:
import java.util.*; public class CollectionGetterSetterObject { private List<Person> listPeople = new ArrayList<Person>(); public void setListPeople(List<Person> list) { for (Person aPerson : list) { this.listPeople.add((Person) aPerson.clone()); } } public List<Person> getListPeople() { List<Person> listReturn = new ArrayList<Person>(); for (Person aPerson : this.listPeople) { listReturn.add((Person) aPerson.clone()); } return listReturn; } }
小結一下,實現 Collection 類型的 Getter/Setter 的關鍵點是:
- 對於 String 對象的集合,由於 String 對象是不可變的,因此不需要任何特殊的調整。
- 對於對象的自定義類型的集合:
- 實現自定義類型的 clone() 方法。
- 對於 setter,將克隆的項目從源集合添加到目標集合。
- 對於 getter,創建一個新的 Collection,並將其返回。將原始集合中的克隆項添加到新集合中。
自定義對象的 Getter/Setter 方法
如果定義對象的自定義類型,則應針對自己的類型實現 clone() 方法。
class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String toString() { return this.name; } // 自己實現clone方法 public Object clone() { Person aClone = new Person(this.name); return aClone; } }
如我們所見,類 Person 實現了其 clone() 方法以返回其自身的克隆版本。然後,setter 方法應該實現如下:
public void setFriend(Person person) { this.friend = (Person) person.clone(); }
而對於 getter 方法:
public Person getFriend() { return (Person) this.friend.clone(); }
小結一下,爲自定義對象類型實現 getter 和 setter 的規則是:
- 爲自定義類型實現一個 clone() 方法。
- 從 getter 返回一個克隆的對象。
- 在 setter 中分配一個克隆的對象。
總結
Java 的 Getter/Setter 看起來很簡單,但是如果實現不當,可能會很危險,它甚至可能是導致你代碼行爲異常的問題的根源。或者更糟糕的是,別人可以通過隱式操縱 Getter 或者 Setter 的參數並從中獲取對象來輕易地“蹂躪”你的程序。
請小心使用,避免踩坑。