深入分析類與物件
一、封裝性
在研究封裝之前,首先先觀察如下一段程式碼:
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方法除了具備有設定屬性的內容之外,也具備有修改屬性內容的功能。