自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

原生態(tài)Java 程序員容易忽視的編程細(xì)節(jié)

開(kāi)發(fā) 后端
如果您熟悉C或C++,那么學(xué)習(xí)Java語(yǔ)言并不困難,這就像是會(huì)說(shuō)瑞典語(yǔ)的人去學(xué)丹麥語(yǔ)一樣,雖然它們彼此互通,但是每種語(yǔ)言都有自己的語(yǔ)法標(biāo)準(zhǔn)。

Java是Java程序設(shè)計(jì)語(yǔ)言和Java平臺(tái)的總稱,要想學(xué)好一門(mén)語(yǔ)言,打好基礎(chǔ)最關(guān)鍵的,學(xué)習(xí)一種新的編程語(yǔ)言比學(xué)習(xí)新的口頭語(yǔ)言要容易得多。然而,在這兩種學(xué)習(xí)過(guò)程中,都要付出額外的努力去學(xué)習(xí)不帶口音地說(shuō)新語(yǔ)言。

如果您熟悉C或C++,那么學(xué)習(xí)Java語(yǔ)言并不困難,這就像是會(huì)說(shuō)瑞典語(yǔ)的人去學(xué)丹麥語(yǔ)一樣。語(yǔ)言雖有不同,但又彼此互通。但若不夠謹(jǐn)慎,您的口音每次都會(huì)暴露出您并非原生語(yǔ)言使用者這個(gè)秘密。

C++ 程序員往往會(huì)對(duì)Java代碼做出一些變形,而這樣的舉動(dòng)將他們與原生Java語(yǔ)言用戶清晰地區(qū)分開(kāi)來(lái)。他們的代碼可以無(wú)錯(cuò)運(yùn)行,但對(duì)于原生語(yǔ)言用戶來(lái)說(shuō),就是有一些地方不對(duì)勁。因而原生語(yǔ)言用戶可能會(huì)輕視非原生用戶。從 C 或 C++(或者 Basic、Fortran、Scheme 等)轉(zhuǎn)到Java語(yǔ)言時(shí),您需要根除一些習(xí)慣用語(yǔ),并糾正某些發(fā)音,以便流暢地使用新語(yǔ)言。在本文中,我探討了一些往往被忽視的Java編程細(xì)節(jié),因?yàn)閺恼Z(yǔ)義上來(lái)說(shuō),它們并不重要,甚至是無(wú)關(guān)緊要的。它們純粹是風(fēng)格和慣例問(wèn)題。其中有些細(xì)節(jié)有著似是而非的理由,其他一些甚至連似是而非的理由也沒(méi)有。但所有這些細(xì)節(jié)都是當(dāng)今編寫(xiě)的Java代碼中真實(shí)存在的現(xiàn)象。

這是什么語(yǔ)言?

讓我們首先來(lái)看一段代碼,其作用是將華氏溫度轉(zhuǎn)換為攝氏度,如清單 1 所示:

清單 1. 一段C代碼?

  1. float F, C;   
  2. float min_tmp, max_tmp, x;   
  3. min_tmp = 0;    
  4. max_tmp = 300;   
  5. x  = 20;   
  6. F = min_tmp;   
  7. while (F <= max_tmp) {   
  8.    C = 5 * (F-32) / 9;   
  9.    printf("%f\t%f\n", F, C);   
  10.    FF = F + x;   
  11. }  

清單 1 中使用的是什么語(yǔ)言?很顯然是 C 語(yǔ)言 —請(qǐng)等一下,讓我們來(lái)看看完整的程序,如清單 2 所示:

清單 2. Java 程序

  1. class Test  {   
  2.  
  3.     public static void main(String argv[]) {    
  4.         float F, C;   
  5.         float min_tmp, max_tmp, x;   
  6.         
  7.         min_tmp = 0;    
  8.         max_tmp = 300;   
  9.         x  = 20;   
  10.         
  11.         F = min_tmp;   
  12.         while (F <= max_tmp) {   
  13.           C = 5 * (F-32) / 9;   
  14.           printf("%f\t%f\n", F, C);   
  15.           FF = F + x;   
  16.         }   
  17.     }   
  18.  
  19.     private static void printf(String format, Object... args) {   
  20.         System.out.printf(format, args);   
  21.     }   
  22.       
  23. }  

無(wú)論您是否相信,清單 1 和清單 2 都是使用 Java 語(yǔ)言編寫(xiě)的。它們只是以 C 語(yǔ)言方言(老實(shí)說(shuō),清單 1 也確實(shí)可以是 C 代碼)編寫(xiě)的 Java 代碼。這里的幾個(gè)習(xí)語(yǔ)標(biāo)志著:編寫(xiě)這段代碼的人是以 C 語(yǔ)言思考的,只是單純地將其翻譯為 Java 語(yǔ)言:

◆變量是 float 而非 double。
◆所有變量都是在方法上方聲明的。
◆初始化緊接聲明之后。
◆使用了 while 循環(huán)而非 for 循環(huán)。
◆使用了 printf 而非 println。
◆main() 方法的參數(shù)名為 argv。
◆數(shù)組括號(hào)緊接參數(shù)名之后,而非類(lèi)型之后。

如果僅僅考慮所編寫(xiě)的這些代碼是否能夠編譯或者是否會(huì)得到正確的結(jié)果,那么這些方言都不是錯(cuò)誤的。如果分開(kāi)來(lái)看,這幾點(diǎn)都并不明顯。但將它們結(jié)合在一起,就構(gòu)成了一段非常古怪的代碼,Java 程序員難以讀懂,就像美國(guó)人難以聽(tīng)懂北英格蘭人的方言一樣。您使用的此類(lèi) C 語(yǔ)言方言越少,您的代碼就會(huì)越清晰。請(qǐng)牢記這一點(diǎn),下面我們將繼續(xù)分析 C 語(yǔ)言程序員暴露自己身份的一些常見(jiàn)方式,并說(shuō)明如何才能使他們的代碼更符合 Java 程序員的眼光。

命名規(guī)范

根據(jù)您原本使用的是 C、C++ 還是 C#,您可能有一些較為主觀的類(lèi)命名規(guī)范。舉例來(lái)說(shuō),在 C# 中,類(lèi)名都是以小寫(xiě)字母開(kāi)頭的,方法名和字段名以大寫(xiě)字母開(kāi)頭。Java 風(fēng)格則恰好相反。我沒(méi)有任何合理的原因能評(píng)判一種規(guī)范是否比另一種更好,但我了解,混用命名規(guī)范會(huì)使代碼看起來(lái)存在嚴(yán)重錯(cuò)誤。這種做法也會(huì)導(dǎo)致 bug。如果您知道,每一個(gè)全部由大寫(xiě)字母組成的名稱都是常量,則會(huì)以不同的方式進(jìn)行處理。在尋找命名規(guī)范與聲明類(lèi)型不匹配之處時(shí),我發(fā)現(xiàn)了程序中的許多 bug。args而非 argv 這一點(diǎn)是最微不足道的,但也正是這場(chǎng)風(fēng)格之爭(zhēng)所關(guān)注的細(xì)節(jié)。在Java的慣例中main()方法的參數(shù)名為args,而不是argv:

  1. public static void main(String[] args)  

這至多只是對(duì) argv 這個(gè)名稱進(jìn)行了一點(diǎn)細(xì)微的改進(jìn)。作為參數(shù)的縮寫(xiě),它或多或少地比 argv 更易懂一些。 當(dāng)然,在合乎慣例的 Java 代碼中,通常是禁止使用縮寫(xiě)的(參見(jiàn) 請(qǐng)勿縮寫(xiě))。我們使用 args 作為 main() 方法的參數(shù)名的惟一原因與 C 程序員使用 argv 的原因是相同的 — ***本關(guān)于 C 語(yǔ)言的圖書(shū)的作者 Kernighan 和 Ritchie 使用了這個(gè)名稱。而 Gosling 和 Arnold 使用了 args。除此之外,再無(wú)其他原因。同樣,所有原生 Java 程序員都傾向于使用 args,如果您希望保持原汁原味,那么也應(yīng)該這樣做。Java 編程中的基本命名規(guī)則非常簡(jiǎn)單,也值得牢記:

◆類(lèi)和接口名以大寫(xiě)字母開(kāi)頭,如 Frame。
◆方法、字段和本地變量名以小寫(xiě)字母開(kāi)頭,如 read()。
◆類(lèi)、方法和字段名均使用駝峰式大小寫(xiě)風(fēng)格,如 InputStream 和 readFully()。
◆常量 — 終態(tài)靜態(tài)字段和臨時(shí)終態(tài)本地變量 — 全部適用大寫(xiě)字母,并以下劃線分隔各詞,如 MAX_CONNECTIONS。

像 sprintf 和 nmtkns 這樣的名稱是超級(jí)計(jì)算機(jī)只有 32 KB 內(nèi)存時(shí)代的遺物。編譯器將標(biāo)識(shí)符限制為 8 個(gè)字符或更少,以此來(lái)節(jié)約內(nèi)存。近 30 年來(lái),這已經(jīng)不再是需要擔(dān)心的問(wèn)題。如今,再?zèng)]有任何理由不使用完整拼寫(xiě)的變量和方法名稱。難以解讀、無(wú)元音字母的變量名清楚地表明這個(gè)程序出自一名皈依 Java 的 C 程序員之手,請(qǐng)參見(jiàn)清單 3:

清單 3. Abbrvtd nms r hrd 2 rd

  1. for (int i = 0; i < nr; i++) {   
  2.     for (int j = 0; j < nc; j++) {   
  3.         t[i][j] = s[i][j];   
  4.     }   
  5. }  

不縮寫(xiě)、采用駝峰式大小寫(xiě)風(fēng)格的名稱更易讀易懂,如清單 4 所示:

清單 4. 未縮寫(xiě)的名稱更易讀

  1. for (int row = 0; i < numRows; row++) {   
  2.     for (int column = 0; column < numColumns; column++) {   
  3.         target[row][column] = source[row][column];   
  4.     }   
  5. }  

一段代碼被閱讀的次數(shù)要遠(yuǎn)遠(yuǎn)超過(guò)編寫(xiě)的次數(shù),Java 語(yǔ)言為易讀性而進(jìn)行了優(yōu)化。C 程序員近乎沉迷于難解的代碼,而 Java 程序員則不然。Java 語(yǔ)言將易讀性置于簡(jiǎn)潔性之前,有一些極為常用的縮寫(xiě)形式,您仍然可以放心使用:

◆max 表示***(maximum)
◆min 表示最?。╩inimum)
◆in 表示 InputStream
◆out 表示 OutputStream
◆e 或 ex 表示 catch 子句中的異常(不用于其他位置)
◆num 表示數(shù)字(number),僅用作前綴,如 numTokens 或 numHits
◆tmp 表示主要在本地使用的臨時(shí)變量 — 針對(duì)實(shí)例,在交換兩個(gè)值的時(shí)候,除此之外(或許還有少數(shù)一些例外),您應(yīng)完整拼寫(xiě)出名稱中使用的所有詞。#p#

變量聲明、初始化和使用(重用)

早期版本的 C 需要在方法開(kāi)始處聲明所有變量。這樣是為了在編譯器中實(shí)現(xiàn)一定的優(yōu)化,允許它在 RAM 極為有限的環(huán)境中運(yùn)行。因而,C 語(yǔ)言中的方法大多以幾行變量聲明開(kāi)頭:

  1. int i, j, k;   
  2. double x, y, z;   
  3. float cf[], gh[], jk[];  

然而,這種風(fēng)格也有一些缺陷。它將變量的聲明與其使用分離開(kāi)來(lái),使代碼的易讀性降低。此外,它會(huì)為多種不同的用途重用一個(gè)本地變量,有可能并非刻意而為。但若變量持有代碼的某個(gè)片段無(wú)法接受的殘值,這可能會(huì)帶來(lái)無(wú)法預(yù)料的 bug。這一點(diǎn)與 C 語(yǔ)言中簡(jiǎn)短而難解的變量名結(jié)合在一起,將會(huì)后患無(wú)窮。

在 Java 語(yǔ)言(和較新版本的 C 語(yǔ)言)中,變量可在初次使用或接近初次使用時(shí)聲明。在編寫(xiě) Java 代碼時(shí),請(qǐng)采取這種做法。這將使您的代碼更加安全、更不易出現(xiàn) bug,也更易于閱讀。此外,Java 代碼通常在聲明變量時(shí)初始化各變量,而 C 程序員有時(shí)會(huì)寫(xiě)出下面這樣的代碼:

  1. int i;   
  2. i = 7;  

盡管這在語(yǔ)法上是正確的,但 Java 程序員永遠(yuǎn)不會(huì)寫(xiě)出這樣的代碼。他們會(huì)這樣寫(xiě)這段代碼:

  1. int i = 7;  

這有助于避免因意外使用了未經(jīng)初始化的變量而導(dǎo)致的 bug。惟一的常見(jiàn)例外是一個(gè)變量的作用域需要同時(shí)包含 try 塊和 catch 或 finally 塊。這往往是由于代碼涉及需要在 finally 塊中關(guān)閉的輸入流和輸出流而導(dǎo)致的,如清單 5 所示:

清單 5. 異常處理可能會(huì)使變量的作用域難以合理設(shè)定

  1. InputStream in;   
  2. try {   
  3.   in = new FileInputStream("data.txt");   
  4.   // read from InputStream   
  5. }   
  6.  finally {   
  7.   if (in != null) {   
  8.     in.close();   
  9.   }   
  10. }  

但這幾乎是惟一的異常,這種風(fēng)格的***一種連鎖反應(yīng)就是 Java 程序員通常每行僅聲明一個(gè)變量。例如,他們初始化變量的方法如下:

  1. int i = 3;   
  2. int j = 8;   
  3. int k = 9;  

通常不會(huì)寫(xiě)出下面這樣的代碼:

  1. int i=3j=8k=9;  

 這條語(yǔ)句在語(yǔ)法上是正確的,但除非在一種特殊的例外情況下,專(zhuān)業(yè) Java 程序員是不會(huì)這樣做的,后文將介紹這種特殊情況。老式的 C 程序員甚至可能編寫(xiě)一個(gè)四行的代碼:

  1. int i, j, k;   
  2. i = 3;   
  3. j = 8;   
  4. k = 9;  

Java 風(fēng)格將聲明與初始化結(jié)合在一起,因而實(shí)際上要更簡(jiǎn)練一些,只需要三行代碼。

將變量置入循環(huán)

常見(jiàn)的一種特殊情況就是在循環(huán)外部聲明變量。例如,考慮清單 6 中簡(jiǎn)單的 for 循環(huán),其作用是計(jì)算斐波那契數(shù)列的前 20 項(xiàng):

清單 6. C 程序員喜歡在循環(huán)外部聲明變量

  1. int high = 1;   
  2. int low = 1;   
  3. int tmp;   
  4. int i;   
  5. for (i = 1; i < 20; i++) {   
  6.     System.out.println(high);   
  7.     tmp = high;   
  8.     highhigh = high+ low;   
  9.     low = tmp;   
  10. }  

所有這四個(gè)變量都是在循環(huán)外聲明的,盡管它們僅在循環(huán)內(nèi)部使用,但作用域不止于此。這容易導(dǎo)致 bug,變量可能會(huì)在其目標(biāo)作用域之外被重用。對(duì)于使用常用名的變量來(lái)說(shuō)更是這樣,例如 i 和 tmp。某次使用的值可能會(huì)殘留下來(lái),并以無(wú)法預(yù)計(jì)的方式干擾后續(xù)的代碼。***項(xiàng)改進(jìn)(C 語(yǔ)言的現(xiàn)代版本也支持這項(xiàng)改進(jìn))是將 i 循環(huán)變量的聲明移到循環(huán)之內(nèi),如清單 7 所示:

清單 7. 將循環(huán)變量移入循環(huán)

  1. int high = 1;   
  2. int low = 1;   
  3. int tmp;   
  4. for (int i = 1; i < 20; i++) {   
  5.     System.out.println(high);   
  6.     tmp = high;   
  7.     highhigh = high+ low;   
  8.     low = tmp;   
  9. }  

到這里還沒(méi)有結(jié)束,經(jīng)驗(yàn)豐富的 Java 程序員還會(huì)將 tmp 變量移入循環(huán),如清單 8 所示:

清單 8. 在循環(huán)內(nèi)聲明臨時(shí)變量

  1. int high = 1;   
  2. int low = 1;   
  3. for (int i = 1; i < 20; i++) {   
  4.     System.out.println(high);   
  5.     int tmp = high;   
  6.     highhigh = high+ low;   
  7.     low = tmp;   
  8. }  

某些極度追求速度而又不夠老練的開(kāi)發(fā)人員有時(shí)會(huì)提出反對(duì)意見(jiàn),認(rèn)為這種做法導(dǎo)致循環(huán)內(nèi)執(zhí)行過(guò)多操作,而不只是必要的操作,從而降低代碼運(yùn)行速度。實(shí)際上,在運(yùn)行時(shí),聲明根本不會(huì)執(zhí)行。將聲明移動(dòng)到循環(huán)內(nèi)絕不會(huì)給 Java 平臺(tái)造成負(fù)面的性能影響。許多程序員,包括許多經(jīng)驗(yàn)豐富的 Java 程序員都可能在這里止步。然而,還有一種不太常見(jiàn)的技巧,將所有變量都移入循環(huán)。您可以在 for 循環(huán)的初始化階段聲明多個(gè)變量,只需使用逗號(hào)分隔即可,如清單 9 所示:

清單 9. 在循環(huán)內(nèi)聲明所有變量

  1. for (int i = 1high = 1low = 1; i < 20; i++) {   
  2.     System.out.println(high);   
  3.     int tmp = high;   
  4.     highhigh = high + low;   
  5.     low = tmp;   
  6. }   

這已經(jīng)不僅僅是慣用的流暢代碼,而是真正的專(zhuān)業(yè)代碼。與 C 代碼相比,Java 代碼中的 for循環(huán)更多、while循環(huán)更少,原因就在于這種嚴(yán)格限制本地變量作用域的能力。 #p#

不要回收變量

上述討論得出這樣一個(gè)結(jié)論,Java 程序員幾乎不會(huì)為不同的值和對(duì)象重用本地變量。例如,清單 10 建立了一些按鈕及其關(guān)聯(lián)的動(dòng)作偵聽(tīng)器:

清單 10. 回收本地變量

  1. Button b = new Button("Play");   
  2.  b.addActionListener(new PlayAction());   
  3.  b = new Button("Pause");   
  4.  b.addActionListener(new PauseAction());   
  5.  b = new Button("Rewind");   
  6.  b.addActionListener(new RewindAction());   
  7.  b = new Button("FastForward");   
  8.  b.addActionListener(new FastForwardAction());   
  9.  b = new Button("Stop");   
  10.  b.addActionListener(new StopAction());  

經(jīng)驗(yàn)豐富的 Java 程序員會(huì)用 5 個(gè)不同的本地變量重寫(xiě)這段代碼,如清單 11 所示:

清單 11. 未回收的變量

  1. Button play = new Button("Play");   
  2.  play.addActionListener(new PlayAction());   
  3.  Button pause = new Button("Pause");   
  4.  pause.addActionListener(new PauseAction());   
  5.  Button rewind = new Button("Rewind");   
  6.  rewind.addActionListener(new RewindAction());   
  7.  Button fastForward = new Button("FastForward");   
  8.  fastForward.addActionListener(new FastForwardAction());   
  9.  Button stop = new Button("Stop");   
  10.  stop.addActionListener(new StopAction());  

為多個(gè)邏輯上不同的值或?qū)ο笾赜靡粋€(gè)本地變量容易導(dǎo)致 bug。實(shí)際上,本地變量(并非始終是它們指向的對(duì)象)并不影響內(nèi)存和時(shí)間問(wèn)題。所以不必為此擔(dān)憂,可以根據(jù)您的需要使用多個(gè)不同的本地變量。

信任垃圾收集器的內(nèi)存管理能力出身 C++ 世界的程序員往往過(guò)度擔(dān)心內(nèi)存消耗和內(nèi)存泄漏問(wèn)題。此類(lèi)程序員有兩種表現(xiàn)。一種是在使用過(guò)變量后將變量設(shè)置為 null。另一種是調(diào)用 finalize()或?qū)⑵溆米饕环N偽析構(gòu)函數(shù)。這是完全沒(méi)有必要的。盡管有些時(shí)候確實(shí)需要在 Java 代碼中手動(dòng)釋放內(nèi)存,但這種情況十分罕見(jiàn)。大多數(shù)時(shí)候,只需依靠垃圾收集器即可合理快速地完成內(nèi)存管理。與大多數(shù)優(yōu)化一樣,***實(shí)踐準(zhǔn)則就是:除非能夠證明是有必要的,否則不要去干涉。

使用***原語(yǔ)數(shù)據(jù)類(lèi)型

Java 語(yǔ)言有八種原語(yǔ)數(shù)據(jù)類(lèi)型,但僅使用了其中的六種。在 Java 代碼中,float 比 C 代碼中少得多。float 變量或文字在 Java 代碼中極為罕見(jiàn),更常用的是 double。使用 float 的惟一時(shí)機(jī)就是操縱精度有限的大型多維浮點(diǎn)數(shù)字?jǐn)?shù)組,此時(shí)存儲(chǔ)空間較為重要。否則使用 double 即可。

比 float 更不常見(jiàn)的是 short。我在 Java 代碼中幾乎沒(méi)有見(jiàn)過(guò) short 變量。只有惟一的一次(我要警告您,這是極其罕見(jiàn)的情況),讀入的外部定義數(shù)據(jù)格式碰巧包含 16 位有符號(hào)整型類(lèi)型。在這種情況下,大多數(shù)程序員都會(huì)將其作為 int 讀入。

確定私有屬性的范圍

您是否見(jiàn)過(guò)清單 22 中這種 equals() 方法?

清單 12. C++ 程序員編寫(xiě)的 equals()方法

  1. public class Foo {   
  2.  
  3.   private double x;   
  4.  
  5.   public double getX() {   
  6.     return this.x;   
  7.   }   
  8.  
  9.   public boolean equals(Object o) {   
  10.     if (o instanceof Foo) {   
  11.       Foo f = (Foo) o;   
  12.       return this.x == f.getX();   
  13.     }   
  14.     return false;   
  15.   }   
  16.  
  17.  }  

這個(gè)方法在技術(shù)上是正確的,但我確信,這個(gè)類(lèi)是由一名保守的 C++ 程序員編寫(xiě)的。他在一個(gè)方法中使用了私有字段 x 和公共 getter 方法 getX(),實(shí)際上是在一行代碼之中,這泄漏了他的身份。在 C++ 中,這種做法是必要的,因?yàn)樗接袑傩缘姆秶菍?duì)象而不是類(lèi)。也就是說(shuō),在 C++ 中,同一個(gè)類(lèi)的對(duì)象無(wú)法看到彼此的私有成員變量。他們必須使用 accessor 方法。在 Java 語(yǔ)言中,私有屬性的范圍是類(lèi)而非對(duì)象。類(lèi)型同為 Foo 的兩個(gè)對(duì)象可直接訪問(wèn)對(duì)方的私有字段。

某些微妙 — 往往又不相關(guān) — 的考慮思路認(rèn)為,您應(yīng)該在 Java 代碼中***直接字段訪問(wèn)而非 getter 訪問(wèn),或者反之。字段訪問(wèn)相對(duì)速度較快,但在少數(shù)時(shí)候,getter 訪問(wèn)可能會(huì)提供與直接字段訪問(wèn)略有不同的值,特別是在涉及子類(lèi)的時(shí)候。在 Java 語(yǔ)言中,沒(méi)有任何理由在同一行代碼中為同一個(gè)類(lèi)的同一個(gè)字段同時(shí)使用直接字段訪問(wèn)和 getter 訪問(wèn)。#p#

標(biāo)點(diǎn)和語(yǔ)法方言

下面是一些與 C 語(yǔ)言對(duì)應(yīng)部分不同的 Java 方言,在某些情況下,這樣的差異是為了利用某些 Java 語(yǔ)言特性。將數(shù)組括號(hào)緊接于類(lèi)型之后,Java 語(yǔ)言聲明數(shù)組的方式與 C 語(yǔ)言中大致相同:

 

  1. int k[];   
  2. double temperature[];   
  3. String names[]; 

但Java語(yǔ)言也提供了一種替代性的語(yǔ)法,將數(shù)組復(fù)括號(hào)緊接于類(lèi)型之后,而不是變量名之后:

  1. int[] k;   
  2.  double[] temperatures;   
  3.  String[] names; 

大多數(shù) Java 程序員都采用了第二種風(fēng)格。上面的代碼表示 k 的類(lèi)型是 int 數(shù)組,temperatures 的類(lèi)型是 double 數(shù)組,names 的類(lèi)型是 String 數(shù)組。同樣,與其他本地變量一樣,Java 程序員習(xí)慣在聲明時(shí)初始化這些變量:

  1. int[] k = new int[10]; double[] temperatures = new double[75]; String[] names = new String[32];  

使用 s == null 而不是 null == s,謹(jǐn)慎的 C 程序員已經(jīng)學(xué)會(huì)了將文字置于比較運(yùn)算符的左側(cè)。例如:

 

  1. if (7 == x) doSomething(); 

目標(biāo)在于避免意外地使用單等號(hào)賦值運(yùn)算符而非雙等號(hào)比較運(yùn)算符:

 

  1. if (7 = x) doSomething(); 

若將文字置于左側(cè),這樣的錯(cuò)誤就會(huì)成為編譯時(shí)錯(cuò)誤。這項(xiàng)技巧是 C 語(yǔ)言中一項(xiàng)著名的編程實(shí)踐。它能幫助避免出現(xiàn)真正的 bug,因?yàn)槿魧⑽淖种糜谟叶耍瑢⑹冀K返回 true。然而,不同于 C 語(yǔ)言,Java 語(yǔ)言具有獨(dú)立的 int 和 boolean 類(lèi)型,賦值運(yùn)算符返回 int,而比較運(yùn)算符返回 boolean。因而,if (x = 7) 已經(jīng)成為編譯時(shí)錯(cuò)誤,就沒(méi)有必要為比較語(yǔ)句使用不自然的形式 if (7 == x),流暢的 Java 程序員不會(huì)這樣做。

連接字符串而非格式化字符串

多年以來(lái),Java 語(yǔ)言一直沒(méi)有 printf() 函數(shù)。最終,Java 5 中增加了這個(gè)函數(shù),有些時(shí)候能夠發(fā)揮作用。具體來(lái)說(shuō),在您希望將數(shù)字格式化為特定寬度或小數(shù)點(diǎn)后帶有特定位數(shù)的形式時(shí),在這種不常見(jiàn)的情況下,格式字符串是一種便捷的字段特定語(yǔ)言。而 C 程序員往往在 Java 代碼中過(guò)多地使用 printf()。不應(yīng)使用它取代簡(jiǎn)單的字符串連接。例如:

 

  1. System.out.println("There were " + numErrors + " errors reported."); 

優(yōu)于:

 

  1. System.out.printf("There were %d errors reported.\n", numErrors); 

變體使用了字符串連接,更易于閱讀,在簡(jiǎn)單的情況下更是如此,此外,由于不存在格式字符串中的占位符和數(shù)字或變量參數(shù)的類(lèi)型匹配不當(dāng)?shù)那闆r,出現(xiàn) bug 的機(jī)會(huì)也更少。

***后增量而非前增量

在某些位置,i++ 和 ++i 之間的差別十分顯著。Java 程序員為這些位置定義了一個(gè)具體的名稱,那就是“bug”。不應(yīng)該編寫(xiě)依賴于前增量和后增量之間差異的代碼(對(duì)于 C 語(yǔ)言來(lái)說(shuō)也是如此)。原因在于難以理解、易于出錯(cuò)。如果您發(fā)現(xiàn),在您編寫(xiě)的代碼中兩者的差別有重大影響,那么就應(yīng)該重新將代碼組織為獨(dú)立的語(yǔ)句,使之不再能夠影響大局。

如果前增量和后增量之間的差別不顯著 — 例如,for 循環(huán)的增量步數(shù) — 80% 的 Java 程序員更傾向于使用后增量,只有 20% 的 Java 程序員會(huì)選擇前增量。i++ 比 ++i 更為常用。我無(wú)法評(píng)判孰是孰非,但事實(shí)就是這樣。如果您編寫(xiě)的代碼中包含 ++i,那么任何閱讀您的代碼的人都要浪費(fèi)時(shí)間去思考您為什么要這樣寫(xiě)。因而,除非有特殊的原因必須使用前增量(應(yīng)該不存在必須使用前增量的情況),否則請(qǐng)使用后增量。#p#

錯(cuò)誤處理

錯(cuò)誤處理是 Java 編程中最令人困惑的問(wèn)題之一,也是真正地將語(yǔ)言風(fēng)格大師與平凡開(kāi)發(fā)者區(qū)分開(kāi)來(lái)的一道門(mén)檻。實(shí)際上,僅僅錯(cuò)誤處理就可以自成一篇文章。簡(jiǎn)而言之,合理使用異常,切勿返回錯(cuò)誤代碼。非原生語(yǔ)言使用者的***類(lèi)錯(cuò)誤是返回一個(gè)表示錯(cuò)誤的值,而不是拋出異常。如果回溯到 Java 1.0 的年代,在 Sun 的所有程序員都充分理解了這種新語(yǔ)言之前,在某些 Java 語(yǔ)言自己的 API 中也會(huì)看到這樣的情況。例如,考慮 java.io.File 中的 delete()方法:

 

  1. public boolean delete() 

若文件或目錄被成功刪除,此方法將返回 true,否則返回 false。但最合理的做法 應(yīng)該是,在成功完成時(shí)不返回任何內(nèi)容,若存在出于某些原因未能刪除的文件,則拋出異常:

 

  1. public void delete() throws IOException 

在方法返回錯(cuò)誤值時(shí),每一個(gè)方法調(diào)用都要包含錯(cuò)誤處理代碼。在大多數(shù)正常情況下,這使得跟蹤和理解方法的正常執(zhí)行流變得困難。同時(shí),如果由異常指出錯(cuò)誤條件,錯(cuò)誤處理即可單獨(dú)作為文件末尾處的一個(gè)代碼塊。如果存在更適合處理問(wèn)題的位置,甚至可將其移動(dòng)到其他方法和其他類(lèi)中。這就帶來(lái)了錯(cuò)誤處理中的第二種反模式。具有 C++ 背景的程序員有時(shí)會(huì)竭力在異常拋出后盡快處理異常。如果達(dá)到極限,可能會(huì)得到如清單 13 所示的代碼:

清單 13. 過(guò)早的異常處理

  1. public void readNumberFromFile(String name) {   
  2.     FileInputStream in;   
  3.     try {   
  4.         in = new FileInputStream(name);   
  5.     } catch (FileNotFoundException e) {   
  6.         System.err.println(e.getMessage());   
  7.         return;   
  8.     }   
  9.  
  10.     InputStreamReader reader;   
  11.     try {   
  12.         reader = new InputStreamReader(in, "UTF-8");   
  13.     } catch (UnsupportedEncodingException e) {   
  14.         System.err.println("This can't happen!");   
  15.         return;   
  16.     }   
  17.  
  18.  
  19.     BufferedReader buffer = new BufferedReader(reader);   
  20.     String line;   
  21.     try {   
  22.        line = buffer.readLine();   
  23.     } catch (IOException e) {   
  24.         System.err.println(e.getMessage());   
  25.         return;   
  26.     }   
  27.  
  28.     double x;   
  29.     try {   
  30.         x = Double.parseDouble(line);   
  31.     }   
  32.     catch (NumberFormatException e) {   
  33.         System.err.println(e.getMessage());   
  34.         return;   
  35.     }   
  36.  
  37.     System.out.println("Read: " + x);   
  38.  }  

這段代碼非常難以閱讀,甚至比異常處理取代的 if (errorCondition) 測(cè)試更為難解。流暢的 Java 代碼將錯(cuò)誤處理與故障點(diǎn)分離開(kāi)來(lái),不會(huì)將錯(cuò)誤處理代碼與正常執(zhí)行流混合在一起。清單 14 中的版本更易于閱讀和理解:

清單 14. 保持代碼的主執(zhí)行路線完好

  1. public void readNumberFromFile(String name) {   
  2.     try {   
  3.         FileInputStream in = new FileInputStream(name);   
  4.         InputStreamReader reader = new InputStreamReader(in, "UTF-8");   
  5.         BufferedReader buffer = new BufferedReader(reader);   
  6.         String line = buffer.readLine();   
  7.         double x = Double.parseDouble(line);   
  8.         System.out.println("Read: " + x);   
  9.     }   
  10.     catch (NumberFormatException e) {   
  11.         System.err.println("Data format error");   
  12.     }   
  13.     catch (IOException e) {   
  14.         System.err.println("Error reading from file: " + name);   
  15.     }   
  16.  }  

某些時(shí)候,您可能需要使用嵌套的 try-catch 塊來(lái)分離造成相同異常的不同故障模式,但這種情況并不常見(jiàn)。主要的實(shí)踐經(jīng)驗(yàn)是:如果一個(gè)方法中存在多個(gè) try 塊,那么就表明方法過(guò)于龐大,應(yīng)拆分為多個(gè)較小的方法。***,具有各種語(yǔ)言背景、剛剛接觸 Java 編程的程序員往往會(huì)錯(cuò)誤地假設(shè)他們必須在拋出檢查異常(checked exception)的方法中捕捉到這些異常。而拋出異常的方法通常并不是應(yīng)該負(fù)責(zé)捕捉異常的方法。例如,考慮如清單 15 所示的方法:

清單 15. 過(guò)早的異常處理

  1. public static void copy(InputStream in, OutputStream out) {   
  2.   try {   
  3.     while (true) {   
  4.       int datum = in.read();   
  5.       if (datum == -1) break;   
  6.       out.write(datum);   
  7.     }   
  8.     out.flush();   
  9.   } catch (IOException ex) {   
  10.      System.err.println(ex.getMessage());   
  11.   }   
  12.  }  

此方法沒(méi)有足夠的信息來(lái)處理很有可能發(fā)生的 IOException。它并不了解誰(shuí)調(diào)用了它,也不了解故障的后果。對(duì)于此方法來(lái)說(shuō),惟一合理的舉措就是允許 IOException 上行至調(diào)用方。編寫(xiě)此方法的正確方式如清單 16 所示:

清單 16. 并非所有異常都需要在***時(shí)間捕捉

  1. public static void copy(InputStream in, OutputStream out) throws IOException {   
  2.   while (true) {   
  3.     int datum = in.read();   
  4.     if (datum == -1) break;   
  5.     out.write(datum);   
  6.   }   
  7.   out.flush();   
  8.  }  

簡(jiǎn)而言之,這更為簡(jiǎn)單、更容易理解,將錯(cuò)誤信息傳遞給代碼中最適合處理這些信息的部分。這些問(wèn)題是否真的那么重要?這些問(wèn)題都不是關(guān)鍵問(wèn)題。某些是慣例:在初次使用時(shí)聲明;在不知道如何處理錯(cuò)誤時(shí)拋出異常。其他則是純粹的風(fēng)格慣例(args 而非 argv;i++ 而非 ++i)。我并不認(rèn)為這些規(guī)則能使您的代碼運(yùn)行速度更快,但其中一些確實(shí)能幫助您避免 bug。如果您要成為一名流暢的 Java 語(yǔ)言使用者,所有這些規(guī)則都是重要的。

無(wú)論如何,以純正的口音講話(或編寫(xiě)代碼)都能使其他人更加尊重您、更加關(guān)注您所表達(dá)的內(nèi)容,甚至?xí)槟磉_(dá)的內(nèi)容付給您更多的錢(qián)。此外,以純正的口音使用 Java 語(yǔ)言要比說(shuō)無(wú)口音的法語(yǔ)、漢語(yǔ)或英語(yǔ)要簡(jiǎn)單得多。一旦您學(xué)會(huì)了一門(mén)語(yǔ)言,就值得付出努力來(lái)使您的表達(dá)變得更加原汁原味。

【編輯推薦】

  1. 通過(guò)Java泛型實(shí)現(xiàn)數(shù)組排序和搜索的通用方法
  2. Maven是什么?回顧Java社區(qū)的變革
  3. Java元數(shù)據(jù)總結(jié):Java注釋的使用和定義
  4. XML和Java Bean的互相轉(zhuǎn)換攻略
  5. Java堆棧溢出的機(jī)制與原理

 

責(zé)任編輯:王曉東 來(lái)源: IBM
相關(guān)推薦

2014-02-09 14:34:56

2017-02-08 09:51:27

JavaScript細(xì)節(jié)

2011-11-08 09:21:16

虛擬化云計(jì)算VDI

2013-03-28 15:24:29

程序員

2016-03-28 14:44:02

DockerMacwindows

2012-12-25 17:40:33

2012-06-15 09:54:58

程序員編程開(kāi)發(fā)

2018-09-29 16:10:02

編程語(yǔ)言Java程序員

2012-08-30 10:05:40

編程編程語(yǔ)言程序員

2009-11-23 15:22:16

2015-08-20 14:34:25

程序員java基礎(chǔ)網(wǎng)絡(luò)編程

2013-11-14 10:05:25

程序員職業(yè)轉(zhuǎn)型

2016-01-11 11:32:41

Java程序員錯(cuò)誤

2015-02-03 02:40:33

程序員盲人程序員

2014-11-10 09:46:57

程序員

2015-10-15 09:38:21

程序員發(fā)福

2021-10-26 16:25:25

編程語(yǔ)言JavaPython

2020-07-27 08:34:17

程序員技術(shù)設(shè)計(jì)

2018-08-30 11:11:32

前端程序員基礎(chǔ)知識(shí)

2013-08-20 09:33:59

程序員
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)