久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

帶你快速看完9.8分神作《Effective Java》—— 方法篇

 小王曾是少年 2022-08-24 發(fā)布于江蘇

🍊 Java學(xué)習(xí):Java從入門到精通總結(jié)

🍊 Spring系列推薦:Spring源碼解析

📆 最近更新:2021年12月16日

🍊 個(gè)人簡(jiǎn)介:通信工程本碩💪,、阿里新晉猿同學(xué)🌕。我的故事充滿機(jī)遇,、挑戰(zhàn)與翻盤,歡迎關(guān)注作者來(lái)共飲一杯雞湯

🍊 點(diǎn)贊 👍 收藏 ?留言 📝 都是我最大的動(dòng)力!

豆瓣評(píng)分9.8的圖書《Effective Java》,是當(dāng)今世界頂尖高手Josh Bloch的著作,在我之前的文章里我也提到過(guò),編程就像練武,既需要外在的武功招式(編程語(yǔ)言、工具、中間件等等),也需要修煉心法(設(shè)計(jì)模式,、源碼等等)學(xué)霸,、學(xué)神OR開掛

我也始終有一個(gè)觀點(diǎn):看視頻跟著敲代碼永遠(yuǎn)只是入門,從書籍里學(xué)到了多少東西才決定了你的上限,。

在這里插入圖片描述

我個(gè)人在Java領(lǐng)域也已經(jīng)學(xué)習(xí)了近5年,在修煉“內(nèi)功”的方面也通過(guò)各種途徑接觸到了一些編程規(guī)約,例如阿里巴巴的泰山版規(guī)約,在此基礎(chǔ)下讀這本書的時(shí)候仍是讓我受到了很大的沖激,學(xué)習(xí)到了很多約定背后的細(xì)節(jié)問(wèn)題,還有一些讓我欣賞此書的點(diǎn)是,書中對(duì)于編程規(guī)約的解釋讓我感到十分受用,并愿意將他們應(yīng)用在我的工作中,也提醒了我要把閱讀JDK源碼的任務(wù)提上日程,。

最后想分享一下我個(gè)人目前的看法,內(nèi)功修煉不像學(xué)習(xí)一個(gè)新的工具那么簡(jiǎn)單,其主旨在于踏實(shí),深入探索底層原理的過(guò)程很緩慢并且是艱辛的,但一旦開悟,修為一定會(huì)突破瓶頸,達(dá)到更高的境界,這遠(yuǎn)遠(yuǎn)不是我通過(guò)一兩篇博客就能學(xué)到的東西。

接下來(lái)就針對(duì)此書列舉一下我的收獲與思考,。

不過(guò)還是要吐槽一下的是翻譯版屬實(shí)讓人一言難盡,有些地方會(huì)有誤導(dǎo)的效果,你比如java語(yǔ)言里extends是繼承的關(guān)鍵字,書本中全部翻譯成了擴(kuò)展 就完全不是原來(lái)的意思了,。所以建議有問(wèn)題的地方對(duì)照英文原版進(jìn)行語(yǔ)義上的理解。

沒有時(shí)間讀原作的同學(xué)可以參考我這篇文章,。


文章目錄

49 檢查參數(shù)的有效性

當(dāng)編寫方法或構(gòu)造方法時(shí),都應(yīng)該考慮其參數(shù)應(yīng)該有哪些限制,。應(yīng)該把這些限制寫到文檔里,并在方法體的開頭顯式檢查


大多數(shù)方法和構(gòu)造方法對(duì)于傳遞給他們的參數(shù)有一些限制,。例如,索引值必須是非負(fù)數(shù),對(duì)象引用必須為非null,。我們應(yīng)該在文檔里清楚地指明這些限制,并且在方法的最開始進(jìn)行檢查。

如果沒有驗(yàn)證參數(shù)的有效性,可能會(huì)導(dǎo)致違背失敗原子性

  1. 該方法可能在處理過(guò)程中失敗,該方法可能會(huì)出現(xiàn)費(fèi)解的異常
  2. 該方法可以正常返回,會(huì)默默地計(jì)算出錯(cuò)誤的結(jié)果
  3. 該方法可以正常返回,但是使得某個(gè)對(duì)象處于受損狀態(tài),在將來(lái)某個(gè)時(shí)間點(diǎn)會(huì)報(bào)錯(cuò)

對(duì)于publicprotected方法,要用Java文檔的@throws注解來(lái)說(shuō)明會(huì)拋出哪些異常,通常為:IllegalArgumentExceptionIndexOutOfBoundsExceptionNullPointerException,例如:

/**
 * Returns a BigInteger whose value is (this mod m). This method
 * differs from the remainder method in that it always returns a
 * non-negative BigInteger.
 *
 * @param m the modulus, which must be positive
 * @return this mod m
 * @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {
	if (m.signum() <= 0)
		throw new ArithmeticException("Modulus <= 0: " + m);
	
	... // Do the computation
}

在Java 7中添加的 Objects.requireNonNull 方法靈活方便,因此沒有理由再手動(dòng)執(zhí)行null檢查,。該方法返回其輸入的值,因此可以在使用值的同時(shí)執(zhí)行null檢查:

this.strategy = Objects.requireNonNull(strategy, "strategy");

對(duì)于不是public的方法,通常應(yīng)該使用斷言來(lái)檢查參數(shù):

private static void sort(long a[], int offset, int length) {
	assert a != null;
	assert offset >= 0 && offset <= a.length;
	assert length >= 0 && length <= a.length - offset;
	... // Do the computation
}

不同于一般的有效性檢查,如果它們沒有起到作用,本質(zhì)上也沒有成本開銷,。


在某些場(chǎng)景下,有效性檢查的成本很高,且在計(jì)算過(guò)程里也已經(jīng)完成了有效性檢查,例如對(duì)象列表排序的方法Collections.sort(List)

如果List里的對(duì)象不能互相比較,就會(huì)拋ClassCastException異常,這正是sort方法該做的事情,所以提前檢查列表中的元素是否可以互相比較并沒有很大意義,。


有些計(jì)算會(huì)隱式執(zhí)行必要的有效性檢查,如果檢查失敗則會(huì)拋異常,這個(gè)異??赡芎臀臋n里標(biāo)明的不同,此時(shí)就應(yīng)該使用異常轉(zhuǎn)換將其轉(zhuǎn)換成正確的異常。


50 必要時(shí)進(jìn)行保護(hù)性拷貝

Java是一門安全的語(yǔ)言,它對(duì)于緩存區(qū)溢出,、數(shù)組越界,、非法指針以及其他內(nèi)存損壞錯(cuò)誤都自動(dòng)免疫。


但僅管如此,我們也必須保護(hù)性地編寫程序,因?yàn)榇a隨時(shí)可能會(huì)遭受攻擊,。

如果沒有對(duì)象的幫助,另一個(gè)類是不可能修改對(duì)象的內(nèi)部狀態(tài)的,但對(duì)象可能會(huì)在無(wú)意的情況下提供這樣的幫助。例如,下面的代碼表示一個(gè)不可變的時(shí)間周期:

public final class Period {
	private final Date start;
	private final Date end;
	
	/**
	* @param start the beginning of the period
	* @param end the end of the period; must not precede start
	* @throws IllegalArgumentException if start is after end
	* @throws NullPointerException if start or end is null
	*/
	public Period(Date start, Date end) {
		if (start.compareTo(end) > 0)
			throw new IllegalArgumentException(start + " after " + end);
		
		this.start = start;
		this.end = end;
	}
	
	public Date start() {
		return start;
	}
	
	public Date end() {
		return end;
	}
	... // Remainder omitted
}

上面代碼雖然強(qiáng)制令period 實(shí)例的開始時(shí)間小于結(jié)束時(shí)間,。然而,Date 類是可變的,很容易違反這個(gè)約束:

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!

從Java 8 開始,解決此問(wèn)題的顯而易?的方法是使用 Instant(或LocalDateTimeZonedDateTime)代替Date,因?yàn)樗麄兪遣豢勺兊?。?code>Date在老代碼里仍有使用的地方,為了保護(hù) Period 實(shí)例的內(nèi)部不受這種攻擊,可以使用拷?來(lái)做 Period 實(shí)例的組件:

public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());

    if (this.start.compareTo(this.end) > 0)
        throw new IllegalArgumentException(this.start + " after " + this.end);
}

有了新的構(gòu)造方法后,前面的攻擊將不會(huì)對(duì)Period 實(shí)例產(chǎn)生影響。注意:保護(hù)性拷?是在檢查參數(shù)的有效性之前進(jìn)行的,且有效性檢查是在拷貝實(shí)例上進(jìn)行的,。

這樣做可以避免從檢查參數(shù)開始到拷貝參數(shù)之間的時(shí)間段內(nèi),其他的線程改變類的參數(shù)

也被稱作 Time-Of-Check / Time-Of-Use 或 TOCTOU攻擊


看了之前章節(jié)的同學(xué)可能有疑問(wèn)了,這里為什么沒用clone方法來(lái)進(jìn)行保護(hù)性拷貝?

答案是:Date不是final的,所以clone方法不能保證返回類確實(shí)是 java.util.Date 的對(duì)象,也可能返回一個(gè)惡意的子類實(shí)例,。


但是普通方法就不一樣了,它們?cè)谶M(jìn)行保護(hù)性拷貝是允許使用clone方法,原因是我們知道Period內(nèi)部的Date對(duì)象類型確實(shí)是java.util.Date

對(duì)于參數(shù)類型可能被惡意子類化的參數(shù),不要使用 clone 方法進(jìn)行防御性拷?,。


其實(shí),改變Period實(shí)例仍是有可能的:

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // Modifies internals of p!

修改方法也很簡(jiǎn)單:

public Date start() {
	return new Date(start.getTime());
}

public Date end() {
	return new Date(end.getTime());
}

上面的分析帶來(lái)的啟發(fā)是:應(yīng)該盡量使用不可變對(duì)象作為對(duì)象內(nèi)部的組件,這樣就不必?fù)?dān)心保護(hù)性拷?,。在 Period 示例中,使用Instant(或LocalDateTimeZonedDateTime)。另一個(gè)選項(xiàng)是存儲(chǔ)Date.getTime() 返回的long類型來(lái)代替Date引用,。


最后,如果拷貝成本較大的話,并且我們新人使用它的客戶端不會(huì)惡意修改組件,則可以在文檔中指明客戶端不得修改受到影響的組件,以此來(lái)代替保護(hù)性拷貝,。


51 謹(jǐn)慎設(shè)計(jì)方法

這一條介紹了若干經(jīng)驗(yàn):

1. 謹(jǐn)慎給方法起名

  • 方法名應(yīng)該選易于理解的,并且與同一個(gè)包里其他名稱的風(fēng)格一致
  • 選擇大眾認(rèn)可的名稱

2. 不要過(guò)于追求提供便利的方法
方法太多會(huì)使類難以學(xué)習(xí)、使用、文檔化,、維護(hù),。只有當(dāng)一項(xiàng)操作被經(jīng)常用到時(shí),才考慮為它提供快捷方式(shorthand)

3. 避免過(guò)長(zhǎng)的參數(shù)列表,相同類型的長(zhǎng)參數(shù)序列格外有害

參數(shù)個(gè)數(shù)不超過(guò)4個(gè)

有三種技巧可以縮短過(guò)長(zhǎng)的參數(shù)列表:

  1. 把一個(gè)方法分解成多個(gè)方法,每個(gè)方法只需要這些參數(shù)的一個(gè)子集。例如:java.util.List接口里沒有提供在子列表中查找元素的第一個(gè)索引和最后一個(gè)索引的方法,。相反,它提供了 subList 方法,返回子列表,。此方法可以與 indexOflastIndexOf 方法結(jié)合使用來(lái)達(dá)到所需的功能。

  2. 創(chuàng)建輔助類用來(lái)保存參數(shù)的分組,。例如:編寫一個(gè)表示紙牌游戲的類,發(fā)現(xiàn)需要兩個(gè)參數(shù)來(lái)表示紙牌的點(diǎn)數(shù)和花色,這時(shí)就可以創(chuàng)建一個(gè)類來(lái)表示卡片,。

  3. 從對(duì)象構(gòu)建到方法調(diào)用全都采用Builder模式


4. 優(yōu)先使用接口作為入?yún)㈩愋?/strong>
只要有適當(dāng)?shù)慕涌诳捎脕?lái)定義參數(shù),就優(yōu)先使用這個(gè)接口,而不是使用實(shí)現(xiàn)該接口的類。例如:在編寫方法時(shí)使用Map接口作為參數(shù)


5. 對(duì)于boolean型參數(shù),優(yōu)先使用有兩個(gè)元素的枚舉
例如,有一個(gè) Thermometer 類型的靜態(tài)工廠方法,這個(gè)方法的簽名需要以下這個(gè)枚舉的值:

public enum TemperatureScale { FAHRENHEIT, CELSIUS }

Thermometer.newInstance(TemperatureScale.CELSIUS) 不僅比Thermometer.newInstance(true) 更有意義,而且可以在將來(lái)的版本中將新的枚舉值添加到 TemperatureScale 中,而無(wú)需向 Thermometer 添加新的靜態(tài)工廠,。


52 慎用重載

下面這個(gè)程序試圖將一個(gè)集合進(jìn)行分類:

public class CollectionClassifier {

    public static String classify(Set<?> s) {
        return "Set";
    }

    public static String classify(List<?> lst) {
        return "List";
    }

    public static String classify(Collection<?> c) {
        return "Unknown Collection";
    }

    public static void main(String[] args) {
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<BigInteger>(),
                new HashMap<String, String>().values()
        };

        for (Collection<?> c : collections)
            System.out.println(classify(c));
    }
}

運(yùn)行結(jié)果是打印了三次Unknown Collection,。為什么會(huì)這樣呢?

原因就是classify方法被重載了,要調(diào)用哪個(gè)重載方法是在編譯時(shí)做出決定的。for循環(huán)里參數(shù)的編譯時(shí)類型一直是Collection<?>,所以唯一適合的重載方法是classify(Collection<?> c)


有一個(gè)很有意思的事實(shí):重載(overloaded)方法的選擇是靜態(tài)的,重寫(overridden)方法的選擇是動(dòng)態(tài)的,。

重寫方法的選擇是在運(yùn)行時(shí)進(jìn)行的,依據(jù)是被調(diào)用的方法所在的對(duì)象的運(yùn)行時(shí)類型,。


以下面這個(gè)例子具體說(shuō)明:

class Wine {
    String name() {
        return "wine";
    }
}

class SparklingWine extends Wine {
    @Override
    String name() {
        return "sparkling wine";
    }
}

class Champagne extends SparklingWine {
    @Override
    String name() {
        return "champagne";
    }
}

public class Overriding {
    public static void main(String[] args) {
        List<Wine> wineList = Arrays.asList(
                new Wine(), new SparklingWine(), new Champagne());

        for (Wine wine : wineList)
            System.out.println(wine.name());
    }
}

這段代碼打印出wine,sparkling wine和champagne,盡管在每次迭代里,實(shí)例的編譯類型都是Wine,但總是會(huì)執(zhí)行最具體(most specific)的重寫方法,也就是在子類上調(diào)用的就執(zhí)行被子類覆蓋的方法。


CollectionClassifier示例中,程序的目的是根據(jù)參數(shù)的運(yùn)行時(shí)類型自動(dòng)執(zhí)行適當(dāng)?shù)姆椒ㄖ剌d來(lái)辨別參數(shù)的類型,。但方法重載完全沒有提供這樣的功能,這段代碼最佳修改方案是:用單個(gè)方法來(lái)替換這三個(gè)重載的classify方法,代碼邏輯里用instanceof判斷:

public static String classify(Collection<?> c) {
	return c instanceof Set ? "Set" : c instanceof List ? "List" : "Unknown Collection";
}

如果API的普通用戶根本不知道哪個(gè)重載會(huì)被調(diào)用,使用這樣的API就會(huì)報(bào)錯(cuò),。所以,應(yīng)該避免混淆使用重載

安全保守的策略是:一個(gè)安全和保守的策略是永遠(yuǎn)不要編寫兩個(gè)具有相同參數(shù)數(shù)量的重載,。

因?yàn)槲覀兪冀K可以給方法起不同的名字,避免使用重載,。


例如,考慮ObjectOutputStream類。對(duì)于每個(gè)類型,它的write方法都有一種變體,例如writeBoolean(boolean),、writeInt(int)writeLong(long),。這種命名模式的另一個(gè)好處是,可以為read方法提供相應(yīng)的名稱,例如readBoolean()readInt()readLong(),。


一個(gè)類的多個(gè)構(gòu)造器總是重載的,可以選擇導(dǎo)出靜態(tài)工廠,。


對(duì)于每一對(duì)重載方法,至少要有一個(gè)形參在這兩個(gè)重載中具有「完全不同的」類型。這時(shí)主要的混淆根源就沒有了,。例如ArrayList有接受int的構(gòu)造方法和接受Collection的構(gòu)造方法,。


Java有一個(gè)自動(dòng)裝箱的概念,他們的出現(xiàn)也引入了一些麻煩:

public class SetList {
    public static void main(String[] args) {
        Set<Integer> set = new TreeSet<>();
        List<Integer> list = new ArrayList<>();

        for (int i = -3; i < 3; i++) {
            set.add(i);
            list.add(i);
        }

        for (int i = 0; i < 3; i++) {
            set.remove(i);
            list.remove(i);
        }

        System.out.println(set + " " + list);
    }
}

實(shí)際上,程序從Set中刪除非負(fù)值,從List中刪除奇數(shù)值,并打印 [-3, -2, -1] 和 [-2, 0, 2]。


  • set.remove(i)選擇重載了remove(E)方法,執(zhí)行結(jié)果正確
  • list.remove(i)的調(diào)用選擇重載remove(int i)方法,它將刪除列表中指定位置的元素,所以最終打印 [-2, 0, 2]

有兩種手段可以解決這個(gè)問(wèn)題:

  1. 強(qiáng)制轉(zhuǎn)換list.remove的參數(shù)為Integer
  2. 調(diào)用Integer.valueOf(i),將結(jié)果傳遞list.remove方法
for (int i = 0; i < 3; i++) {
	set.remove(i);
	list.remove((Integer) i); // or remove(Integer.valueOf(i))
}

在Java 8中添加Lambda表達(dá)式和方法引用以后,進(jìn)一步增加了重載混淆的可能性,。

new Thread(System.out::println).start();

ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(System.out::println);

Thread 構(gòu)造方法調(diào)用和submit方法調(diào)用看起來(lái)很相似,但是前者編譯而后者不編譯,。參數(shù)是相同的(System.out::println)。因?yàn)?code>sumbit方法有一個(gè)帶有Callable <T>參數(shù)的重載,而Thread構(gòu)造方法卻沒有,。在submit這里不知道應(yīng)該調(diào)用哪個(gè)方法,。


在更新現(xiàn)有類時(shí),可能會(huì)違反這一條目中的指導(dǎo)原則。例如,從Java 4開始就有一個(gè)contentEquals(StringBuffer)方法,。在Java 5中,添加了contentEquals(CharSequence)接口,。但只要這兩個(gè)方法返回相同的結(jié)果就可以,例如下面的代碼:

public boolean contentEquals(StringBuffer sb) {
	return contentEquals((CharSequence) sb);
}

原因是這兩個(gè)重載互相調(diào)用,。


Java類庫(kù)在很大程度上遵循了這一條中的建議,但是有一些類違反了它。例如,String導(dǎo)出兩個(gè)重載的靜態(tài)工廠方法valueOf(char[])valueOf(Object),這應(yīng)該被看成是一種反常行為,。


53 慎用可變參數(shù)

可變參數(shù)方法接受0個(gè)或多個(gè)指定類型的參數(shù),首先創(chuàng)建一個(gè)數(shù)組,其大小是在調(diào)用位置傳遞的參數(shù)數(shù)量,然后將參數(shù)值放入數(shù)組中,最后將數(shù)組傳遞給方法,。


例如,這里有一個(gè)可變參數(shù)方法,返回入?yún)⒌目偤?#xff1a;

static int sum(int... args) {
int sum = 0;
for (int arg : args)
	sum += arg;
return sum;
}

有時(shí),編寫一個(gè)需要某種類型的一個(gè)或多個(gè)參數(shù)的方法是合適的,而不是0個(gè)或者多個(gè)??梢栽谶\(yùn)行時(shí)檢查數(shù)組?
度:

static int min(int... args) {
	if (args.length == 0)
		throw new IllegalArgumentException("Too few arguments");
	int min = args[0];
	for (int i = 1; i < args.length; i++)
		if (args[i] < min)
			min = args[i];
	return min;
}

最嚴(yán)重的是,如果客戶端在沒有參數(shù)的情況下調(diào)用此方法,則它在運(yùn)行時(shí)而不是在編譯時(shí)失敗,。

有一種更好的方法可以達(dá)到預(yù)期的效果。聲明方法采用兩個(gè)參數(shù),一個(gè)指定類型的普通參數(shù),另一個(gè)此類型的可變參數(shù),。

static int min(int firstArg, int... remainingArgs) {
	int min = firstArg;
	for (int arg : remainingArgs)
		if (arg < min)
			min = arg;
	return min;
}

在性能關(guān)鍵的情況下使用可變參數(shù)時(shí)要小心,。每次調(diào)用可變參數(shù)方法都會(huì)導(dǎo)致數(shù)組分配和初始化。

還有一種模式可以讓你如愿以償:

public void foo() { }

public void foo(int a1) { }

public void foo(int a1, int a2) { }

public void foo(int a1, int a2, int a3) { }

public void foo(int a1, int a2, int a3, int... rest) { }

當(dāng)參數(shù)數(shù)目超過(guò)3個(gè)時(shí)需要?jiǎng)?chuàng)建數(shù)組,。

EnumSet類的靜態(tài)工廠使用這種方法,將創(chuàng)建枚舉集合的成本降到最低,。


54 返回空的數(shù)組或集合,不要返回null

像如下的方法并不罕?:

private final List<Cheese> cheesesInStock = ...;

/**
* @return a list containing all of the cheeses in the shop,
* or null if no cheeses are available for purchase.
*/
public List<Cheese> getCheeses() {
	return cheesesInStock.isEmpty() ? null : new ArrayList<>(cheesesInStock);
}

把沒有奶酪(Cheese)可買的情況當(dāng)做一種特例,這是不合常理的。這樣需要在客戶端中必須有額外的代碼來(lái)處理null的返回值:

List<Cheese> cheeses = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
	System.out.println("Jolly good, just the thing.");

這樣做很容易出錯(cuò),因?yàn)榫帉懣蛻舳说某绦騿T可能忘記編寫特殊情況代碼來(lái)處理null返回,。


下面是返回可能為空的集合的典型代碼,。一般情況下,這些都是必須的:

public List<Cheese> getCheeses() {
	return new ArrayList<>(cheesesInStock);
}

如果有證據(jù)表明分配空集合會(huì)損害性能,可以通過(guò)重復(fù)返回相同的不可變空集合來(lái)避免多次分配

// Optimization - avoids allocating empty collections
public List<Cheese> getCheeses() {
	return cheesesInStock.isEmpty() ? Collections.emptyList() : new ArrayList<>(cheesesInStock);
}

數(shù)組的情況與集合的情況相同。永遠(yuǎn)不要返回null,而是返回?度為零的數(shù)組,。

// Optimization - avoids allocating empty arrays
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheeses() {
	return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}

55 謹(jǐn)慎返回optional

在Java 8之前,編寫在特定情況下無(wú)法返回任何值的方法時(shí),可以采用兩種方法,。要么拋出異常,要么返回null。這兩種方式都不完美:

  1. 拋出異常代價(jià)很高,因?yàn)樵趧?chuàng)建異常時(shí)捕獲整個(gè)堆棧trace
  2. 返回null有可能拋NullPointerException異常

在Java 8中,還有第三種方法來(lái)編寫可能無(wú)法返回任何值的方法,。Optional<T>類表示一個(gè)不可變的容器,它可以包含一個(gè)非null的T引用,也可以什么都不包含,。

不包含任何內(nèi)容的Optional被稱為空(empty)。非空的包含值稱的Optional被稱為存在(present)

返回Optional的方法比拋出異常的方法更靈活,、更容易使用,而且比返回null的方法更不容易出錯(cuò),。

例如在第30條中,有一個(gè)根據(jù)集合中元素的自然順序計(jì)算集合最大值的方法:

public static <E extends Comparable<E>> E max(Collection<E> c) {
	if (c.isEmpty())
		throw new IllegalArgumentException("Empty collection");
	
	E result = null;
	for (E e : c)
		if (result == null || e.compareTo(result) > 0)
			result = Objects.requireNonNull(e);
	return result;
}

如果給定集合為空,此方法將拋出IllegalArgumentException異常。更好的替代方法是返回Optional<E>,。下面是修改后的方法:

public static <E extends Comparable<E>>
	Optional<E> max(Collection<E> c) {
	if (c.isEmpty())
		return Optional.empty();
	
	E result = null;
	for (E e : c)
		if (result == null || e.compareTo(result) > 0)
			result = Objects.requireNonNull(e);
	return Optional.of(result);
}

將null傳遞給Optional.of(value)是一個(gè)編程錯(cuò)誤,會(huì)拋NullPointerException異常,。Optional.ofNullable(value)方法接受一個(gè)可能為null的值,如果傳入null則返回一個(gè)空的Optional


Stream 上的很多終止操作返回Optional,??梢杂?code>Stream重寫max方法,Streammax 操作會(huì)為我們生成Optional的工作:

public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
	return c.stream().max(Comparator.naturalOrder());
}

如果方法返回一個(gè)Optional,則客戶端可以選擇在方法無(wú)法返回值時(shí)要采取的操作。有以下兩種方式:

1. 指定默認(rèn)值

String lastWordInLexicon = max(words).orElse("No words...");

2. 拋出任何適當(dāng)?shù)漠惓?/strong>

Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);

注意,我們傳遞的是異常工廠,而不是實(shí)際的異常,。這避免了創(chuàng)建異常的開銷


有時(shí)候,可能會(huì)遇到這樣一種情況:獲取默認(rèn)值的代價(jià)很高,我們希望避免這種代價(jià)。對(duì)于這些情況,Optional提供了一個(gè)方法orElseGet,傳入一個(gè)Supplier<T>,。


Optional還提供了isPresent()方法,可以將其視為安全閥,。如果Optional包含值,則返回true;否則返回false。


當(dāng)使用Stream時(shí),經(jīng)常會(huì)遇到Stream<Optional<T>>,為了推動(dòng)進(jìn)程還需要一個(gè)包含了非空optional中所有元素的Stream<T>,。Java 8里可以這樣寫:

streamOfOptionals
	.filter(Optional::isPresent)
	.map(Optional::get)

并不是所有的返回類型都能從Optional的處理中獲益,。容器類型,包括集合,、映射、Stream,、數(shù)組和Optional,不應(yīng)該封裝在Optional中,。與其返回一個(gè)空的Optional<List<T>>,不還如返回一個(gè)空的List<T>


那么什么時(shí)候應(yīng)該聲明一個(gè)方法來(lái)返回Optional<T> 而不是T 呢?

如果可能無(wú)法返回結(jié)果,并且在沒有返回結(jié)果,客戶端還必須執(zhí)行特殊處理的情況下,則應(yīng)聲明返回Optional <T>的方法,。


使用Optional還有一些其他的注意事項(xiàng):

  1. 永遠(yuǎn)不應(yīng)該返回基本包裝類型的Optional(小型的BooleanByteCharacterShortFloat 除外)

  2. 不適合將optional作為鍵,、值、集合或數(shù)組中的元素

  3. 除了作為返回值之外,不要在任何其他地方中使用Optional


56 為所有已公開的API 元素編寫文檔注釋

如果API要可用,就必須對(duì)其編寫文檔化,。


要正確地記錄API,必須在每個(gè)導(dǎo)出的類,、接口、構(gòu)造方法,、方法和屬性聲明之前加上文檔注釋,。如果一個(gè)類是可序列化的,應(yīng)該對(duì)它的序列化形式編寫文檔。puiblic類不應(yīng)該使用無(wú)參構(gòu)造方法,因?yàn)闊o(wú)法為它們提供文檔注釋,。要編寫可維護(hù)的代碼,還應(yīng)該為所有沒有被導(dǎo)出的類,、接口、構(gòu)造方法,、方法和屬性編寫文檔注釋,盡管這些注釋不需要像導(dǎo)出API元素那樣完整,。


方法的文檔注釋應(yīng)該簡(jiǎn)潔地描述方法與其客戶端之間的約定。這個(gè)約定應(yīng)該說(shuō)明方法做了什么,而不是它如何完成工作的,。文檔注釋應(yīng)該列舉方法的所有前置條件以及后置條件

前置條件:為了使客戶能夠調(diào)用這個(gè)方法,必須要滿足的條件
后置條件:調(diào)用完成之后,哪些條件必須滿足

通常,每個(gè)未受檢的異常都對(duì)應(yīng)一個(gè)前提違例(precondition violation),要在受影響的參數(shù)的 @param 標(biāo)簽中指定前置條件


方法還應(yīng)在文檔中記錄它的副作用(side effort),。例如,如果方法啟動(dòng)后臺(tái)線程,則應(yīng)該在文檔里說(shuō)明。


文檔注釋應(yīng)該為每個(gè)參數(shù)都有一個(gè) @param 標(biāo)簽,一個(gè) @return 標(biāo)簽,以及一個(gè) @throw 標(biāo)簽,。


@param@return 標(biāo)簽后面的文本應(yīng)該是一個(gè)名詞短語(yǔ),描述參數(shù)或返回值所表示的值,。@throw 標(biāo)簽后面的文本應(yīng)該包含單詞「if」@param ,、@return@throw 標(biāo)簽后面的短語(yǔ)或子句都不用句號(hào)來(lái)結(jié)束,。

/**
* Returns the element at the specified position in this list.
*
* <p>This method is <i>not</i> guaranteed to run in constant
* time. In some implementations it may run in time proportional
* to the element position.
*
* @param index index of element to return; must be
* non-negative and less than the size of this list
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index >= this.size()})
*/
E get(int index);

在此文檔注釋中使用了HTML標(biāo)記(<p><i>)。Javadoc實(shí)用工具將文檔注釋轉(zhuǎn)換為HTML,。


@throw子句中的代碼片段周圍使用Javadoc的{@code}標(biāo)簽,。它使代碼片段以 code font(代碼字體)形式呈現(xiàn)


@implSpec 注釋描述了方法與其子類之間的約定,如果子類繼承了該方法,或者通過(guò)super調(diào)用了方法,則允許子類依賴實(shí)現(xiàn)行為。

/**
* Returns true if this collection is empty.
*
* @implSpec
* This implementation returns {@code this.size() == 0}.
*
* @return true if this collection is empty
*/
public boolean isEmpty() { ... }

包含HTML元字符的文檔,例如小于號(hào)(<),大于號(hào)(>)和 and 符號(hào)(&),是用{@literal}標(biāo)簽將它們包圍起來(lái):

* A geometric series converges if {@literal |r| < 1}.

文檔注釋在源代碼和生成的文檔中都應(yīng)該是易讀的,。


每個(gè)文檔注釋的第一個(gè)「句子」是注釋所在元素的概要描述,。同一個(gè)類或接口中的兩個(gè)成員或構(gòu)造方法不應(yīng)具有相同的概要描述


注意概要描述是否包含句點(diǎn),例如以「A college degree, such as B.S., M.S. or Ph.D.」會(huì)導(dǎo)致概要描述為「A college degree, such as B.S., M.S」,??s寫「M.S.」中的第二個(gè)句號(hào)后面跟著一個(gè)空格。最好的解決方案是用{@literal}標(biāo)簽

/**
* A college degree, such as B.S., {@literal M.S.} or Ph.D.
*/
public class Degree { ... }

概要描述應(yīng)該是一個(gè)動(dòng)詞短語(yǔ),描述了該方法執(zhí)行的操作,。例如:

  • ArrayList(int initialCapacity) ——構(gòu)造具有指定初始容量的空列表,。
  • Collection.size() ——返回此集合中的元素個(gè)數(shù),。

對(duì)于類,接口和屬性,概要描述應(yīng)該是描述由類或接口的實(shí)例或?qū)傩员旧肀硎镜氖挛锏拿~短語(yǔ)。

  • Instant ——時(shí)間線上的瞬時(shí)點(diǎn),。
  • Math.PI ——更加接近pi的double類型數(shù)值,即圓的周?與其直徑之比,。

為泛型類型或方法寫文檔時(shí),請(qǐng)務(wù)必記錄所有類型參數(shù):

/**
* An object that maps keys to values. A map cannot contain
* duplicate keys; each key can map to at most one value.
*
* (Remainder omitted)
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public interface Map<K, V> { ... }

在記錄枚舉類型時(shí),一定要記錄常量:

/**
 * An instrument section of a symphony orchestra.
 */
 public enum OrchestraSection {
	 /** Woodwinds, such as flute, clarinet, and oboe. */
	 WOODWIND,
	
	 /** Brass instruments, such as french horn and trumpet. */
	 BRASS,
	
	/** Percussion instruments, such as timpani and cymbals. */
	PERCUSSION,
	/** Stringed instruments, such as violin and cello. */
	STRING;
}

在為注解類型記錄文檔時(shí),一定要記錄任何成員:

/**
	* Indicates that the annotated method is a test method that
	* must throw the designated exception to pass.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
	/**
	* The exception that the annotated test method must throw
	* in order to pass. (The test is permitted to throw any
	* subtype of the type described by this class object.)
	*/
	Class<? extends Throwable> value();
}

無(wú)論類或靜態(tài)方法是否線程安全,都應(yīng)該在文檔中描述其線程安全級(jí)別


Javadoc具有「繼承(inherit)」方法注釋的能力。如果API元素沒有文檔注釋,Javadoc將搜索最適用的文檔注釋,接口文檔注釋優(yōu)先于超類文檔注釋,。


對(duì)于由多個(gè)相互關(guān)聯(lián)的類組成的復(fù)雜API,通常需要用描述API總體架構(gòu)的外部文檔來(lái)補(bǔ)充文檔注釋,。如
果存在這樣的文檔,相關(guān)的類或包文檔注釋應(yīng)該包含到外部文檔的鏈接。

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多