JetBrains @Contract 註解
一、概述
在本文中,我們將解釋如何使用@Contract
註解。多虧了這個註解,我們可以定義一個我們的方法必須匹配的契約。 IntelliJ IDEA 背後的品牌 JetBrains 引入了註解。它允許文本編輯器直接解決我們代碼中調用方法的潛在問題。
2.設置Maven依賴
最新版本的annotations
庫可以在Maven Central Repository中找到。讓我們在pom.xml
中添加依賴項:
<dependency>
<groupId>org.jetbrains</groupId
<artifactId>annotations</artifactId>
<version>24.0.1</version>
</dependency>
3. 值屬性
@Contract
註解有兩個屬性: value
和pure
。 value
屬性包含描述方法輸入和輸出之間關係的子句。它是註釋的主要功能,首次在 IntelliJ 14 中引入。
3.1.合約語法
合同是一組形式為“A -> B”的因果關係條款。這意味著向方法提供 A 將始終返回 B 作為結果。例如, “_ -> null”
表示該方法對任何輸入值都返回 null。
對輸入的可能約束如下:
-
_
:任何值 -
null
:空值 -
!null
:非空值 -
true
: 一個真正的布爾值 -
false
: 一個錯誤的布爾值
返回值支持與以下約束相同的約束:
-
fail
:方法拋出Exception
-
new
:該方法返回一個新的Object
。此Object
必須為非空且不同於堆中已存在的任何其他Object
-
this
:該方法返回其限定符值。它不能應用於靜態方法 -
param1
,param2
, ..:該方法返回其第一個(分別是第二個……)參數的值
new
、 this
和param1
關鍵字僅在 IntelliJ 2018.2 之後才受支持。
另外,我們要指出的是,只要不發生衝突,各種約束是可以累積的。
3.2.編寫我們的第一個合同
現在讓我們展示第一個示例:我們將創建一個具有單獨name
屬性的Person
類。我們將添加一個構建器方法,該方法將設置Person
的nam
並返回Person
實例:
public class Person {
String name;
@Contract("_ -> this")
Person withName(String name) {
this.name = name;
return this;
}
}
如我們所見,無論輸入是什麼,都會返回對象本身,因此我們使用有效的@Contract(“_ -> this”)
註釋對該方法進行了註釋。
現在讓我們編寫一個具有更複雜簽名的方法。僅當第二個字符串不為 null 時,此方法才連接兩個Strings
,否則返回 null。因此,我們可以給它一個合同,聲明:
- 如果第二個參數為 null,則結果為 null,並且不依賴於第一個參數的值
- 如果第一個參數為 null,則結果為第二個參數的值
- 如果第二個參數不為空,則結果不為空
簡而言之,我們可以使用以下契約來註釋我們的方法:
@Contract("_, null -> null; null, _ -> param2; _, !null -> !null")
String concatenateOnlyIfSecondArgumentIsNotNull(String head, String tail) {
if (tail == null) {
return null;
}
if (head == null) {
return tail;
}
return head + tail;
}
3.3.代碼檢查
編寫合同可能會導致兩種類型的錯誤:
- 合同寫得不正確
- 調用方法有一些無法訪問的代碼
IntelliJ 可以運行代碼分析並突出顯示這兩種錯誤。要運行代碼檢查,我們可以打開Code
菜單,然後選擇Inspect Code
:
例如,讓我們編寫一個不執行任何操作的方法。我們將添加一個錯誤的合同,聲明它應該總是失敗:
@Contract(" -> fail")
void doNothingWithWrongContract() {}
Problems
選項卡打開,編輯器警告我們代碼中可能存在錯誤:
那些涉及合同正確性的提示很有用,但檢查工具在幫助刪除死代碼或冗餘代碼時真正發揮作用。例如,讓我們編寫一段代碼,為兩個非空Strings
調用concatenateOnlyIfSecondArgumentIsNotNull
方法。然後,如果結果不為空,我們將打印結果。天真地,我們可以這樣寫:
String concatenation = concatenateOnlyIfSecondArgumentIsNotNull("1234", "5678");
if (concatenation != null) {
System.out.println(concatenation);
}
讓我們運行代碼檢查工具。出現以下消息:
Condition 'concatenation != null' is always 'true'
我們的非空檢查確實是多餘的,因為對兩個非空參數調用concatenateOnlyIfSecondArgumentIsNotNull
將始終返回非空結果。由於@Contract
註釋,IntelliJ 能夠發現這一點!
最後但並非最不重要的一點是,IntelliJ 還能夠仔細查看我們庫的字節碼並推斷契約註釋。例如,IntelliJ 自動使用@Contract(“null->true”)
註釋isEmpty()
方法。
4. 純屬性
pure
屬性指定該方法沒有可見的副作用。因此,如果不使用純方法的返回值,我們將能夠安全地刪除調用而不更改代碼的整體結果。打印到標準輸出不被視為可見的副作用。另一方面,似乎沒有副作用但可能允許其他線程中發生的更改在執行後在其線程中可見的方法不能標記為純方法。
例如,替換 String 中的某些字符是一個純操作:
@Contract(pure = true)
String replace(String string, char oldChar, char newChar) {
return string.replace(oldChar, newChar);
}
此外,可以將pure
屬性與value
屬性一起使用。例如,我們可以編寫一個not
方法,該方法返回與其boolean
參數相反的值,具有以下約定:
@Contract(value = "true -> false; false -> true", pure = true)
boolean not(boolean input) {
return !input;
}
從版本 2023.1 開始,IntelliJ 不會運行任何基於pure
屬性的相關代碼檢查。 pure
屬性的默認值為false
。
六,結論
在本教程中,我們了解瞭如何使用@Contract
註釋。將此註釋添加到我們的工具箱可以顯著影響我們的代碼質量。特別是,由於value
屬性,我們可以很容易地發現死代碼。但是,我們需要指出的是,這裡的註解僅用於描述目的,對編譯後的代碼沒有任何影響。
與往常一樣,代碼在 GitHub 上可用。