1. 程式人生 > >深入分析類與物件

深入分析類與物件

一、封裝性

在研究封裝之前,首先先觀察如下一段程式碼:

class Book {// 定義一個新的類
	String title;// 書的名字
	double price;// 書的價格

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		Book book = new Book();
		book.title = "Java開發";
		book.price = -89.9;
		book.getInfo();

	}
}

以上的程式碼之中沒有語法錯誤,但是卻存在業務邏輯上的錯誤,因為沒有一本書的價格是負的。而造成此類問題的核心的關鍵在於:物件可以在一個類的外部直接訪問屬性。

那麼現在首先要解決的問題是需要將Book類中的屬性設定為對外不可見,只能夠針對於本類訪問。用private關鍵字來定義屬性。

範例:修改之前的程式

class Book {// 定義一個新的類
	private String title;// 書的名字
	private double price;// 書的價格

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		Book book = new Book();
		book.title = "Java開發";// 欄位 Book.title 不可視
		book.price = -89.9;// 欄位 Book.price 不可視
		book.getInfo();

	}
}

在訪問屬性的時候發現,外部的物件無法再直接呼叫類中的屬性了,所以現在等於是屬性對外部不可見。

但是如果要想使程式正常使用,那麼必須想辦法讓外部的程式可以操作類的屬性,所以在開發之中,針對於屬性有這樣一種定義:所有在類中定義的屬性都要求使用private宣告,如果屬性需要被外部所使用,那麼按照要求定義相應的setter、getter方法,以String title為例。

  • setter方法:主要是設定內容,public void setTitle(String t);有引數
  • getter方法:主要是取得屬性內容,public String getTitle();無引數

範例:為Book類中的封裝屬性設定setter、getter方法

class Book {// 定義一個新的類
	private String title;// 書的名字
	private double price;// 書的價格

	public void setTitle(String t) {
		title = t;
	}

	public void setPrice(double p) {
		price = p;
	}

	public String getTitle() {
		return title;
	}

	public double getPrice() {
		return price;
	}

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		Book book = new Book();
		book.setTitle("Java開發");
		book.setPrice(-89.9);
		book.getInfo();

	}
}

如果真的需要加入檢查錯誤的程式碼,那麼應該在setter之中增加,因為getter只是簡單地返回資料。

範例:增加驗證

public void setPrice(double p) {
		if (p > 0.0) {
			price = p;
		}
	}

對於資料的驗證部分,在標準開發之中應該是由其它的輔助程式碼完成的,而在實際開發之中,setter往往是簡單的設定資料,getter是簡單的取得資料。

二、構造方法與匿名物件

現在我們已經清楚了物件的產生格式:

①類名稱 ②物件名稱  = ③new ④類名稱()

  • ①類名稱:規定了物件的型別,即:物件可以使用哪些屬性與哪些方法,都是由類定義的;
  • ②物件名稱:如果要想使用物件,需要有一個名字,這是一個唯一的標記;
  • ③new:開闢新的堆記憶體空間,如果沒有此語句,物件無法例項化;
  • ④類名稱():呼叫了一個和類名稱一樣的方法,這就是構造方法。

通過以上分析,可以發現,所有的構造方法一直在被我們呼叫。但是,我們從來沒有去定義一個構造方法,之所以能夠使用,是因為在整個Java類之中,為了保證程式可以正常執行,那麼即使使用者沒有定義任何的構造方法,也會在程式編譯之後自動的為類裡面增加一個沒有引數,方法名稱與類名稱相同,沒有返回值的構造方法。

構造方法的定義原則:方法名稱與類名稱相同,沒有返回值宣告。

class Book {
	public Book() {// 無參的,無返回值的構造方法

	}
}

如果在Book類裡面沒有寫上以上的構造方法,那麼也會自動生成一個。

構造方法的作用是什麼呢?

範例:觀察程式碼

public class Demo {

	public static void main(String[] args) {
		Book book = null;
		book = new Book();

	}

}

class Book {
	public Book() {
		System.out.println("********");

	}
}

可以發現所有的構造方法所有的物件都在物件使用關鍵字new例項化的時候被預設呼叫。

構造方法與普通方法的最大區別:

  • 構造方法是在例項化新(new)物件的時候只調用一次。
  • 普通方法是在例項化物件產生之後可以隨意呼叫多次。

構造方法在物件例項化時使用有什麼作用呢?

class Book {// 定義一個新的類
	private String title;// 書的名字
	private double price;// 書的價格

	public void setTitle(String t) {
		title = t;
	}

	public void setPrice(double p) {
		if (p > 0.0) {
			price = p;
		}
	}

	public String getTitle() {
		return title;
	}

	public double getPrice() {
		return price;
	}

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		Book book = new Book();//使用預設生成的構造方法
		book.setTitle("Java開發");
		book.setPrice(-89.9);
		book.getInfo();

	}
}

本程式是先例項化了Book物件,而後利用Book類的例項化物件去呼叫類中定義的setter方法。如果要想設定全部屬性的內容,那麼一定要呼叫多次構造方法。

範例:定義構造方法

class Book {// 定義一個新的類
	private String title;// 書的名字
	private double price;// 書的價格
	// 已經明確定義了一個構造,預設的構造將不再自動生成

	public Book(String t, double p) {
		title = t;
		setPrice(p);// 呼叫本類中定義的setter方法
	}

	public void setTitle(String t) {
		title = t;
	}

	public void setPrice(double p) {
		if (p > 0.0) {
			price = p;
		}
	}

	public String getTitle() {
		return title;
	}

	public double getPrice() {
		return price;
	}

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		//在例項化物件的同時將所需要的類屬性傳遞到物件的構造方法裡
		Book book = new Book("Java開發",89.9);// 使用預設生成的構造方法
		book.getInfo();

	}
}

在實際的工作之中,構造方法的核心作用:在類物件例項化的時候設定屬性的初始化內容。構造方法是為屬性初始化準備的。

如果一個類之中明確的定義了一個構造方法,那麼不會再自動生成預設的構造方法,即:一個類之中至少保留有一個構造方法。

另外,既然構造方法也屬於方法行列,那麼可以針對於構造方法進行過載,但是構造方法由於其定義的特殊性,所以在構造方法過載時,要求只注意引數的型別及個數即可。

範例:構造方法過載

class Book {
	public Book() {
		System.out.println("無參構造");
	}

	public Book(String t) {
		System.out.println("有一個引數的構造");
	}

	public Book(String t, double p) {
		System.out.println("有兩個引數的構造");
	}

}

public class Demo {
	public static void main(String[] args) {
		Book ba = new Book("Java");

	}
}

在定義構造方法過載的時候有一點程式碼編寫要求:請按照引數的個數進行升序或降序排列。

遺留問題:在定義一個類的時候可以為屬性直接設定預設值,但是這個預設值只有在構造執行完後才會設定,否則不會設定,而構造方法是屬於整個物件構造過程的最後一步,即:是留給使用者處理的步驟。

在物件例項化的過程之中,一定會經歷類的載入,記憶體的分配,預設值的設定,構造方法。

class Book {
	private String title = "Java開發";

	public Book() {//title現在的預設值跟此構造方法沒關係
	}
}

本程式之中,只有在整個構造都完成之後,才會真正將我們的“Java開發”這個字串的內容設定給title屬性,在沒有構造之前title都是其對應資料型別的預設值。

依照構造方法的概念使用匿名物件。之前定義的都屬於有名物件,所有的物件都給了一個名字,但是這個名字真正使用的時候呼叫的肯定是堆記憶體空間,即:真實的物件資訊都儲存在了堆記憶體空間之中,那麼如果沒有棧指向的物件就稱為匿名物件。

class Book {// 定義一個新的類
	private String title;// 書的名字
	private double price;// 書的價格
	// 已經明確定義了一個構造,預設的構造將不再自動生成

	public Book(String t, double p) {
		title = t;
		setPrice(p);// 呼叫本類中定義的setter方法
	}

	public void setTitle(String t) {
		title = t;
	}

	public void setPrice(double p) {
		if (p > 0.0) {
			price = p;
		}
	}

	public String getTitle() {
		return title;
	}

	public double getPrice() {
		return price;
	}

	public void getInfo() {// 此方法將由物件呼叫
		System.out.println("圖書名稱:" + title + "價格:" + price);
	}
}

public class TestDemo {
	public static void main(String args[]) {
		new Book("Java開發",89.9).getInfo();

	}
}

但是匿名物件由於沒有其它物件對其進行引用,所以只能使用一次,一次之後該物件空間就將成為垃圾等待回收。

三、綜合實戰:簡單Java類

現在要求開發一個僱員的類,裡面包含有僱員編號、姓名、職位、基本工資、佣金。

這種功能的類在開發之中成為簡單Java類,因為這些類裡面不會包含有過於複雜的程式邏輯。

對於簡單Java類而言,那麼現在可以給出它的第一種開發形式:

  • 類名稱必須存在有意義,例如:Book、Emp;
  • 類之中所有的屬性必須用private封裝,封裝後的屬性必須提供有setter、getter;
  • 類之中可以提供有任意多個構造方法,但是必須保留有一個無參構造方法;
  • 類之中不允許出現任何的輸出語句,所有的資訊輸出必須交給被呼叫處輸出。
  • 類之中需要提供有一個取得物件完整資訊的方法,暫定為:getInfo(),而且返回String型資料。

範例:開發程式類

class Emp {
	private int empno;
	private String ename;
	private String job;
	private double sal;
	private double comm;

	public Emp() {

	}

	public Emp(int eno, String ena, String j, double s, double c) {
		empno = eno;
		ename = ena;
		job = j;
		sal = s;
		comm = c;
	}

	public void setEmpno(int e) {
		empno = e;
	}

	public void setEname(String e) {
		ename = e;
	}

	public void setJob(String j) {
		job = j;
	}

	public void setSal(double s) {
		sal = s;
	}

	public void setComm(double c) {
		comm = c;
	}

	public int getEmpno() {
		return empno;
	}

	public String getEname() {
		return ename;

	}

	public String getJob() {
		return job;
	}

	public double getSal() {
		return sal;
	}

	public double getComm() {
		return comm;
	}

	public String getInfo() {
		return "僱員編號:" + empno + "\n" + "僱員姓名:" + ename + "\n" + "僱員職位:" + job + "\n" + "基本工資:" + sal + "\n" + "佣金:"
				+ comm;
	}

}

範例:編寫測試程式

public class Demo {
	public static void main(String args[]) {
		Emp e = new Emp(7899, "SMITH", "CLERK", 800.0, 1.0);
		e.setEname("ALLEN");
		System.out.println(e.getInfo());
		System.out.println("姓名:" + e.getEname());
	}
}

所有類之中提供的setter、getter方法可能某些操作之中不會使用到,但是依然必須提供。

所有的setter方法除了具備有設定屬性的內容之外,也具備有修改屬性內容的功能。