您好,登錄后才能下訂單哦!
二哥,究竟什么是面向對象呢?還有,什么是面向過程。今天去面試的時候,面試官讓我用面向對象的思想談一談這次面試的過程。
前兩天,讀者秋秋問我了我上面這個問題。
看到這個問題后,我思考了好一會兒,總覺得面試官的問法有點問題:為什么要用面向對象的思想談一談面試的“過程”?
有點矛盾,有沒有?先不管這么多了,且來看看什么是面向對象吧。
很久沒有思考過什么是面向對象這個問題了,就好像很久沒有吃過烤紅薯一樣,那股香味究竟是什么,已經很難準確地形容出來了。腦海中只浮現出這樣一幅圖:
一開始的時候,并沒有面向對象,只有面向過程的概念。我們回到秋秋面試的話題上,把面試前(可以降低需求的復雜性)的過程簡單地拆解一下。
為了實現這 3 個步驟,我們定義 3 個方法,并依次調用:
但是,假如參加面試的不是秋秋,這 3 個方法就要重新定義了(莫抬杠),盡管步驟并沒有變。面向對象從另一個角度來解決這個問題,它把對象(對事物的一種抽象描述)作為程序的基本單元。
回到秋秋面試的例子,用面向對象的思想來實現,就需要先定義 2 個類(類是構建對象的藍圖,里面包含若干的數據和操作這些數據的方法),分別是應聘者和面試官。
應聘者可以投遞簡歷;面試官可以接收應聘者的簡歷和通知應聘者前來面試。然后再通過類創建兩個對象,分別是秋秋和他的面試官;對象創建成功后,就可以依次調用對應的方法完成上述的 3 個步驟。
面向對象(英語:Object Oriented,縮寫:OO)思想是一種試圖降低代碼間的依賴,應對復雜性,從而解決代碼重用的軟件設計思想——恰好解決了面向過程帶來的問題。
面向對象有很多重要的特性,比如說封裝、繼承和多態。這些概念又該怎么理解呢?所謂一圖勝千言,我給你來一張有趣的、形象的。
了解了面向對象的思想后,我們來通過具體的代碼完成秋秋面試前的 3 個步驟。并對類和對象的相關知識點進行歸納和總結。
先來細致地看一下應聘者類——Candidate.java。
package com.cmower
class Candidate {
private String name;
public Candidate(String name) {
this.name = name;
}
public void deliverResume() {
System.out.println(getName() + "發簡歷");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Candidate 包含了類的 4 個重要概念:
name
;getter/setter
)getName()
和 setName()
;Candidate()
;deliverResume()
。Candidate 類雖然簡單,但卻大有學問。
1)為了保證包名的絕對唯一,Sun 公司建議將域名(絕對是獨一無二的)以逆序的形式作為包名——這也是為什么包名經常以 org
、com
開頭的原因(是不是有一種豁然開朗的感覺)。我曾申請過一個域名,叫 cmower.com,所以我個人編寫的絕大多數代碼都是在 com.cmower
包下。
2)類的方法定義順序依次是:構造方法 > 公有(public
)方法或保護(protected
)方法 > 私有(private
)方法 > getter/setter 方法。
構造方法是創建對象的必經之路,放在首位是必須的。如果只有系統默認的無參構造方法,可忽略。
公有方法是類的調用者和維護者最關心的方法,應該在比較靠前的位置展示;保護方法雖然只有子類關心,也可能是“模板設計模式”下的核心方法,所以也要靠前;私有方法只對本類可見,一般不需要特別關心,所以往后放;getter/setter
方法承載的信息價值較低,所以放在類的最后面。
3)setter 方法中,參數名稱與成員變量名稱保持一致,采用 this.成員名 = 參數名
的形式。
4)成員變量不要用 public
修飾,盡量用 private
修飾;如果需要被子類繼承,可以用 protected
修飾。
在初學 Java 編程的時候,我經常產生一個疑惑:為什么不使用 public
修飾成員變量呢?這樣做不是比 getter/setter
更方便嗎?
我最先想到的答案是這樣的:
解釋:如果只有 private String name
而沒有 getter/setter
的話,Eclipse 會提示 The value of the field Candidate.name is not used
的警告。
當然了,這樣的答案過于牽強。那能不能來個靠譜點的答案呢?
能,為了體現封裝的思想:將數據與行為進行分離。封裝有什么好處呢?
getter/setter
)來訪問數據,可以方便地加入控制方法,限制對成員變量的不合理操作;不過,我對這些嚴肅的詞匯和科學用語實在是提不起半點興致。那就再換一個答案吧。
套用《Java 開發實戰經典》中舉過的一個例子,我們增加一個應聘者年齡的共有成員變量 age。
class Candidate {
public int age;
}
然后在創建應聘者對象的時候,直接通過類成員變量賦值:new Candidate().age = -99;
這樣賦值是沒有任何問題的,但沒有實際的意義,年齡是不可能為負數的。為了防止出現這樣的錯誤,可以對它進行封裝,也就是私有化,然后在 setter
方法中對年齡進行判斷,代碼如下:
class Candidate {
private int age;
public void setAge(int age) {
if (age >= 0) {
this.age = age;
}
}
}
這個答案你覺得滿意嗎?我最開始看到這個答案的時候覺得很滿意。但看了《阿里巴巴 Java 開發手冊》后(詳情截圖如下),就覺得不滿意了。
第一,類成員變量使用基本類型很容易造成NullPointException
的錯誤;第二,在 getter/setter
增加業務邏輯的確很容易把實際的問題隱藏起來。
那,好的答案究竟是什么呢?
如果設置成員變量為 public
,那么每個調用者都可以讀寫它,但如果以 private
配合 getter/setter
的形式訪問時,就可以達到“不準訪問”、“只讀訪問”、“讀寫訪問”以及“只寫訪問”的目的。因為不是每個成員變量都需要 getter/setter
。
5)每個類都至少會有一個構造方法。初學者可能會非常疑惑:我的那個類真的沒有構造方法啊!
如果在編寫一個類的時候沒有編寫構造方法,那么系統就會提供一個無參的構造方法,就好像是這樣:
class Candidate {
private String name;
public Candidate() {
}
}
當執行 new Candidate()
的時候,成員變量 name 就會被初始化為 null
。一般情況下,我們會為類設置它必須的構造方法,然后在創建對象的時候對成員變量進行賦值。
再來粗略地看一下面試官類——Interviewer.java。
class Interviewer {
private Candidate candidate;
public Interviewer (Candidate candidate) {
this.candidate = candidate;
}
public void receviveResume() {
System.out.println("收到" + getCandidate().getName() + "簡歷");
}
public void notifyInterview() {
System.out.println("通知" + getCandidate().getName() + "面試");
}
public Candidate getCandidate() {
return candidate;
}
public void setCandidate(Candidate candidate) {
this.candidate = candidate;
}
}
Interviewer 有一個成員變量 Candidate,一個構造方法,兩個共有方法,以及成員變量對應的 getter/setter
。
(這段代碼存在一個嚴重的問題,你注意到了嗎?)
然后,我們讓應聘者發送簡歷,讓面試官接收簡歷并發送通知。
Candidate qiuqiu = new Candidate("秋秋");
// 發送簡歷
qiuqiu.deliverResume();
Interviewer interviewer = new Interviewer(qiuqiu);
// 面試官接收到簡歷
interviewer.receviveResume();
// 面試官通知應聘者來面試
interviewer.notifyInterview();
在初學 Java 的很長一段時間里,我總是搞不清楚什么是“對象”,什么是“引用”,差點因此放棄我的程序生涯。后來,在網上認識了一個大佬,人稱老王,是他挽救了我的程序生涯。
他解釋說。
Candidate qiuqiu = new Candidate("秋秋");
可以拆分為兩行代碼:
Candidate qiuqiu;
qiuqiu = new Candidate("秋秋");
第一行代碼 Candidate qiuqiu;
中的 qiuqiu 這時候可以稱作是對象變量,它暫時還沒有引用任何對象,嚴格意義上,它也不能稱為 null
。
第二行代碼 qiuqiu = new Candidate("秋秋");
可以拆分為兩個部分,= 號左側和 = 號右側。
右側的表達式 new Candidate("秋秋")
先執行,執行完后,會在堆上創建了一個 name 為“秋秋”的對象,類型為 Candidate,表達式 new Candidate("秋秋")
的值是新創建對象的引用。
然后再把這個引用通過 = 操作符賦值給左側的對象變量 qiuqiu
,賦值后,qiuqiu
就不再是對象變量了,應該稱為對象引用。
看完老王的解釋,你會不會情不自禁地“哦,原來如此啊!”反正我當時頓悟的時候是這樣的。
前面提到,Interviewer 類的設計存在一個嚴重的問題,是什么呢?
Candidate qiuqiu = new Candidate("秋秋");
Interviewer interviewer = new Interviewer(qiuqiu);
interviewer.getCandidate().setName("夏夏");
System.out.println(qiuqiu.getName());
這段代碼執行完后,你會發現秋秋變成了夏夏,應聘者的私有成員變量 name 竟然被改變了!問題的原因也很簡單,qiuqiu 和 interviewer.getCandidate()
引用了同一個對象。
那怎么解決呢?當 getter
需要返回一個可變對象的引用時,應該先進行克隆(clone)。以下展示了一個非常簡單的克隆方案。
class Interviewer {
private Candidate candidate;
public Interviewer (Candidate candidate) {
this.candidate = candidate;
}
public Candidate getCandidate() {
Candidate candidate = new Candidate(this.candidate.getName());
return candidate;
}
}
這篇文章花了 5 個多小時才寫完,此刻我的感覺只有一個字——餓,我要出去吃飯了。吃飯之前,我決定先買個烤紅薯吃,重溫一下那種久違的香。
上一篇:Java面試官:兄弟,你確定double精度比float低嗎?
下一篇:再談 Java 的繼承和超類 Object
謝謝大家的閱讀,原創不易,喜歡就隨手點個贊
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。