编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议36~40)
建議36:使用構(gòu)造代碼塊精簡(jiǎn)程序
什么叫做代碼塊(Code Block)?用大括號(hào)把多行代碼封裝在一起,形成一個(gè)獨(dú)立的數(shù)據(jù)體,實(shí)現(xiàn)特定算法的代碼集合即為代碼塊,一般來(lái)說(shuō)代碼快不能單獨(dú)運(yùn)行的,必須要有運(yùn)行主體。在Java中一共有四種類型的代碼塊:
我么知道一個(gè)類中至少有一個(gè)構(gòu)造函數(shù)(如果沒(méi)有,編譯器會(huì)無(wú)私的為其創(chuàng)建一個(gè)無(wú)參構(gòu)造函數(shù)),構(gòu)造函數(shù)是在對(duì)象生成時(shí)調(diào)用的,那現(xiàn)在為你來(lái)了:構(gòu)造函數(shù)和代碼塊是什么關(guān)系,構(gòu)造代碼塊是在什么時(shí)候執(zhí)行的?在回答這個(gè)問(wèn)題之前,我們先看看編譯器是如何處理構(gòu)造代碼塊的,看如下代碼:
1 public class Client36 { 2 3 { 4 // 構(gòu)造代碼塊 5 System.out.println("執(zhí)行構(gòu)造代碼塊"); 6 } 7 8 public Client36() { 9 System.out.println("執(zhí)行無(wú)參構(gòu)造"); 10 } 11 12 public Client36(String name) { 13 System.out.println("執(zhí)行有參構(gòu)造"); 14 }15 }這是一段非常簡(jiǎn)單的代碼,它包含了構(gòu)造代碼塊、無(wú)參構(gòu)造、有參構(gòu)造,我們知道代碼塊不具有獨(dú)立執(zhí)行能力,那么編譯器是如何處理構(gòu)造代碼塊的呢?很簡(jiǎn)單,編譯器會(huì)把構(gòu)造代碼塊插入到每個(gè)構(gòu)造函數(shù)的最前端,上面的代碼等價(jià)于:
1 public class Client36 { 2 3 public Client36() { 4 System.out.println("執(zhí)行構(gòu)造代碼塊"); 5 System.out.println("執(zhí)行無(wú)參構(gòu)造"); 6 } 7 8 public Client36(String name) { 9 System.out.println("執(zhí)行構(gòu)造代碼塊"); 10 System.out.println("執(zhí)行有參構(gòu)造"); 11 } 12 }每個(gè)構(gòu)造函數(shù)的最前端都被插入了構(gòu)造代碼塊,很顯然,在通過(guò)new關(guān)鍵字生成一個(gè)實(shí)例時(shí)會(huì)先執(zhí)行構(gòu)造代碼塊,然后再執(zhí)行其他代碼,也就是說(shuō):構(gòu)造代碼塊會(huì)在每個(gè)構(gòu)造函數(shù)內(nèi)首先執(zhí)行(需要注意的是:構(gòu)造代碼塊不是在構(gòu)造函數(shù)之前運(yùn)行的,它依托于構(gòu)造函數(shù)的執(zhí)行),明白了這一點(diǎn),我們就可以把構(gòu)造代碼塊應(yīng)用到如下場(chǎng)景中:
以上兩個(gè)場(chǎng)景利用了構(gòu)造代碼塊的兩個(gè)特性:在每個(gè)構(gòu)造函數(shù)中都運(yùn)行和在構(gòu)造函數(shù)中它會(huì)首先運(yùn)行。很好的利用構(gòu)造代碼塊的這連個(gè)特性不僅可以減少代碼量,還可以讓程序更容易閱讀,特別是當(dāng)所有的構(gòu)造函數(shù)都要實(shí)現(xiàn)邏輯,而且這部分邏輯有很復(fù)雜時(shí),這時(shí)就可以通過(guò)編寫多個(gè)構(gòu)造代碼塊來(lái)實(shí)現(xiàn)。每個(gè)代碼塊完成不同的業(yè)務(wù)邏輯(當(dāng)然了構(gòu)造函數(shù)盡量簡(jiǎn)單,這是基本原則),按照業(yè)務(wù)順序一次存放,這樣在創(chuàng)建實(shí)例對(duì)象時(shí)JVM就會(huì)按照順序依次執(zhí)行,實(shí)現(xiàn)復(fù)雜對(duì)象的模塊化創(chuàng)建。
建議37:構(gòu)造代碼塊會(huì)想你所想
? 上一建議中我們提議使用構(gòu)造代碼塊來(lái)簡(jiǎn)化代碼,并且也了解到編譯器會(huì)自動(dòng)把構(gòu)造代碼塊插入到各個(gè)構(gòu)造函數(shù)中,那我們接下來(lái)看看,編譯器是不是足夠聰明,能為我們解決真實(shí)的開(kāi)發(fā)問(wèn)題,有這樣一個(gè)案例,統(tǒng)計(jì)一個(gè)類的實(shí)例變量數(shù)。你可要說(shuō)了,這很簡(jiǎn)單,在每個(gè)構(gòu)造函數(shù)中加入一個(gè)對(duì)象計(jì)數(shù)器補(bǔ)救解決了嘛?或者我們使用上一建議介紹的,使用構(gòu)造代碼塊也可以,確實(shí)如此,我們來(lái)看如下代碼是否可行:
1 public class Client37 { 2 public static void main(String[] args) { 3 new Student(); 4 new Student("張三"); 5 new Student(10); 6 System.out.println("實(shí)例對(duì)象數(shù)量:"+Student.getNumOfObjects()); 7 } 8 } 9 10 class Student { 11 // 對(duì)象計(jì)數(shù)器 12 private static int numOfObjects = 0; 13 14 { 15 // 構(gòu)造代碼塊,計(jì)算產(chǎn)生的對(duì)象數(shù)量 16 numOfObjects++; 17 } 18 19 public Student() { 20 21 } 22 23 // 有參構(gòu)造調(diào)用無(wú)參構(gòu)造 24 public Student(String stuName) { 25 this(); 26 } 27 28 // 有參構(gòu)造不調(diào)用無(wú)參構(gòu)造 29 public Student(int stuAge) { 30 31 } 32 //返回在一個(gè)JVM中,創(chuàng)建了多少實(shí)例對(duì)象 33 public static int getNumOfObjects(){ 34 return numOfObjects; 35 } 36 }這段代碼可行嗎?能計(jì)算出實(shí)例對(duì)象的數(shù)量嗎?如果編譯器把構(gòu)造代碼塊插入到各個(gè)構(gòu)造函數(shù)中,那帶有String形參的構(gòu)造函數(shù)就可能有問(wèn)題,它會(huì)調(diào)用無(wú)參構(gòu)造,那通過(guò)它生成的Student對(duì)象就會(huì)執(zhí)行兩次構(gòu)造代碼塊:一次是無(wú)參構(gòu)造函數(shù)調(diào)用構(gòu)造代碼塊,一次是執(zhí)行自身的構(gòu)造代碼塊,這樣的話計(jì)算就不準(zhǔn)確了,main函數(shù)實(shí)際在內(nèi)存中產(chǎn)生了3個(gè)對(duì)象,但結(jié)果確是4。不過(guò)真的是這樣嗎?我們運(yùn)行之后,結(jié)果是:
實(shí)例對(duì)象數(shù)量:3;
實(shí)例對(duì)象的數(shù)量還是3,程序沒(méi)有問(wèn)題,奇怪嗎?不奇怪,上一建議是說(shuō)編譯器會(huì)把構(gòu)造代碼塊插入到每一個(gè)構(gòu)造函數(shù)中,但是有一個(gè)例外的情況沒(méi)有說(shuō)明:如果遇到this關(guān)鍵字(也就是構(gòu)造函數(shù)調(diào)用自身的其它構(gòu)造函數(shù)時(shí)),則不插入構(gòu)造代碼塊,對(duì)于我們的例子來(lái)說(shuō),編譯器在編譯時(shí)發(fā)現(xiàn)String形參的構(gòu)造函數(shù)調(diào)用了無(wú)參構(gòu)造,于是放棄插入構(gòu)造代碼塊,所以只執(zhí)行了一次構(gòu)造代碼塊。
那Java編譯器為何如此聰明?這還要從構(gòu)造代碼塊的誕生說(shuō)起,構(gòu)造代碼塊是為了提取構(gòu)造函數(shù)的共同量,減少各個(gè)構(gòu)造函數(shù)的代碼產(chǎn)生的,因此,Java就很聰明的認(rèn)為把代碼插入到this方法的構(gòu)造函數(shù)中即可,而調(diào)用其它的構(gòu)造函數(shù)則不插入,確保每個(gè)構(gòu)造函數(shù)只執(zhí)行一次構(gòu)造代碼塊。
還有一點(diǎn)需要說(shuō)明,大家千萬(wàn)不要以為this是特殊情況,那super也會(huì)類似處理了,其實(shí)不會(huì),在構(gòu)造塊的處理上,super方法沒(méi)有任何特殊的地方,編譯器只把構(gòu)造代碼塊插入到super方法之后執(zhí)行而已。僅此不同。
注意:放心的使用構(gòu)造代碼塊吧,Java已經(jīng)想你所想了。
建議38:使用靜態(tài)內(nèi)部類提高封裝性
? Java中的嵌套類(Nested Class)分為兩種:靜態(tài)內(nèi)部類(也叫靜態(tài)嵌套類,Static Nested Class)和內(nèi)部類(Inner Class)。本次主要看看靜態(tài)內(nèi)部類。什么是靜態(tài)內(nèi)部類呢?是內(nèi)部類,并且是靜態(tài)(static修飾)的即為靜態(tài)內(nèi)部類,只有在是靜態(tài)內(nèi)部類的情況下才能把static修飾符放在類前,其它任何時(shí)候static都是不能修飾類的。
靜態(tài)內(nèi)部類的形式很好理解,但是為什么需要靜態(tài)內(nèi)部類呢?那是因?yàn)殪o態(tài)內(nèi)部類有兩個(gè)優(yōu)點(diǎn):加強(qiáng)了類的封裝和提高了代碼的可讀性,我們通過(guò)下面代碼來(lái)解釋這兩個(gè)優(yōu)點(diǎn)。
1 public class Person { 2 // 姓名 3 private String name; 4 // 家庭 5 private Home home; 6 7 public Person(String _name) { 8 name = _name; 9 } 10 11 /* home、name的setter和getter方法略 */ 12 13 public static class Home { 14 // 家庭地址 15 private String address; 16 // 家庭電話 17 private String tel; 18 19 public Home(String _address, String _tel) { 20 address = _address; 21 tel = _tel; 22 } 23 /* address、tel的setter和getter方法略 */ 24 } 25 }其中,Person類中定義了一個(gè)靜態(tài)內(nèi)部類Home,它表示的意思是"人的家庭信息",由于Home類封裝了家庭信息,不用再Person中再定義homeAddr,homeTel等屬性,這就使封裝性提高了。同時(shí)我們僅僅通過(guò)代碼就可以分析出Person和Home之間的強(qiáng)關(guān)聯(lián)關(guān)系,也就是說(shuō)語(yǔ)義增強(qiáng)了,可讀性提高了。所以在使用時(shí)就會(huì)非常清楚它表達(dá)的含義。
public static void main(String[] args) {// 定義張三這個(gè)人Person p = new Person("張三");// 設(shè)置張三的家庭信息p.setHome(new Home("北京", "010"));}定義張三這個(gè)人,然后通過(guò)Person.Home類設(shè)置張三的家庭信息,這是不是就和我們真是世界的情形相同了?先登記人的主要信息,然后登記人員的分類信息。可能你由要問(wèn)了,這和我們一般定義的類有神么區(qū)別呢?又有什么吸引人的地方呢?如下所示:
解釋了這么多,大家可能會(huì)覺(jué)得外部類和靜態(tài)內(nèi)部類之間是組合關(guān)系(Composition)了,這是錯(cuò)誤的,外部類和靜態(tài)內(nèi)部類之間有強(qiáng)關(guān)聯(lián)關(guān)系,這僅僅表現(xiàn)在"字面上",而深層次的抽象意義則依類的設(shè)計(jì).
那靜態(tài)類內(nèi)部類和普通內(nèi)部類有什么區(qū)別呢?下面就來(lái)說(shuō)明一下:
建議39:使用匿名類的構(gòu)造函數(shù)
? 閱讀如下代碼,看上是否可以編譯:
public static void main(String[] args) {List list1=new ArrayList();List list2=new ArrayList(){};List list3=new ArrayList(){{}};System.out.println(list1.getClass() == list2.getClass());System.out.println(list2.getClass() == list3.getClass());System.out.println(list1.getClass() == list3.getClass());}注意ArrayList后面的不通點(diǎn):list1變量后面什么都沒(méi)有,list2后面有一對(duì){},list3后面有兩個(gè)嵌套的{},這段程序能否編譯呢?若能編譯,那輸結(jié)果是什么呢?
答案是能編譯,輸出的是3個(gè)false。list1很容易理解,就是生命了ArrayList的實(shí)例對(duì)象,那list2和list3代表的是什么呢?
(1)、list2 = new ArrayList(){}:list2代表的是一個(gè)匿名類的聲明和賦值,它定義了一個(gè)繼承于ArrayList的匿名類,只是沒(méi)有任何覆寫的方法而已,其代碼類似于:
// 定義一個(gè)繼承ArrayList的內(nèi)部類class Sub extends ArrayList {}// 聲明和賦值List list2 = new Sub();(2)、list3 = new ArrayList(){{}}:這個(gè)語(yǔ)句就有點(diǎn)奇怪了,帶了兩對(duì){},我們分開(kāi)解釋就明白了,這也是一個(gè)匿名類的定義,它的代碼類似于:
// 定義一個(gè)繼承ArrayList的內(nèi)部類class Sub extends ArrayList {{//初始化代碼塊 }}// 聲明和賦值List list3 = new Sub();看到了吧,就是多了一個(gè)初始化塊而已,起到構(gòu)造函數(shù)的功能,我們知道一個(gè)類肯定有一個(gè)構(gòu)造函數(shù),而且構(gòu)造函數(shù)的名稱和類名相同,那問(wèn)題來(lái)了:匿名類的構(gòu)造函數(shù)是什么呢?它沒(méi)有名字呀!很顯然,初始化塊就是它的構(gòu)造函數(shù)。當(dāng)然,一個(gè)類中的構(gòu)造函數(shù)塊可以是多個(gè),也就是說(shuō)會(huì)出現(xiàn)如下代碼:
List list4 = new ArrayList(){{} {} {} {} {}};上面的代碼是正確無(wú)誤,沒(méi)有任何問(wèn)題的,現(xiàn)在清楚了,匿名類雖然沒(méi)有名字,但也是可以有構(gòu)造函數(shù)的,它用構(gòu)造函數(shù)塊來(lái)代替構(gòu)造函數(shù),那上面的3個(gè)輸出就很明顯了:雖然父類相同,但是類還是不同的。
建議40:匿名類的構(gòu)造函數(shù)很特殊
?在上一建議中我們講到匿名類雖然沒(méi)有名字,但可以有一個(gè)初始化塊來(lái)充當(dāng)構(gòu)造函數(shù),那這個(gè)構(gòu)造函數(shù)是否就和普通的構(gòu)造函數(shù)完全不一樣呢?我們來(lái)看一個(gè)例子,設(shè)計(jì)一個(gè)計(jì)算器,進(jìn)行加減運(yùn)算,代碼如下:
1 public class Calculator { 2 enum Ops { 3 ADD, SUB 4 }; 5 6 private int i, j, result; 7 8 // 無(wú)參構(gòu)造 9 public Calculator() { 10 11 } 12 13 // 有參構(gòu)造 14 public Calculator(int _i, int _j) { 15 i = _i; 16 j = _j; 17 } 18 19 // 設(shè)置符號(hào),是加法運(yùn)算還是減法運(yùn)算 20 protected void setOperator(Ops _ops) { 21 result = _ops.equals(Ops.ADD) ? i + j : i - j; 22 } 23 24 // 取得運(yùn)算結(jié)果 25 public int getResult() { 26 return result; 27 } 28 29 }代碼的意圖是,通過(guò)構(gòu)造函數(shù)傳遞兩個(gè)int類型的數(shù)字,然后根據(jù)設(shè)置的操作符(加法還是減法)進(jìn)行運(yùn)算,編寫一個(gè)客戶端調(diào)用:
public static void main(String[] args) {Calculator c1 = new Calculator(1, 2) {{setOperator(Ops.ADD);}};System.out.println(c1.getResult());}這段匿名類的代碼非常清晰:接收兩個(gè)參數(shù)1和2,然后設(shè)置一個(gè)操作符號(hào),計(jì)算其值,結(jié)果是3,這毫無(wú)疑問(wèn),但是這中間隱藏著一個(gè)問(wèn)題:帶有參數(shù)的匿名類聲明時(shí)到底調(diào)用的是哪一個(gè)構(gòu)造函數(shù)呢?我們把這段程序模擬一下:
//加法計(jì)算 class Add extends Calculator{{setOperator(Ops.ADD);}//覆寫父類的構(gòu)造方法public Add(int _i, int _j){} }匿名類和這個(gè)Add類等價(jià)嗎?可能有人會(huì)說(shuō):上面只是把匿名類增加了一個(gè)名字,其它的都沒(méi)有改動(dòng),那肯定是等價(jià)了,毫無(wú)疑問(wèn) ,那好,編寫一個(gè)客戶端調(diào)用Add類的方法看看。代碼就略了,因?yàn)楹芎?jiǎn)單new Add,然后調(diào)用父類的getResult方法就可以了,經(jīng)過(guò)測(cè)試,輸出結(jié)果為0(為什么而是0?這很容易,有參構(gòu)造沒(méi)有賦值)。這說(shuō)明兩者不等價(jià),不過(guò),原因何在呢?
因?yàn)槟涿惖臉?gòu)造函數(shù)特殊處理機(jī)制,一般類(也就是沒(méi)有顯示名字的類)的所有構(gòu)造函數(shù)默認(rèn)都是調(diào)用父類的無(wú)參構(gòu)造函數(shù)的,而匿名類因?yàn)闆](méi)有名字,只能由構(gòu)造代碼塊代替,也就無(wú)所謂有參和無(wú)參的構(gòu)造函數(shù)了,它在初始化時(shí)直接調(diào)用了父類的同參數(shù)構(gòu)造函數(shù),然后再調(diào)用了自己的構(gòu)造代碼塊,也就是說(shuō)上面的匿名類和下面的代碼是等價(jià)的:
//加法計(jì)算 class Add extends Calculator{{setOperator(Ops.ADD);}//覆寫父類的構(gòu)造方法public Add(int _i, int _j){super(_i,_j);} }它會(huì)首先調(diào)用父類有兩個(gè)參數(shù)的構(gòu)造函數(shù),而不是無(wú)參構(gòu)造,這是匿名類的構(gòu)造函數(shù)與普通類的差別,但是這一點(diǎn)也確實(shí)鮮有人仔細(xì)琢磨,因?yàn)樗奶幚頇C(jī)制符合習(xí)慣呀,我傳遞兩個(gè)參數(shù),就是希望先調(diào)用父類有兩個(gè)參數(shù)的構(gòu)造,然后再執(zhí)行我自己的構(gòu)造函數(shù),而Java的處理機(jī)制也正是如此處理的。
總結(jié)
以上是生活随笔為你收集整理的编写高质量代码:改善Java程序的151个建议(第3章:类、对象及方法___建议36~40)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ajax传参到实体类对应字段
- 下一篇: Thinking In Java 第四章