主頁 > 軟體設計 > 帶你快速看完9.8分神作《Effective Java》—— 方法篇

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

2021-12-28 08:24:10 軟體設計

🍊 Java學習:Java從入門到精通總結

🍊 Spring系列推薦:Spring原始碼決議

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

🍊 個人簡介:通信工程本碩💪、阿里新晉猿同學🌕,我的故事充滿機遇、挑戰與翻盤,歡迎關注作者來共飲一杯雞湯

🍊 點贊 👍 收藏 ?留言 📝 都是我最大的動力!

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

我也始終有一個觀點:看視頻跟著敲代碼永遠只是入門,從書籍里學到了多少東西才決定了你的上限,

在這里插入圖片描述

我個人在Java領域也已經學習了近5年,在修煉“內功”的方面也通過各種途徑接觸到了一些編程規約,例如阿里巴巴的泰山版規約,在此基礎下讀這本書的時候仍是讓我受到了很大的沖激,學習到了很多約定背后的細節問題,還有一些讓我欣賞此書的點是,書中對于編程規約的解釋讓我感到十分受用,并愿意將他們應用在我的作業中,也提醒了我要把閱讀JDK原始碼的任務提上日程,

最后想分享一下我個人目前的看法,內功修煉不像學習一個新的工具那么簡單,其主旨在于踏實,深入探索底層原理的程序很緩慢并且是艱辛的,但一旦開悟,修為一定會突破瓶頸,達到更高的境界,這遠遠不是我通過一兩篇博客就能學到的東西,

接下來就針對此書列舉一下我的識訓與思考,

不過還是要吐槽一下的是翻譯版屬實讓人一言難盡,有些地方會有誤導的效果,你比如java語言里extends是繼承的關鍵字,書本中全部翻譯成了擴展 就完全不是原來的意思了,所以建議有問題的地方對照英文原版進行語意上的理解,

沒有時間讀原作的同學可以參考我這篇文章,


文章目錄

    • 49 檢查引數的有效性
    • 50 必要時進行保護性拷貝
    • 51 謹慎設計方法
    • 52 慎用多載
    • 53 慎用可變引數
    • 54 回傳空的陣列或集合,不要回傳null
    • 55 謹慎回傳optional
    • 56 為所有已公開的API 元素撰寫檔案注釋

49 檢查引數的有效性

當撰寫方法或構造方法時,都應該考慮其引數應該有哪些限制,應該把這些限制寫到檔案里,并在方法體的開頭顯式檢查


大多數方法和構造方法對于傳遞給他們的引數有一些限制,例如,索引值必須是非負數,物件參考必須為非null,我們應該在檔案里清楚地指明這些限制,并且在方法的最開始進行檢查,

如果沒有驗證引數的有效性,可能會導致違背失敗原子性

  1. 該方法可能在處理程序中失敗,該方法可能會出現費解的例外
  2. 該方法可以正常回傳,會默默地計算出錯誤的結果
  3. 該方法可以正常回傳,但是使得某個物件處于受損狀態,在將來某個時間點會報錯

對于publicprotected方法,要用Java檔案的@throws注解來說明會拋出哪些例外,通常為: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 方法靈活方便,因此沒有理由再手動執行null檢查,該方法回傳其輸入的值,因此可以在使用值的同時執行null檢查:

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

對于不是public的方法,通常應該使用斷言來檢查引數:

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
}

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


在某些場景下,有效性檢查的成本很高,且在計算程序里也已經完成了有效性檢查,例如物件串列排序的方法Collections.sort(List)

如果List里的物件不能互相比較,就會拋ClassCastException例外,這正是sort方法該做的事情,所以提前檢查串列中的元素是否可以互相比較并沒有很大意義,


有些計算會隱式執行必要的有效性檢查,如果檢查失敗則會拋例外,這個例外可能和檔案里標明的不同,此時就應該使用例外轉換將其轉換成正確的例外,


50 必要時進行保護性拷貝

Java是一門安全的語言,它對于快取區溢位、陣列越界、非法指標以及其他記憶體損壞錯誤都自動免疫,


但僅管如此,我們也必須保護性地撰寫程式,因為代碼隨時可能會遭受攻擊

如果沒有物件的幫助,另一個類是不可能修改物件的內部狀態的,但物件可能會在無意的情況下提供這樣的幫助,例如,下面的代碼表示一個不可變的時間周期:

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
}

上面代碼雖然強制令period 實體的開始時間小于結束時間,然而,Date 類是可變的,很容易違反這個約束:

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

從Java 8 開始,解決此問題的顯而易?的方法是使用 Instant(或LocalDateTimeZonedDateTime)代替Date,因為他們是不可變的,但Date在老代碼里仍有使用的地方,為了保護 Period 實體的內部不受這種攻擊,可以使用拷?來做 Period 實體的組件:

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);
}

有了新的構造方法后,前面的攻擊將不會對Period 實體產生影響,注意:保護性拷?是在檢查引數的有效性之前進行的,且有效性檢查是在拷貝實體上進行的

這樣做可以避免從檢查引數開始到拷貝引數之間的時間段內,其他的執行緒改變類的引數

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


看了之前章節的同學可能有疑問了,這里為什么沒用clone方法來進行保護性拷貝?

答案是:Date不是final的,所以clone方法不能保證回傳類確實是 java.util.Date 的物件,也可能回傳一個惡意的子類實體,


但是普通方法就不一樣了,它們在進行保護性拷貝是允許使用clone方法,原因是我們知道Period內部的Date物件型別確實是java.util.Date

對于引數型別可能被惡意子類化的引數,不要使用 clone 方法進行防御性拷?


其實,改變Period實體仍是有可能的:

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

修改方法也很簡單:

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

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

上面的分析帶來的啟發是:應該盡量使用不可變物件作為物件內部的組件,這樣就不必擔心保護性拷?,在 Period 示例中,使用Instant(或LocalDateTimeZonedDateTime),另一個選項是存盤Date.getTime() 回傳的long型別來代替Date參考,


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


51 謹慎設計方法

這一條介紹了若干經驗:

1. 謹慎給方法起名

  • 方法名應該選易于理解的,并且與同一個包里其他名稱的風格一致
  • 選擇大眾認可的名稱

2. 不要過于追求提供便利的方法
方法太多會使類難以學習、使用、檔案化、維護,只有當一項操作被經常用到時,才考慮為它提供快捷方式(shorthand)

3. 避免過長的引數串列,相同型別的長引數序列格外有害

引數個數不超過4個

有三種技巧可以縮短過長的引數串列:

  1. 把一個方法分解成多個方法,每個方法只需要這些引數的一個子集,例如:java.util.List介面里沒有提供在子串列中查找元素的第一個索引和最后一個索引的方法,相反,它提供了 subList 方法,回傳子串列,此方法可以與 indexOflastIndexOf 方法結合使用來達到所需的功能,

  2. 創建輔助類用來保存引數的分組,例如:撰寫一個表示紙牌游戲的類,發現需要兩個引數來表示紙牌的點數和花色,這時就可以創建一個類來表示卡片,

  3. 從物件構建到方法呼叫全都采用Builder模式


4. 優先使用介面作為入參型別
只要有適當的介面可用來定義引數,就優先使用這個介面,而不是使用實作該介面的類,例如:在撰寫方法時使用Map介面作為引數


5. 對于boolean型引數,優先使用有兩個元素的列舉
例如,有一個 Thermometer 型別的靜態工廠方法,這個方法的簽名需要以下這個列舉的值:

public enum TemperatureScale { FAHRENHEIT, CELSIUS }

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


52 慎用多載

下面這個程式試圖將一個集合進行分類:

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));
    }
}

運行結果是列印了三次Unknown Collection為什么會這樣呢?

原因就是classify方法被多載了,要呼叫哪個多載方法是在編譯時做出決定的,for回圈里引數的編譯時型別一直是Collection<?>,所以唯一適合的多載方法是classify(Collection<?> c)


有一個很有意思的事實:多載(overloaded)方法的選擇是靜態的,重寫(overridden)方法的選擇是動態的

重寫方法的選擇是在運行時進行的,依據是被呼叫的方法所在的物件的運行時型別,


以下面這個例子具體說明:

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,盡管在每次迭代里,實體的編譯型別都是Wine,但總是會執行最具體(most specific)的重寫方法,也就是在子類上呼叫的就執行被子類覆寫的方法,


CollectionClassifier示例中,程式的目的是根據引數的運行時型別自動執行適當的方法多載來辨別引數的型別,但方法多載完全沒有提供這樣的功能,這段代碼最佳修改方案是:用單個方法來替換這三個多載的classify方法,代碼邏輯里用instanceof判斷:

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

如果API的普通用戶根本不知道哪個多載會被呼叫,使用這樣的API就會報錯,所以,應該避免混淆使用多載

安全保守的策略是:一個安全和保守的策略是永遠不要撰寫兩個具有相同引數數量的多載

因為我們始終可以給方法起不同的名字,避免使用多載


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


一個類的多個構造器總是多載的,可以選擇匯出靜態工廠,


對于每一對多載方法,至少要有一個形參在這兩個多載中具有「完全不同的」型別,這時主要的混淆根源就沒有了,例如ArrayList有接受int的構造方法和接受Collection的構造方法,


Java有一個自動裝箱的概念,他們的出現也引入了一些麻煩:

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);
    }
}

實際上,程式從Set中洗掉非負值,從List中洗掉奇數值,并列印 [-3, -2, -1] 和 [-2, 0, 2],


  • set.remove(i)選擇多載了remove(E)方法,執行結果正確
  • list.remove(i)的呼叫選擇多載remove(int i)方法,它將洗掉串列中指定位置的元素,所以最終列印 [-2, 0, 2]

有兩種手段可以解決這個問題:

  1. 強制轉換list.remove的引數為Integer
  2. 呼叫Integer.valueOf(i),將結果傳遞list.remove方法
for (int i = 0; i < 3; i++) {
	set.remove(i);
	list.remove((Integer) i); // or remove(Integer.valueOf(i))
}

在Java 8中添加Lambda運算式和方法參考以后,進一步增加了多載混淆的可能性,

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

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

Thread 構造方法呼叫和submit方法呼叫看起來很相似,但是前者編譯而后者不編譯,引數是相同的(System.out::println),因為sumbit方法有一個帶有Callable <T>引數的多載,而Thread構造方法卻沒有,在submit這里不知道應該呼叫哪個方法,


在更新現有類時,可能會違反這一條目中的指導原則,例如,從Java 4開始就有一個contentEquals(StringBuffer)方法,在Java 5中,添加了contentEquals(CharSequence)介面,但只要這兩個方法回傳相同的結果就可以,例如下面的代碼:

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

原因是這兩個多載互相呼叫,


Java類別庫在很大程度上遵循了這一條中的建議,但是有一些類違反了它,例如,String匯出兩個多載的靜態工廠方法valueOf(char[])valueOf(Object),這應該被看成是一種反常行為,


53 慎用可變引數

可變引數方法接受0個或多個指定型別的引數,首先創建一個陣列,其大小是在呼叫位置傳遞的引數數量,然后將引數值放入陣列中,最后將陣列傳遞給方法,


例如,這里有一個可變引數方法,回傳入參的總和:

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

有時,撰寫一個需要某種型別的一個或多個引數的方法是合適的,而不是0個或者多個,可以在運行時檢查陣列?
度:

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;
}

最嚴重的是,如果客戶端在沒有引數的情況下呼叫此方法,則它在運行時而不是在編譯時失敗,

有一種更好的方法可以達到預期的效果,宣告方法采用兩個引數,一個指定型別的普通引數,另一個此型別的可變引數,

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

在性能關鍵的情況下使用可變引數時要小心,每次呼叫可變引數方法都會導致陣列分配和初始化,

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

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) { }

當引數數目超過3個時需要創建陣列,

EnumSet類的靜態工廠使用這種方法,將創建列舉集合的成本降到最低,


54 回傳空的陣列或集合,不要回傳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)可買的情況當做一種特例,這是不合常理的,這樣需要在客戶端中必須有額外的代碼來處理null的回傳值:

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

這樣做很容易出錯,因為撰寫客戶端的程式員可能忘記撰寫特殊情況代碼來處理null回傳,


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

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

如果有證據表明分配空集合會損害性能,可以通過重復回傳相同的不可變空集合來避免多次分配

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

陣列的情況與集合的情況相同,永遠不要回傳null,而是回傳?度為零的陣列,

// 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 謹慎回傳optional

在Java 8之前,撰寫在特定情況下無法回傳任何值的方法時,可以采用兩種方法,要么拋出例外,要么回傳null,這兩種方式都不完美:

  1. 拋出例外代價很高,因為在創建例外時捕獲整個堆疊trace
  2. 回傳null有可能拋NullPointerException例外

在Java 8中,還有第三種方法來撰寫可能無法回傳任何值的方法,Optional<T>類表示一個不可變的容器,它可以包含一個非null的T參考,也可以什么都不包含,

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

回傳Optional的方法比拋出例外的方法更靈活、更容易使用,而且比回傳null的方法更不容易出錯,

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

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)是一個編程錯誤,會拋NullPointerException例外,Optional.ofNullable(value)方法接受一個可能為null的值,如果傳入null則回傳一個空的Optional


Stream 上的很多終止操作回傳Optional,可以用Stream重寫max方法,Streammax 操作會為我們生成Optional的作業:

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

如果方法回傳一個Optional,則客戶端可以選擇在方法無法回傳值時要采取的操作,有以下兩種方式:

1. 指定默認值

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

2. 拋出任何適當的例外

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

注意,我們傳遞的是例外工廠,而不是實際的例外,這避免了創建例外的開銷


有時候,可能會遇到這樣一種情況:獲取默認值的代價很高,我們希望避免這種代價,對于這些情況,Optional提供了一個方法orElseGet,傳入一個Supplier<T>


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


當使用Stream時,經常會遇到Stream<Optional<T>>,為了推動行程還需要一個包含了非空optional中所有元素的Stream<T>,Java 8里可以這樣寫:

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

并不是所有的回傳型別都能從Optional的處理中獲益,容器型別,包括集合、映射、Stream、陣列和Optional,不應該封裝在Optional中,與其回傳一個空的Optional<List<T>>,不還如回傳一個空的List<T>


那么什么時候應該宣告一個方法來回傳Optional<T> 而不是T 呢?

如果可能無法回傳結果,并且在沒有回傳結果,客戶端還必須執行特殊處理的情況下,則應宣告回傳Optional <T>的方法


使用Optional還有一些其他的注意事項:

  1. 永遠不應該回傳基本包裝型別的Optional(小型的BooleanByteCharacterShortFloat 除外)

  2. 不適合將optional作為鍵、值、集合或陣列中的元素

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


56 為所有已公開的API 元素撰寫檔案注釋

如果API要可用,就必須對其撰寫檔案化,


要正確地記錄API,必須在每個匯出的類、介面、構造方法、方法和屬性宣告之前加上檔案注釋,如果一個類是可序列化的,應該對它的序列化形式撰寫檔案,puiblic類不應該使用無參構造方法,因為無法為它們提供檔案注釋,要撰寫可維護的代碼,還應該為所有沒有被匯出的類、介面、構造方法、方法和屬性撰寫檔案注釋,盡管這些注釋不需要像匯出API元素那樣完整,


方法的檔案注釋應該簡潔地描述方法與其客戶端之間的約定,這個約定應該說明方法做了什么,而不是它如何完成作業的,檔案注釋應該列舉方法的所有前置條件以及后置條件

前置條件:為了使客戶能夠呼叫這個方法,必須要滿足的條件
后置條件:呼叫完成之后,哪些條件必須滿足

通常,每個未受檢的例外都對應一個前提違例(precondition violation),要在受影響的引數的 @param 標簽中指定前置條件


方法還應在檔案中記錄它的副作用(side effort),例如,如果方法啟動后臺執行緒,則應該在檔案里說明,


檔案注釋應該為每個引數都有一個 @param 標簽,一個 @return 標簽,以及一個 @throw 標簽,


@param@return 標簽后面的文本應該是一個名詞短語,描述引數或回傳值所表示的值,@throw 標簽后面的文本應該包含單詞「if」@param@return@throw 標簽后面的短語或子句都不用句號來結束,

/**
* 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標記(<p><i>),Javadoc實用工具將檔案注釋轉換為HTML,


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


@implSpec 注釋描述了方法與其子類之間的約定,如果子類繼承了該方法,或者通過super呼叫了方法,則允許子類依賴實作行為,

/**
* 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元字符的檔案,例如小于號(<),大于號(>)和 and 符號(&),是用{@literal}標簽將它們包圍起來:

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

檔案注釋在源代碼和生成的檔案中都應該是易讀的


每個檔案注釋的第一個「句子」是注釋所在元素的概要描述,同一個類或介面中的兩個成員或構造方法不應具有相同的概要描述


注意概要描述是否包含句點,例如以「A college degree, such as B.S., M.S. or Ph.D.」會導致概要描述為「A college degree, such as B.S., M.S」,縮寫「M.S.」中的第二個句號后面跟著一個空格,最好的解決方案是用{@literal}標簽

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

概要描述應該是一個動詞短語,描述了該方法執行的操作,例如:

  • ArrayList(int initialCapacity) ——構造具有指定初始容量的空串列,
  • Collection.size() ——回傳此集合中的元素個數,

對于類,介面和屬性,概要描述應該是描述由類或介面的實體或屬性本身表示的事物的名詞短語,

  • Instant ——時間線上的瞬時點,
  • Math.PI ——更加接近pi的double型別數值,即圓的周?與其直徑之比,

為泛型型別或方法寫檔案時,請務必記錄所有型別引數:

/**
* 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> { ... }

在記錄列舉型別時,一定要記錄常量:

/**
 * 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;
}

在為注解型別記錄檔案時,一定要記錄任何成員:

/**
	* 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();
}

無論類或靜態方法是否執行緒安全,都應該在檔案中描述其執行緒安全級別


Javadoc具有「繼承(inherit)」方法注釋的能力,如果API元素沒有檔案注釋,Javadoc將搜索最適用的檔案注釋,介面檔案注釋優先于超類檔案注釋,


對于由多個相互關聯的類組成的復雜API,通常需要用描述API總體架構的外部檔案來補充檔案注釋,如
果存在這樣的檔案,相關的類或包檔案注釋應該包含到外部檔案的鏈接,

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/395168.html

標籤:其他

上一篇:【Kotlin 初學者】標準函式

下一篇:微軟修改 MIT 專案原作者著作權宣告引發爭議;白宮為提高開源安全性邀請軟體行業者座談;Ruby 3.1.0 發布 | 開源日報

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more