🍊 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,我們應該在檔案里清楚地指明這些限制,并且在方法的最開始進行檢查,
如果沒有驗證引數的有效性,可能會導致違背失敗原子性:
- 該方法可能在處理程序中失敗,該方法可能會出現費解的例外
- 該方法可以正常回傳,會默默地計算出錯誤的結果
- 該方法可以正常回傳,但是使得某個物件處于受損狀態,在將來某個時間點會報錯
對于public和protected方法,要用Java檔案的@throws注解來說明會拋出哪些例外,通常為:IllegalArgumentException,IndexOutOfBoundsException 或 NullPointerException,例如:
/**
* 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(或LocalDateTime 或 ZonedDateTime)代替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(或LocalDateTime或ZonedDateTime),另一個選項是存盤Date.getTime() 回傳的long型別來代替Date參考,
最后,如果拷貝成本較大的話,并且我們新人使用它的客戶端不會惡意修改組件,則可以在檔案中指明客戶端不得修改受到影響的組件,以此來代替保護性拷貝,
51 謹慎設計方法
這一條介紹了若干經驗:
1. 謹慎給方法起名
- 方法名應該選易于理解的,并且與同一個包里其他名稱的風格一致
- 選擇大眾認可的名稱
2. 不要過于追求提供便利的方法
方法太多會使類難以學習、使用、檔案化、維護,只有當一項操作被經常用到時,才考慮為它提供快捷方式(shorthand)
3. 避免過長的引數串列,相同型別的長引數序列格外有害
引數個數不超過4個
有三種技巧可以縮短過長的引數串列:
-
把一個方法分解成多個方法,每個方法只需要這些引數的一個子集,例如:
java.util.List介面里沒有提供在子串列中查找元素的第一個索引和最后一個索引的方法,相反,它提供了subList方法,回傳子串列,此方法可以與indexOf或lastIndexOf方法結合使用來達到所需的功能, -
創建輔助類用來保存引數的分組,例如:撰寫一個表示紙牌游戲的類,發現需要兩個引數來表示紙牌的點數和花色,這時就可以創建一個類來表示卡片,
-
從物件構建到方法呼叫全都采用
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]
有兩種手段可以解決這個問題:
- 強制轉換
list.remove的引數為Integer - 呼叫
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,這兩種方式都不完美:
- 拋出例外代價很高,因為在創建例外時捕獲整個堆疊trace
- 回傳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方法,Stream的max 操作會為我們生成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還有一些其他的注意事項:
-
永遠不應該回傳基本包裝型別的Optional(小型的
Boolean,Byte,Character,Short和Float除外) -
不適合將optional作為鍵、值、集合或陣列中的元素
-
除了作為回傳值之外,不要在任何其他地方中使用
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 發布 | 開源日報
