1. 程式人生 > >java set get訪問器理解

java set get訪問器理解

簡述

java中習慣將類的成員變數屬性設定為私有(private),並通過設定setXXX和getXXX方法來完成對成員變數的賦值和取值操作。在剛開始學習java時一直很疑惑為什麼不將成員變數設為公有(public),直接操作成員變數不是更方便嗎?其實這樣設計是源於java的三大特性(封裝,繼承,多型)中封裝的概念。

封裝

封裝是把過程和資料包圍起來,對資料的訪問只能通過已定義的介面(該介面並非指java中interface,而是泛指只關心方法而不關心具體實現的方法,在此處可理解為set,get訪問器)。面向物件計算始於這個基本概念,即現實世界可以被描繪成一系列完全自治、封裝的物件,這些物件通過一個受保護的介面訪問其他物件。封裝是一種資訊隱藏技術,在java中通過關鍵字private實現封裝。什麼是封裝?封裝把物件的所有組成部分組合在一起,封裝定義程式如何引用物件的資料,封裝實際上使用方法將類的資料隱藏起來,控制使用者對類的修改和訪問資料的程度。

上述解釋來自百度百科,紅字部分為自己理解

public VS private

我們來看下為什麼不採用public標記例項域的理由:

我們可以用public 標記例項域,但是這是一種極為不提倡的做法。public資料域允許程式中的任何方法對其進行讀取和修改。這就完全破壞了封裝。任何類的任何方法都可以修改public域,從我們經驗來看,某些程式碼將使用這種存取許可權。而這並不是我們所希望的,因此,這裡強烈建議將例項域標記為private。

上述解釋摘自 《java核心技術 卷一 (第九版)》 p109

作者只從破壞封裝的角度談了一下public標記例項域的缺點,其實我們可以從另一方面考慮。我們發現set get訪問器和成員方法本質是一樣(邏輯上不同),那麼我們可以考慮下呼叫方法和直接訪問屬性的不同。

只有修改許可權

這絕對是用public標記例項域無法辦到的(反射除外),而訪問器可以通過暴露set方法來實現。

成員變數型別改變

public class Person {
	private String age;

	public String getAge() {
		return age;
	}

	public void setAge(String age) {
		this.age = age;
	}
}

這是我們需要將age型別改為int
public class Person {
	private int age;

	public String getAge() {
		return ""+age;
	}

	public void setAge(String age) {
		this.age = Integer.parseInt(age);
	}
}

所有依賴person的類都無需修改程式碼,如果是public標記例項域就必須將所有對依賴person物件age屬性的程式碼進行修改。假如依賴100個。這還不是做可怕的,如果依賴類下面還要用到這個屬性那該行程式碼同樣可能需要修改。

返回引用可變物件

這裡我們建立一個Person類,該類有一個birthday(生日)屬性,正常情況下,人的生日在出生(初始化)的時候就已經固定,並且不會放生改變。

public class Person {
    private Date birthday;
    public Person(Date birthday){
        this.birthday=birthday;
    }
    public Date getBirthday() {
        return birthday;
    }
    public static void main(String[] args) {
        Person john=new Person(new GregorianCalendar(1991,6,5).getTime());
        Date d=john.getBirthday();
        System.out.println(john.getBirthday()); //=>Fri Jul 05 00:00:00 CDT 1991
        d.setMonth(0);
        System.out.println(john.getBirthday()); //=>Sat Jan 05 00:00:00 CST 1991
    }
}

出錯的原因很微妙。d和john.getBirthday引用同一物件.對d呼叫更改器方法就可以自動修改生日這個私有狀態。這是我們其實是要將get方法中return birthday修改為return (Date) birthday.clone();即可。

當然public標記例項域也可以實現,只不過每次獲取成員變數是都需要手動複製一遍。顯示增加程式碼的複雜度,而且增加了程式碼的重複率。

而從關注點分離的角度來看,複製這個行為和呼叫者的業務無關,身為呼叫者我只是想拿到john的生日,並將這個日期進行一些處理,而不應該去關心你傳遞的是一個引用所以我需要複製一下。這本身就是技術對於業務邏輯的汙染,應該是我們儘量去避免的。