Getter/Setter 在 Java 中被普遍使用。看似簡單,但並不是每一個 Java 開發人員都能很好理解並正確實現 Getter/Setter 方法。所以,在這篇文章裏,我想深刻討論 Java 中的 getter 和 setter 方法,請跟隨我一塊兒來看看吧。git
下面的代碼展現了 Getter/Setter 方法的基本使用。github
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 方法,變量的訪問(get)和更新(set)將變得可控。考慮如下 Setter 方法的代碼:安全
public void setName(String name) {
if (name == null || "".equals(name)) {
throw new IllegalArgumentException();
}
this.name = name;
}
複製代碼
這樣能夠確保 name 設置的值始終不爲空。假若能夠直接經過 .
操做符設置 name 的值,那麼調用方能夠隨意爲 name 設置任何值,這就違反了 name 變量的非空約束。bash
也就是說,Getter/Setter 方法能夠確保變量的值免受外界(調用方代碼)的意外更改。當變量被 private
修飾符隱藏而且只能經過 getter 和 setter 訪問時,它就被「封裝」起來了。封裝是面向對象編程(OOP)的基本特性之一,實現 Getter/Setter 是在程序代碼中強制執行封裝的方法之一。函數
Setter 和 Getter 的命名須要遵循 Java bean 的命名約定,如 setXxx()
和 getXxx()
,其中 Xxx 是變量的名稱:this
public void setName(String name) { }
public String getName() { } // getter
複製代碼
而若是變量是 boolean 類型,那麼 getter 方法能夠命名爲 isXxx() 或者 getXxx(),但首選使用前者進行命名:spa
private boolean single;
public boolean isSingle() { } // getter
複製代碼
如如下代碼片斷所示:
public String name; // 使用public修飾
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
複製代碼
變量 name 被聲明爲 public,所以咱們能夠直接在類外部使用點 .
操做符對其進行訪問,從而使 setter 和 getter 無效。這種狀況的解決方法很簡單,直接使用更加「嚴格」的訪問修飾符,例如 protected 和 private。
考慮如下 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 方法中返回原始對象的引用。相反,它應該返回原始對象的副本。
在 Java 中,基本類型有 int, float, double, boolean, char...,你能夠直接自由設置或者返回值,由於 Java 是將一個基本變量的值複製到另外一個變量中,而不是複製對象的引用,所以,錯誤2、三都可以輕鬆避免。
private float amount;
public void setAmount(float amount) {
this.amount = amount;
}
public float getAmount() {
return amount;
}
複製代碼
也就是說,對於基本數據類型,用不着一些正確實現 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;
}
複製代碼
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 對象,正如上面錯誤2、三所描述,咱們不能這樣簡單實現 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 的關鍵點是:
若是定義對象的自定義類型,則應針對本身的類型實現 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 的規則是:
Java 的 Getter/Setter 看起來很簡單,可是若是實現不當,可能會很危險,它甚至多是致使你代碼行爲異常的問題的根源。或者更糟糕的是,別人能夠經過隱式操縱 Getter 或者 Setter 的參數並從中獲取對象來輕易地「蹂躪」你的程序。
請當心使用,避免踩坑。