十個(gè)精妙的Java編碼是什么

本篇內(nèi)容介紹了“十個(gè)精妙的Java編碼是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

10年積累的做網(wǎng)站、成都做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先網(wǎng)站設(shè)計(jì)后付款的網(wǎng)站建設(shè)流程,更有六安免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

1. 牢記C++的析構(gòu)函數(shù)

記得C++的析構(gòu)函數(shù)?不記得了?那么你真的很幸運(yùn),因?yàn)槟悴槐厝フ{(diào)試那些由于對(duì)象刪除后分配的內(nèi)存沒(méi)有被釋放而導(dǎo)致內(nèi)存泄露的代碼。感謝Sun/Oracle實(shí)現(xiàn)的垃圾回收機(jī)制吧!

盡管如此,析構(gòu)函數(shù)仍提供了一個(gè)有趣的特征。它理解逆分配順序釋放內(nèi)存。記住在Java中也是這樣的,當(dāng)你操作類析構(gòu)函數(shù)語(yǔ)法:

  • 使用JUnit的@Before和@After注釋

  • 分配,釋放JDBC資源

  • 調(diào)用super方法

還有其他各種用例。這里有一個(gè)具體的例子,說(shuō)明如何實(shí)現(xiàn)一些事件偵聽(tīng)器的SPI:

@Override
public void beforeEvent(EventContext e) {
    super.beforeEvent(e);
    // Super code before my code
}

@Override
public void afterEvent(EventContext e) {
    // Super code after my code
    super.afterEvent(e);
}

臭名昭著的哲學(xué)家就餐問(wèn)題是另一個(gè)說(shuō)明它為什么重要的好例子。 關(guān)于哲學(xué)家用餐的問(wèn)題,請(qǐng)查看鏈接:

http://adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html

規(guī)則:無(wú)論何時(shí)使用before/after, allocate/free, take/return語(yǔ)義實(shí)現(xiàn)邏輯時(shí),考慮是否逆序執(zhí)行after/free/return操作。

2. 不要相信你早期的SPI演進(jìn)判斷

向客戶提供SPI可以使他們輕松的向你的庫(kù)/代碼中注入自定義行為的方法。當(dāng)心你的SPI演進(jìn)判斷可能會(huì)迷惑你,使你認(rèn)為你 (不)打算需要附加參數(shù)。 當(dāng)然,不應(yīng)當(dāng)過(guò)早增加功能。但一旦你發(fā)布了你的SPI,一旦你決定遵循語(yǔ)義版本控制,當(dāng)你意識(shí)到在某種情況下你可能需要另外一個(gè)參數(shù)時(shí),你會(huì)真的后悔在SPI中增加一個(gè)愚蠢的單參數(shù)的方法:

interface EventListener {
    // Bad
    void message(String message);
}

如果你也需要消息ID和消息源,怎么辦?API演進(jìn)將會(huì)阻止你向上面的類型添加參數(shù)。當(dāng)然,有了Java8,你可以添加一個(gè)defender方法,“防御”你早期糟糕的設(shè)計(jì)決策:

interface EventListener {
    // Bad
    default void message(String message) {
        message(message, null, null);
    }
    // Better?
    void message(
        String message,
        Integer id,
        MessageSource source
    );
}

注意,不幸的是,defender方法不能使用final修飾符。

但是比起使用許多方法污染你的SPI,使用上下文對(duì)象(或者參數(shù)對(duì)象)會(huì)好很多。

interface MessageContext {
    String message();
    Integer id();
    MessageSource source();
}

interface EventListener {
    // Awesome!
    void message(MessageContext context);
}

比起EventListner SPI你可以更容易演進(jìn)MessageContext API,因?yàn)楹苌儆脩魰?huì)實(shí)現(xiàn)它。

規(guī)則: 無(wú)論何時(shí)指定SPI時(shí),考慮使用上下文/參數(shù)對(duì)象,而不是寫(xiě)帶有固定參數(shù)的方法。

備注: 通過(guò)專用的MessageResult類型交換結(jié)果也是一個(gè)好主意,該類型可以使用建設(shè)者API構(gòu)造它。這樣將大大增加SPI進(jìn)化的靈活性。

3. 避免返回匿名,本地或者內(nèi)部類

Swing程序員通 常只要按幾下快捷鍵即可生成成百上千的匿名類。在多數(shù)情況下,只要遵循接口、不違反SPI子類型的生命周期(SPI subtype  lifecycle),這樣做也無(wú)妨。  但是不要因?yàn)橐粋€(gè)簡(jiǎn)單的原因——它們會(huì)保存對(duì)外部類的引用,就頻繁的使用匿名、局部或者內(nèi)部類。因?yàn)闊o(wú)論它們走到哪,外部類就得跟到哪。例如,在局部類的 域外操作不當(dāng)?shù)脑挘敲凑麄€(gè)對(duì)象圖就會(huì)發(fā)生微妙的變化從而可能引起內(nèi)存泄露。

規(guī)則:在編寫(xiě)匿名、局部或內(nèi)部類前請(qǐng)三思能否將它轉(zhuǎn)化為靜態(tài)的或普通的***類,從而避免方法將它們的對(duì)象返回到更外層的域中。

注意:使用雙層花括號(hào)來(lái)初始化簡(jiǎn)單對(duì)象:

new HashMap<String, String>() {{
  put("1", "a");
  put("2", "b");
}}

這個(gè)方法利用了 JLS &sect;8.6規(guī)范里描述的實(shí)例初始化方法(initializer)。表面上看起來(lái)不錯(cuò),但實(shí)際上不提倡這種做法。因?yàn)橐鞘褂猛耆?dú)立的HashMap對(duì)象,那么實(shí)例就不會(huì)一直保存著外部對(duì)象的引用。此外,這也會(huì)讓類加載器管理更多的類。

4. 現(xiàn)在就開(kāi)始編寫(xiě)SAM!

Java8的腳步近了。伴隨著Java8帶來(lái)了lambda表達(dá)式,無(wú)論你是否喜歡。盡管你的API用戶可能會(huì)喜歡,但是你***確保他們可以盡可能 經(jīng)常的使用。因此除非你的API接收簡(jiǎn)單的“標(biāo)量”類型,比如int、long、String 、Date,否則讓你的API盡可能經(jīng)常的接收SAM。

什么是SAM?SAM是單一抽象方法[類型]。也稱為函數(shù)接口,不久會(huì)被注釋為@FunctionalInterface。這與規(guī)則2很 配,EventListener實(shí)際上就是一個(gè)SAM。***的SAM只有一個(gè)參數(shù),因?yàn)檫@將會(huì)進(jìn)一步簡(jiǎn)化lambda表達(dá)式的編寫(xiě)。設(shè)想編寫(xiě)

listeners.add(c -> System.out.println(c.message()));

來(lái)替代

listeners.add(new EventListener() {
  @Override
  public void message(MessageContext c) {
    System.out.println(c.message()));
  }
});

設(shè)想以JOOX的方式來(lái)處理XML。JOOX就包含很多的SAM:

$(document)
  // Find elements with an ID
  .find(c -> $(c).id() != null)
  // Find their child elements
  .children(c -> $(c).tag().equals("order"))
  // Print all matches
  .each(c -> System.out.println($(c)))

規(guī)則:對(duì)你的API用戶好一點(diǎn)兒,從現(xiàn)在開(kāi)始編寫(xiě)SAM/函數(shù)接口。

備注:有許多關(guān)于Java8 lambda表達(dá)式和改善的Collections API的有趣的博客:

  • http://blog.informatech.cr/2013/04/10/java-optional-objects/

  • http://blog.informatech.cr/2013/03/25/java-streams-api-preview/

  • http://blog.informatech.cr/2013/03/24/java-streams-preview-vs-net-linq/

  • http://blog.informatech.cr/2013/03/11/java-infinite-streams/

5.避免讓方法返回null

我曾寫(xiě)過(guò)1、2篇關(guān)于java NULLs的文章,也講解過(guò)Java8中引入新的Optional類。從學(xué)術(shù)或?qū)嵱玫慕嵌葋?lái)看,這些話題還是比較有趣的。

盡管現(xiàn)階段Null和NullPointerException依然是Java的硬傷,但是你仍可以設(shè)計(jì)出不會(huì)出現(xiàn)任何問(wèn)題的API。在設(shè)計(jì)API時(shí),應(yīng)當(dāng)盡可能的避免讓方法返回null,因?yàn)槟愕挠脩艨赡軙?huì)鏈?zhǔn)秸{(diào)用方法:

initialise(someArgument).calculate(data).dispatch();

從上面代碼中可看出,任何一個(gè)方法都不應(yīng)返回null。實(shí)際上,在通常情況下使用null會(huì)被認(rèn)為相當(dāng)?shù)漠愵?。?jQuery或 jOOX這樣的庫(kù)在可迭代的對(duì)象上已完全的摒棄了null。

Null通常用在延遲初始化中。在許多情況下,在不嚴(yán)重影響性能的條件下,延遲初始化也應(yīng)該被避免。實(shí)際上,如果涉及的數(shù)據(jù)結(jié)構(gòu)過(guò)于龐大,那么就要慎用延遲初始化。

規(guī)則:無(wú)論何時(shí)方法都應(yīng)避免返回null。null僅用來(lái)表示“未初始化”或“不存在”的語(yǔ)義。

6.設(shè)計(jì)API時(shí)永遠(yuǎn)不要返回空(null)數(shù)組或List

盡管在一些情況下方法返回值為null是可以的,但是絕不要返回空數(shù)組或空集合!請(qǐng)看 java.io.File.list()方法,它是這樣設(shè)計(jì)的:

此方法會(huì)返回一個(gè)指定目錄下所有文件或目錄的字符串?dāng)?shù)組。如果目錄為空(empty)那么返回的數(shù)組也為空(empty)。如果指定的路徑不存在或發(fā)生I/O錯(cuò)誤,則返回null。

因此,這個(gè)方法通常要這樣使用:

File directory = // ...

if (directory.isDirectory()) {
  String[] list = directory.list();

if (list != null) {
    for (String file : list) {
      // ...
    }
  }
}

大家覺(jué)得null檢查有必要嗎?大多數(shù)I/O操作會(huì)產(chǎn)生IOExceptions,但這個(gè)方法卻只返回了null。Null是無(wú)法存放I/O錯(cuò)誤信息的。因此這樣的設(shè)計(jì),有以下3方面的不足:

  • Null無(wú)助于發(fā)現(xiàn)錯(cuò)誤

  • Null無(wú)法表明I/O錯(cuò)誤是由File實(shí)例所對(duì)應(yīng)的路徑不正確引起的

  • 每個(gè)人都可能會(huì)忘記判斷null情況

以集合的思維來(lái)看待問(wèn)題的話,那么空的(empty)的數(shù)組或集合就是對(duì)“不存在”的***實(shí)現(xiàn)。返回空(null)數(shù)組或集合幾乎是無(wú)任何實(shí)際意義的,除非用于延遲初始化。

規(guī)則:返回的數(shù)組或集合不應(yīng)為null。

7. 避免狀態(tài),使用函數(shù)

HTTP的好處是無(wú)狀態(tài)。所有相關(guān)的狀態(tài)在每次請(qǐng)求和響應(yīng)中轉(zhuǎn)移。這是REST命名的本質(zhì):含狀態(tài)傳輸(Representational  state  transfer)。在Java中這樣做也很贊。當(dāng)方法接收狀態(tài)參數(shù)對(duì)象的時(shí)候從規(guī)則2的角度想想這件事。如果狀態(tài)通過(guò)這種對(duì)象傳輸,而不是從外邊操作狀 態(tài),那么事情將會(huì)更簡(jiǎn)單。以JDBC為例。下述例子從一個(gè)存儲(chǔ)的程序中讀取一個(gè)光標(biāo)。

CallableStatement s =
  connection.prepareCall("{ ? = ... }");

// Verbose manipulation of statement state:
s.registerOutParameter(1, cursor);
s.setString(2, "abc");
s.execute();
ResultSet rs = s.getObject(1);

// Verbose manipulation of result set state:
rs.next();
rs.next();

這使得JDBC API如此的古怪。每個(gè)對(duì)象都是有狀態(tài)的,難以操作。具體的說(shuō),有兩個(gè)主要的問(wèn)題:

  • 在多線程環(huán)境很難正確的處理有狀態(tài)的API

  • 很難讓有狀態(tài)的資源全局可用,因?yàn)闋顟B(tài)沒(méi)有被描述

規(guī)則:更多的以函數(shù)風(fēng)格實(shí)現(xiàn)。通過(guò)方法參數(shù)轉(zhuǎn)移狀態(tài)。極少操作對(duì)象狀態(tài)。

8. 短路式 equals()

這是一個(gè)比較容易操作的方法。在比較復(fù)雜的對(duì)象系統(tǒng)中,你可以獲得顯著的性能提升,只要你在所有對(duì)象的equals()方法中首先進(jìn)行相等判斷:

@Override
public boolean equals(Object other) {
  if (this == other) return true;
  // 其它相等判斷邏輯...
}

注意,其它短路式檢查可能涉及到null值檢查,所以也應(yīng)當(dāng)加進(jìn)去:

@Override
public boolean equals(Object other) {
  if (this == other) return true;
  if (other == null) return false;
  // Rest of equality logic...
}

規(guī)則: 在你所有的equals()方法中使用短路來(lái)提升性能。

9. 盡量使方法默認(rèn)為final

有些人可能不同意這一條,因?yàn)槭狗椒J(rèn)為final與Java開(kāi)發(fā)者的習(xí)慣相違背。但是如果你對(duì)代碼有完全的掌控,那么使方法默認(rèn)為final是肯定沒(méi)錯(cuò)的:

  • 如果你確實(shí)需要覆蓋(override)一個(gè)方法(你真的需要?),你仍然可以移除final關(guān)鍵字

  • 你將永遠(yuǎn)不會(huì)意外地覆蓋(override)任何方法

這特別適用于靜態(tài)方法,在這種情況下“覆蓋”(實(shí)際上是遮蔽)幾乎不起作用。我最近在Apache Tika中遇到了一個(gè)很糟糕的遮蔽靜態(tài)方法的例子??匆幌拢?/p>

  • TaggedInputStream.get(InputStream)

  • TikaInputStream.get(InputStream)

TikaInputStream擴(kuò)展了TaggedInputStream,以一種相對(duì)不同的實(shí)現(xiàn)遮蔽了它的靜態(tài)get()方法。

與常規(guī)方法不同,靜態(tài)方法不能互相覆蓋,因?yàn)檎{(diào)用的地方在編譯時(shí)就綁定了靜態(tài)方法調(diào)用。如果你不走運(yùn),你可能會(huì)意外獲得錯(cuò)誤的方法。

規(guī)則:如果你完全掌控你的API,那么使盡可能多的方法默認(rèn)為final。

10. 避免方法(T&hellip;)簽名

在特殊場(chǎng)合下使用“accept-all”變量參數(shù)方法接收一個(gè)Object&hellip;參數(shù)就沒(méi)有錯(cuò)的:

void acceptAll(Object... all);

編寫(xiě)這樣的方法為Java生態(tài)系統(tǒng)帶來(lái)一點(diǎn)兒JavaScript的感覺(jué)。當(dāng)然你可能想要根據(jù)真實(shí)的情形限制實(shí)際的類型,比如String&hellip;。因?yàn)槟悴幌胍拗铺啵憧赡軙?huì)認(rèn)為用泛型T取代Object是一個(gè)好想法:

void acceptAll(T... all);

但是不是。T總是會(huì)被推斷為Object。實(shí)際上你可能僅僅認(rèn)為上述方法中不能使用泛型。更重要的是你可能認(rèn)為你可以重載上述方法,但是你不能:

void acceptAll(T... all);
void acceptAll(String message, T... all);

這看起來(lái)好像你可以可選地傳遞一個(gè)String消息到方法。但是這個(gè)調(diào)用會(huì)發(fā)生什么呢?

acceptAll("Message", 123, "abc");

編譯器將T推斷為<? extends Serializable & Comparable<?>>,這將會(huì)使調(diào)用不明確!

所以無(wú)論何時(shí)你有一個(gè)“accept-all”簽名(即使是泛型),你將永遠(yuǎn)不能類型安全地重載它。API使用者可能僅僅在走運(yùn)的時(shí)候才會(huì)讓編譯器“偶然地”選擇“正確的”方法。但是也可能使用accept-all方法或者無(wú)法調(diào)用任何方法。

規(guī)則: 如果可能,避免“accept-all”簽名。如果不能,不要重載這樣的方法。

“十個(gè)精妙的Java編碼是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

網(wǎng)站名稱:十個(gè)精妙的Java編碼是什么
本文URL:http://muchs.cn/article48/geddep.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供外貿(mào)建站、微信小程序、定制開(kāi)發(fā)品牌網(wǎng)站設(shè)計(jì)、云服務(wù)器、商城網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

綿陽(yáng)服務(wù)器托管