## Chapter 6. Enums and Annotations(枚舉和注解)
### Item 39: Prefer annotations to naming patterns(注解優于命名模式)
Historically, it was common to use naming patterns to indicate that some program elements demanded special treatment by a tool or framework. For example, prior to release 4, the JUnit testing framework required its users to designate test methods by beginning their names with the characters test [Beck04]. This technique works, but it has several big disadvantages. First, typographical errors result in silent failures. For example, suppose you accidentally named a test method tsetSafetyOverride instead of testSafetyOverride. JUnit 3 wouldn’t complain, but it wouldn’t execute the test either, leading to a false sense of security.
從歷史上看,使用命名模式來標明某些程序元素需要工具或框架特殊處理的方式是很常見的。例如,在版本 4 之前,JUnit 測試框架要求其用戶通過以字符 test [Beck04] 開頭的名稱來指定測試方法。這種技術是有效的,但是它有幾個很大的缺點。首先,排版錯誤會導致沒有提示的失敗。例如,假設你意外地將一個測試方法命名為 tsetSafetyOverride,而不是 testSafetyOverride。JUnit 3 不會報錯,但它也不會執行測試,這導致一種正確執行了測試的假象。
A second disadvantage of naming patterns is that there is no way to ensure that they are used only on appropriate program elements. For example, suppose you called a class TestSafetyMechanisms in hopes that JUnit 3 would automatically test all of its methods, regardless of their names. Again, JUnit 3 wouldn’t complain, but it wouldn’t execute the tests either.
命名模式的第二個缺點是,無法確保只在相應的程序元素上使用它們。例如,假設你調用了一個類 TestSafetyMechanisms,希望 JUnit 3 能夠自動測試它的所有方法,而不管它們的名稱是什么。同樣,JUnit 3 不會報錯,但它也不會執行測試。
A third disadvantage of naming patterns is that they provide no good way to associate parameter values with program elements. For example, suppose you want to support a category of test that succeeds only if it throws a particular exception. The exception type is essentially a parameter of the test. You could encode the exception type name into the test method name using some elaborate naming pattern, but this would be ugly and fragile (Item 62). The compiler would have no way of knowing to check that the string that was supposed to name an exception actually did. If the named class didn’t exist or wasn’t an exception, you wouldn’t find out until you tried to run the test.
命名模式的第三個缺點是,它們沒有提供將參數值與程序元素關聯的好方法。例如,假設你希望支持只有在拋出特定異常時才成功的測試類別。異常類型本質上是測試的一個參數。你可以使用一些精心設計的命名模式,將異常類型名稱編碼到測試方法名稱中,但這樣的代碼將不好看且脆弱([Item-62](/Chapter-9/Chapter-9-Item-62-Avoid-strings-where-other-types-are-more-appropriate.md))。編譯器將無法檢查這些用于命名異常的字符串是否確實執行了。如果指定的類不存在或不是異常,則在運行測試之前不會被發現。
Annotations [JLS, 9.7] solve all of these problems nicely, and JUnit adopted them starting with release 4. In this item, we’ll write our own toy testing framework to show how annotations work. Suppose you want to define an annotation type to designate simple tests that are run automatically and fail if they throw an exception. Here’s how such an annotation type, named Test, might look:
注解 [JLS, 9.7] 很好地解決了所有這些問題,JUnit 從版本 4 開始就采用了它們。在本條目中,我們將編寫自己的示例測試框架來展示注解是如何工作的。假設你希望定義注解類型,以指定自動運行的簡單測試,并在拋出異常時失敗。下面是這種名為 Test 的注解類型的概貌:
```
// Marker annotation type declaration
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method.
* Use only on parameterless static methods.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
```
The declaration for the Test annotation type is itself annotated with Retention and Target annotations. Such annotations on annotation type declarations are known as meta-annotations. The @Retention(RetentionPolicy.RUNTIME) meta-annotation indicates that Test annotations should be retained at runtime. Without it, Test annotations would be invisible to the test tool. The @Target.get(ElementType.METHOD) meta-annotation indicates that the Test annotation is legal only on method declarations: it cannot be applied to class declarations, field declarations, or other program elements.
Test 注解類型的聲明本身帶有 Retention 注解和 Target 注解。這種注解類型聲明上的注解稱為元注解。`@Retention(RetentionPolicy.RUNTIME)` 元注解表明測試注解應該在運行時保留。沒有它,測試工具將無法識別測試注解。`@Target.get(ElementType.METHOD)` 元注解表明測試注解僅對方法聲明合法:它不能應用于類聲明、字段聲明或其他程序元素。
**譯注 1:注解的保留策略**
保留策略決定了在什么位置丟棄注解。Java 定義了 3 種策略,它們被封裝到 `java.lang.annotation.RetentionPolicy` 枚舉中。這 3 種策略分別是 SOURCE、CLASS 和 RUNTIME。
- 使用 SOURCE 保留策略的注解,只在源文件中保留,在編譯期間會被拋棄。
- 使用 CLASS 保留策略的注解,在編譯時被存儲到 `.class` 文件中。但是,在運行時不能通過 JVM 得到這些注解。
- 使用 RUNTIME 保留策略的注解,在編譯時被存儲到 `.class` 文件中,并且在運行時可以通過 JVM 獲取這些注解。因此,RUNTIME 保留策略提供了最永久的注解。
**譯注 2:ElementType 各常量定義的范圍**
- ElementType.TYPE
- Class, interface (including annotation type), or enum declaration(類、接口、注解、枚舉)
- ElementType.FIELD
- Field declaration (includes enum constants)(字段、枚舉常量)
- ElementType.METHOD
- Method declaration(方法)
- ElementType.PARAMETER
- Formal parameter declaration(方法參數)
- ElementType.CONSTRUCTOR
- Constructor declaration(構造)
- ElementType.LOCAL_VARIABLE
- Local variable declaration(局部變量)
- ElementType.ANNOTATION_TYPE
- Annotation type declaration(注解)
- ElementType.PACKAGE
- Package declaration(包)
- ElementType.TYPE_PARAMETER
- Type parameter declaration(泛型參數)
- Since: 1.8
- ElementType.TYPE_USE
- Use of a type(任意類型,獲取 class 對象和 import 兩種情況除外)
- Since: 1.8
- ElementType.MODULE
- Module declaration([模塊](https://docs.oracle.com/javase/9/whatsnew/toc.htm#JSNEW-GUID-C23AFD78-C777-460B-8ACE-58BE5EA681F6))
- Since: 9
The comment before the Test annotation declaration says, “Use only on parameterless static methods.” It would be nice if the compiler could enforce this, but it can’t, unless you write an annotation processor to do so. For more on this topic, see the documentation for javax.annotation.processing. In the absence of such an annotation processor, if you put a Test annotation on the declaration of an instance method or on a method with one or more parameters, the test program will still compile, leaving it to the testing tool to deal with the problem at runtime.
Test 注解聲明之前的代碼注釋是這么描述的:「Use only on parameterless static methods.(只對無參數的靜態方法使用)」如果編譯器能夠強制執行這一點,那就太好了,但是它不能,除非你編寫代碼注釋處理器來執行。有關此主題的更多信息,請參閱 `javax.annotation.processing` 的文檔。在沒有這樣的代碼注釋處理程序的情況下,如果你將 Test 注解放在實例方法的聲明上,或者放在帶有一個或多個參數的方法上,測試程序仍然會編譯,讓測試工具在運行時處理。
Here is how the Test annotation looks in practice. It is called a marker annotation because it has no parameters but simply “marks” the annotated element. If the programmer were to misspell Test or to apply the Test annotation to a program element other than a method declaration, the program wouldn’t compile:
下面是 Test 注解實際使用時的樣子。它被稱為標記注解,因為它沒有參數,只是對帶注解的元素進行「標記」。如果程序員拼錯 Test 或將 Test 注解應用于除方法聲明之外的程序元素,程序將無法編譯:
```
// Program containing marker annotations
public class Sample {
@Test
public static void m1() { } // Test should pass
public static void m2() { }
@Test
public static void m3() { // Test should fail
throw new RuntimeException("Boom");
}
public static void m4() { }
@Test
public void m5() { } // INVALID USE: nonstatic method
public static void m6() { }
@Test
public static void m7() { // Test should fail
throw new RuntimeException("Crash");
}
public static void m8() { }
}
```
The Sample class has seven static methods, four of which are annotated as tests. Two of these, m3 and m7, throw exceptions, and two, m1 and m5, do not. But one of the annotated methods that does not throw an exception, m5, is an instance method, so it is not a valid use of the annotation. In sum, Sample contains four tests: one will pass, two will fail, and one is invalid. The four methods that are not annotated with the Test annotation will be ignored by the testing tool.
Sample 類有 7 個靜態方法,其中 4 個被注解為 Test。其中兩個方法 m3 和 m7 拋出異常,另外兩個 m1 和 m5 沒有拋出異常。但是,不拋出異常的帶注解的方法 m5 是一個實例方法,因此它不是注解的有效使用。總之,Sample 包含四個測試:一個通過,兩個失敗,一個無效。沒有使用 Test 注釋的四個方法將被測試工具忽略。
The Test annotations have no direct effect on the semantics of the Sample class. They serve only to provide information for use by interested programs. More generally, annotations don’t change the semantics of the annotated code but enable it for special treatment by tools such as this simple test runner:
Test 注解對 Sample 類的語義沒有直接影響。它們僅用于向相關程序提供信息。更普遍的是,注解不會改變被注解代碼的語義,而是通過工具(就像如下這個簡單的 RunTests 類)對其進行特殊處理:
```
// Program to process marker annotations
import java.lang.reflect.*;
public class RunTests {
public static void main(String[] args) throws Exception {
int tests = 0;
int passed = 0;
Class<?> testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
m.invoke(null);
passed++;
} catch (InvocationTargetException wrappedExc) {
Throwable exc = wrappedExc.getCause();
System.out.println(m + " failed: " + exc);
} catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
}
System.out.printf("Passed: %d, Failed: %d%n",passed, tests - passed);
}
}
```
The test runner tool takes a fully qualified class name on the command line and runs all of the class’s Test-annotated methods reflectively, by calling Method.invoke. The isAnnotationPresent method tells the tool which methods to run. If a test method throws an exception, the reflection facility wraps it in an InvocationTargetException. The tool catches this exception and prints a failure report containing the original exception thrown by the test method, which is extracted from the InvocationTargetException with the getCause method.
test runner 工具以命令行方式接受一個完全限定的類名,并通過調用 `Method.invoke` 以反射方式運行類的所有帶測試注解的方法。isAnnotationPresent 方法告訴工具要運行哪些方法。如果測試方法拋出異常,反射工具將其封裝在 InvocationTargetException 中。該工具捕獲這個異常并打印一個失敗報告,其中包含測試方法拋出的原始異常,該異常是用 getCause 方法從 InvocationTargetException 提取的。
If an attempt to invoke a test method by reflection throws any exception other than InvocationTargetException, it indicates an invalid use of the Test annotation that was not caught at compile time. Such uses include annotation of an instance method, of a method with one or more parameters, or of an inaccessible method. The second catch block in the test runner catches these Test usage errors and prints an appropriate error message. Here is the output that is printed if RunTests is run on Sample:
如果通過反射調用測試方法時拋出除 InvocationTargetException 之外的任何異常,則表明在編譯時存在未捕獲的 Test 注解的無效用法。這些用途包括實例方法的注解、帶有一個或多個參數的方法的注解或不可訪問方法的注解。測試運行程序中的第二個 catch 塊捕獲這些 Test 使用錯誤并打印對應的錯誤消息。如果在 Sample 上運行 RunTests,輸出如下:
```
public static void Sample.m3() failed: RuntimeException: Boom
Invalid @Test: public void Sample.m5()
public static void Sample.m7() failed: RuntimeException: Crash
Passed: 1, Failed: 3
```
Now let’s add support for tests that succeed only if they throw a particular exception. We’ll need a new annotation type for this:
現在讓我們添加一個只在拋出特定異常時才成功的測試支持。我們需要一個新的注解類型:
```
// Annotation type with a parameter
import java.lang.annotation.*;
/**
* Indicates that the annotated method is a test method that
* must throw the designated exception to succeed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Throwable> value();
}
```
The type of the parameter for this annotation is `Class<? extends Throwable>`. This wildcard type is, admittedly, a mouthful. In English, it means “the Class object for some class that extends Throwable,” and it allows the user of the annotation to specify any exception (or error) type. This usage is an example of a bounded type token (Item 33). Here’s how the annotation looks in practice. Note that class literals are used as the values for the annotation parameter:
這個注解的參數類型是 `Class<? extends Throwable>`,這個通配符類型確實很復雜。在英語中,它的意思是「某個擴展自 Throwable 的類的 Class 對象」,它允許注解的用戶指定任何異常(或錯誤)類型。這種用法是有界類型令牌([Item-33](/Chapter-5/Chapter-5-Item-33-Consider-typesafe-heterogeneous-containers.md))的一個示例。下面是這個注解在實際應用時的樣子。注意,類的字面量被用作注解參數的值:
```
// Program containing annotations with a parameter
public class Sample2 {
@ExceptionTest(ArithmeticException.class)
public static void m1() { // Test should pass
int i = 0;
i = i / i;
}
@ExceptionTest(ArithmeticException.class)
public static void m2() { // Should fail (wrong exception)
int[] a = new int[0];
int i = a[1];
}
@ExceptionTest(ArithmeticException.class)
public static void m3() { } // Should fail (no exception)
}
```
Now let’s modify the test runner tool to process the new annotation. Doing so consists of adding the following code to the main method:
現在讓我們修改 test runner 工具來處理新的注解。向 main 方法添加以下代碼:
```
if (m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (InvocationTargetException wrappedEx) {
Throwable exc = wrappedEx.getCause();
Class<? extends Throwable> excType =m.getAnnotation(ExceptionTest.class).value();
if (excType.isInstance(exc)) {
passed++;
} else {
System.out.printf("Test %s failed: expected %s, got %s%n",m, excType.getName(), exc);
}
}
catch (Exception exc) {
System.out.println("Invalid @Test: " + m);
}
}
```
This code is similar to the code we used to process Test annotations, with one exception: this code extracts the value of the annotation parameter and uses it to check if the exception thrown by the test is of the right type. There are no explicit casts, and hence no danger of a ClassCastException. The fact that the test program compiled guarantees that its annotation parameters represent valid exception types, with one caveat: if the annotation parameters were valid at compile time but the class file representing a specified exception type is no longer present at runtime, the test runner will throw TypeNotPresentException.
這段代碼與我們用來處理 Test 注解的代碼類似,只有一個不同:這段代碼提取注解參數的值,并使用它來檢查測試拋出的異常是否是正確的類型。這里沒有顯式的強制類型轉換,因此沒有 ClassCastException 的危險。編譯的測試程序保證其注解參數表示有效的異常類型,但有一點需要注意:如果注解參數在編譯時有效,但表示指定異常類型的類文件在運行時不再存在,那么測試運行程序將拋出 TypeNotPresentException。
Taking our exception testing example one step further, it is possible to envision a test that passes if it throws any one of several specified exceptions. The annotation mechanism has a facility that makes it easy to support this usage. Suppose we change the parameter type of the ExceptionTest annotation to be an array of Class objects:
進一步修改我們的異常測試示例,如果它拋出幾個指定異常中的任意一個,那么可以認為測試通過了。注解機制具有一種工具,可以輕松地支持這種用法。假設我們將 ExceptionTest 注解的參數類型更改為一個 Class 對象數組:
```
// Annotation type with an array parameter
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
Class<? extends Exception>[] value();
}
```
The syntax for array parameters in annotations is flexible. It is optimized for single-element arrays. All of the previous ExceptionTest annotations are still valid with the new array-parameter version of ExceptionTest and result in single-element arrays. To specify a multiple-element array, surround the elements with curly braces and separate them with commas:
注解中數組參數的語法是靈活的。它針對單元素數組進行了優化。前面的 ExceptionTest 注解對于 ExceptionTest 的新數組參數版本仍然有效,并且可以生成單元素數組。要指定一個多元素數組,用花括號包圍元素,并用逗號分隔它們:
```
// Code containing an annotation with an array parameter
@ExceptionTest({ IndexOutOfBoundsException.class,NullPointerException.class })
public static void doublyBad() {
List<String> list = new ArrayList<>();
// The spec permits this method to throw either
// IndexOutOfBoundsException or NullPointerException
list.addAll(5, null);
}
```
It is reasonably straightforward to modify the test runner tool to process the new version of ExceptionTest. This code replaces the original version:
修改測試運行器工具來處理 ExceptionTest 的新版本是相當簡單的。這段代碼替換了原來的版本:
```
if (m.isAnnotationPresent(ExceptionTest.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (Throwable wrappedExc) {
Throwable exc = wrappedExc.getCause();
int oldPassed = passed;
Class<? extends Exception>[] excTypes =m.getAnnotation(ExceptionTest.class).value();
for (Class<? extends Exception> excType : excTypes) {
if (excType.isInstance(exc)) {
passed++;
break;
}
}
if (passed == oldPassed)
System.out.printf("Test %s failed: %s %n", m, exc);
}
}
```
As of Java 8, there is another way to do multivalued annotations. Instead of declaring an annotation type with an array parameter, you can annotate the declaration of an annotation with the @Repeatable meta-annotation, to indicate that the annotation may be applied repeatedly to a single element. This meta-annotation takes a single parameter, which is the class object of a containing annotation type, whose sole parameter is an array of the annotation type [JLS, 9.6.3]. Here’s how the annotation declarations look if we take this approach with our ExceptionTest annotation. Note that the containing annotation type must be annotated with an appropriate retention policy and target, or the declarations won’t compile:
在 Java 8 中,還有另一種方法可以執行多值注解。你可以在注解聲明上使用 `@Repeatable` 元注解,以表明注解可以重復地應用于單個元素,而不是使用數組參數來聲明注解類型。這個元注解只接受一個參數,這個參數是包含注解類型的類對象,它的唯一參數是注解類型的數組 [JLS, 9.6.3]。如果我們對 ExceptionTest 注解采用這種方法,那么注解聲明是這樣的。注意,包含的注解類型必須使用適當的 Retention 注解和 Target 注解,否則聲明將無法編譯:
```
// Repeatable annotation type
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
Class<? extends Exception> value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
ExceptionTest[] value();
}
```
Here’s how our doublyBad test looks with a repeated annotation in place of an array-valued annotation:
下面是使用重復注解代替數組值注解的 doublyBad 測試:
```
// Code containing a repeated annotation
@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad() { ... }
```
Processing repeatable annotations requires care. A repeated annotation generates a synthetic annotation of the containing annotation type. The getAnnotationsByType method glosses over this fact, and can be used to access both repeated and non-repeated annotations of a repeatable annotation type. But isAnnotationPresent makes it explicit that repeated annotations are not of the annotation type, but of the containing annotation type. If an element has a repeated annotation of some type and you use the isAnnotationPresent method to check if the element has an annotation of that type, you’ll find that it does not. Using this method to check for the presence of an annotation type will therefore cause your program to silently ignore repeated annotations. Similarly, using this method to check for the containing annotation type will cause the program to silently ignore non-repeated annotations. To detect repeated and non-repeated annotations with isAnnotationPresent, you much check for both the annotation type and its containing annotation type. Here’s how the relevant part of our RunTests program looks when modified to use the repeatable version of the ExceptionTest annotation:
處理可重復注解需要小心。「重復狀態」會生成名為「容器注解類型」的合成注解。getAnnotationsByType 方法可忽略這一區別,它可以用于訪問可重復注解類型的「重復狀態」和「非重復狀態」。但是 isAnnotationPresent 明確指出,「重復狀態」的情況不屬于注解類型,而是「容器注解類型」。如果一個元素是某種類型的「重復狀態」注解,并且你使用 isAnnotationPresent 方法檢查該元素是否具有該類型的注解,你將發現它提示不存在。因此,使用此方法檢查注解類型的存在與否,將導致你的程序忽略「重復狀態」。類似地,使用此方法檢查「容器注解類型」將導致程序忽略「非重復狀態」。要使用 isAnnotationPresent 檢測「重復狀態」和「非重復狀態」,需要同時檢查注解類型及其「容器注解類型」。下面是我們的 RunTests 程序的相關部分修改為使用 ExceptionTest 注解的可重復版本時的樣子:
```
// Processing repeatable annotations
if (m.isAnnotationPresent(ExceptionTest.class)|| m.isAnnotationPresent(ExceptionTestContainer.class)) {
tests++;
try {
m.invoke(null);
System.out.printf("Test %s failed: no exception%n", m);
} catch (Throwable wrappedExc) {
Throwable exc = wrappedExc.getCause();
int oldPassed = passed;
ExceptionTest[] excTests =m.getAnnotationsByType(ExceptionTest.class);
for (ExceptionTest excTest : excTests) {
if (excTest.value().isInstance(exc)) {
passed++;
break;
}
}
if (passed == oldPassed)
System.out.printf("Test %s failed: %s %n", m, exc);
}
}
```
**譯注:比較原文中提及的 getAnnotationsByType 與 isAnnotationPresent 在可重復注解的「重復狀態」和「非重復狀態」下的使用差別:**
**原 doublyBad 方法不變,屬于「重復狀態」(重復注解大于等于兩個的,都屬于「重復狀態」);新增一個 doublyBad2 方法,僅使用一個重復注解,屬于「非重復狀態」**
```
class Simple4 {
// Code containing a repeated annotation
@ExceptionTest(IndexOutOfBoundsException.class)
@ExceptionTest(NullPointerException.class)
public static void doublyBad() {
}
@ExceptionTest(ArithmeticException.class)
public static void doublyBad2() {
}
}
```
**測試代碼**
```
public static void main(String[] args) throws NoSuchMethodException {
Class<?> testClass = Simple4.class;
for (int count = 1; count <= 2; count++) {
Method m = testClass.getMethod(count == 1 ? "doublyBad" : "doublyBad" + count);
System.out.println(m.getName() + "「重復狀態」:" + m.isAnnotationPresent(ExceptionTest.class));
System.out.println(m.getName() + "「容器注解類型」:" + m.isAnnotationPresent(ExceptionTestContainer.class));
System.out.println(m.getName() + "「非重復狀態」:" + m.isAnnotationPresent(ExceptionTest.class));
System.out.println(m.getName() + "「重復狀態」:" + m.getAnnotationsByType(ExceptionTest.class));
System.out.println(m.getName() + "「容器注解類型」:" + m.getAnnotationsByType(ExceptionTestContainer.class));
System.out.println(m.getName() + "「非重復狀態」:" + m.getAnnotationsByType(ExceptionTest.class));
}
}
```
**結果**
```
doublyBad「重復狀態」:false
doublyBad「容器注解類型」:true
doublyBad「非重復狀態」:false
doublyBad「重復狀態」:[LItem_39.ExceptionTest;@1593948d
doublyBad「容器注解類型」:[LItem_39.ExceptionTestContainer;@1b604f19
doublyBad「非重復狀態」:[LItem_39.ExceptionTest;@7823a2f9
doublyBad2「重復狀態」:true
doublyBad2「容器注解類型」:false
doublyBad2「非重復狀態」:true
doublyBad2「重復狀態」:[LItem_39.ExceptionTest;@cb5822
doublyBad2「容器注解類型」:[LItem_39.ExceptionTestContainer;@4b9e13df
doublyBad2「非重復狀態」:[LItem_39.ExceptionTest;@2b98378d
```
Repeatable annotations were added to improve the readability of source code that logically applies multiple instances of the same annotation type to a given program element. If you feel they enhance the readability of your source code, use them, but remember that there is more boilerplate in declaring and processing repeatable annotations, and that processing repeatable annotations is error-prone.
添加可重復注解是為了提高源代碼的可讀性,源代碼在邏輯上將同一注解類型的多個實例應用于給定的程序元素。如果你覺得它們增強了源代碼的可讀性,那么就使用它們,但是請記住,在聲明和處理可重復注解方面有更多的樣板,并且處理可重復注解很容易出錯。
The testing framework in this item is just a toy, but it clearly demonstrates the superiority of annotations over naming patterns, and it only scratches the surface of what you can do with them. If you write a tool that requires programmers to add information to source code, define appropriate annotation types. **There is simply no reason to use naming patterns when you can use annotations instead.**
本條目中的測試框架只是一個示例,但是它清楚地展示了注解相對于命名模式的優勢,并且它只涉及到你可以使用它們做什么。如果你編寫的工具要求程序員向源代碼中添加信息,請定義適當的注解類型。**如果可以使用注解,那么就沒有理由使用命名模式。**
That said, with the exception of toolsmiths, most programmers will have no need to define annotation types. But **all programmers should use the predefined annotation types that Java provides** (Items 40, 27). Also, consider using the annotations provided by your IDE or static analysis tools. Such annotations can improve the quality of the diagnostic information provided by these tools. Note, however, that these annotations have yet to be standardized, so you may have some work to do if you switch tools or if a standard emerges.
也就是說,除了 toolsmiths 之外,大多數程序員不需要定義注解類型。但是所有程序員都應該使用 Java 提供的預定義注解類型([Item-40](/Chapter-6/Chapter-6-Item-40-Consistently-use-the-Override-annotation.md) 和 [Item-27](/Chapter-5/Chapter-5-Item-27-Eliminate-unchecked-warnings.md))。另外,考慮使用 IDE 或靜態分析工具提供的注解。這些注解可以提高這些工具提供的診斷信息的質量。但是,請注意,這些注解還沒有標準化,因此,如果你切換了工具或出現了標準,那么你可能需要做一些工作。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-6/Chapter-6-Introduction.md)**
- **Previous Item(上一條目):[Item 38: Emulate extensible enums with interfaces(使用接口模擬可擴展枚舉)](/Chapter-6/Chapter-6-Item-38-Emulate-extensible-enums-with-interfaces.md)**
- **Next Item(下一條目):[Item 40: Consistently use the Override annotation(堅持使用 @Override 注解)](/Chapter-6/Chapter-6-Item-40-Consistently-use-the-Override-annotation.md)**
- Chapter 2. Creating and Destroying Objects(創建和銷毀對象)
- Item 1: Consider static factory methods instead of constructors(考慮以靜態工廠方法代替構造函數)
- Item 2: Consider a builder when faced with many constructor parameters(在面對多個構造函數參數時,請考慮構建器)
- Item 3: Enforce the singleton property with a private constructor or an enum type(使用私有構造函數或枚舉類型實施單例屬性)
- Item 4: Enforce noninstantiability with a private constructor(用私有構造函數實施不可實例化)
- Item 5: Prefer dependency injection to hardwiring resources(依賴注入優于硬連接資源)
- Item 6: Avoid creating unnecessary objects(避免創建不必要的對象)
- Item 7: Eliminate obsolete object references(排除過時的對象引用)
- Item 8: Avoid finalizers and cleaners(避免使用終結器和清除器)
- Item 9: Prefer try with resources to try finally(使用 try-with-resources 優于 try-finally)
- Chapter 3. Methods Common to All Objects(對象的通用方法)
- Item 10: Obey the general contract when overriding equals(覆蓋 equals 方法時應遵守的約定)
- Item 11: Always override hashCode when you override equals(當覆蓋 equals 方法時,總要覆蓋 hashCode 方法)
- Item 12: Always override toString(始終覆蓋 toString 方法)
- Item 13: Override clone judiciously(明智地覆蓋 clone 方法)
- Item 14: Consider implementing Comparable(考慮實現 Comparable 接口)
- Chapter 4. Classes and Interfaces(類和接口)
- Item 15: Minimize the accessibility of classes and members(盡量減少類和成員的可訪問性)
- Item 16: In public classes use accessor methods not public fields(在公共類中,使用訪問器方法,而不是公共字段)
- Item 17: Minimize mutability(減少可變性)
- Item 18: Favor composition over inheritance(優先選擇復合而不是繼承)
- Item 19: Design and document for inheritance or else prohibit it(繼承要設計良好并且具有文檔,否則禁止使用)
- Item 20: Prefer interfaces to abstract classes(接口優于抽象類)
- Item 21: Design interfaces for posterity(為后代設計接口)
- Item 22: Use interfaces only to define types(接口只用于定義類型)
- Item 23: Prefer class hierarchies to tagged classes(類層次結構優于帶標簽的類)
- Item 24: Favor static member classes over nonstatic(靜態成員類優于非靜態成員類)
- Item 25: Limit source files to a single top level class(源文件僅限有單個頂層類)
- Chapter 5. Generics(泛型)
- Item 26: Do not use raw types(不要使用原始類型)
- Item 27: Eliminate unchecked warnings(消除 unchecked 警告)
- Item 28: Prefer lists to arrays(list 優于數組)
- Item 29: Favor generic types(優先使用泛型)
- Item 30: Favor generic methods(優先使用泛型方法)
- Item 31: Use bounded wildcards to increase API flexibility(使用有界通配符增加 API 的靈活性)
- Item 32: Combine generics and varargs judiciously(明智地合用泛型和可變參數)
- Item 33: Consider typesafe heterogeneous containers(考慮類型安全的異構容器)
- Chapter 6. Enums and Annotations(枚舉和注解)
- Item 34: Use enums instead of int constants(用枚舉類型代替 int 常量)
- Item 35: Use instance fields instead of ordinals(使用實例字段替代序數)
- Item 36: Use EnumSet instead of bit fields(用 EnumSet 替代位字段)
- Item 37: Use EnumMap instead of ordinal indexing(使用 EnumMap 替換序數索引)
- Item 38: Emulate extensible enums with interfaces(使用接口模擬可擴展枚舉)
- Item 39: Prefer annotations to naming patterns(注解優于命名模式)
- Item 40: Consistently use the Override annotation(堅持使用 @Override 注解)
- Item 41: Use marker interfaces to define types(使用標記接口定義類型)
- Chapter 7. Lambdas and Streams(λ 表達式和流)
- Item 42: Prefer lambdas to anonymous classes(λ 表達式優于匿名類)
- Item 43: Prefer method references to lambdas(方法引用優于 λ 表達式)
- Item 44: Favor the use of standard functional interfaces(優先使用標準函數式接口)
- Item 45: Use streams judiciously(明智地使用流)
- Item 46: Prefer side effect free functions in streams(在流中使用無副作用的函數)
- Item 47: Prefer Collection to Stream as a return type(優先選擇 Collection 而不是流作為返回類型)
- Item 48: Use caution when making streams parallel(謹慎使用并行流)
- Chapter 8. Methods(方法)
- Item 49: Check parameters for validity(檢查參數的有效性)
- Item 50: Make defensive copies when needed(在需要時制作防御性副本)
- Item 51: Design method signatures carefully(仔細設計方法簽名)
- Item 52: Use overloading judiciously(明智地使用重載)
- Item 53: Use varargs judiciously(明智地使用可變參數)
- Item 54: Return empty collections or arrays, not nulls(返回空集合或數組,而不是 null)
- Item 55: Return optionals judiciously(明智地的返回 Optional)
- Item 56: Write doc comments for all exposed API elements(為所有公開的 API 元素編寫文檔注釋)
- Chapter 9. General Programming(通用程序設計)
- Item 57: Minimize the scope of local variables(將局部變量的作用域最小化)
- Item 58: Prefer for-each loops to traditional for loops(for-each 循環優于傳統的 for 循環)
- Item 59: Know and use the libraries(了解并使用庫)
- Item 60: Avoid float and double if exact answers are required(若需要精確答案就應避免使用 float 和 double 類型)
- Item 61: Prefer primitive types to boxed primitives(基本數據類型優于包裝類)
- Item 62: Avoid strings where other types are more appropriate(其他類型更合適時應避免使用字符串)
- Item 63: Beware the performance of string concatenation(當心字符串連接引起的性能問題)
- Item 64: Refer to objects by their interfaces(通過接口引用對象)
- Item 65: Prefer interfaces to reflection(接口優于反射)
- Item 66: Use native methods judiciously(明智地使用本地方法)
- Item 67: Optimize judiciously(明智地進行優化)
- Item 68: Adhere to generally accepted naming conventions(遵守被廣泛認可的命名約定)
- Chapter 10. Exceptions(異常)
- Item 69: Use exceptions only for exceptional conditions(僅在確有異常條件下使用異常)
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors(對可恢復情況使用 checked 異常,對編程錯誤使用運行時異常)
- Item 71: Avoid unnecessary use of checked exceptions(避免不必要地使用 checked 異常)
- Item 72: Favor the use of standard exceptions(鼓勵復用標準異常)
- Item 73: Throw exceptions appropriate to the abstraction(拋出能用抽象解釋的異常)
- Item 74: Document all exceptions thrown by each method(為每個方法記錄會拋出的所有異常)
- Item 75: Include failure capture information in detail messages(異常詳細消息中應包含捕獲失敗的信息)
- Item 76: Strive for failure atomicity(盡力保證故障原子性)
- Item 77: Don’t ignore exceptions(不要忽略異常)
- Chapter 11. Concurrency(并發)
- Item 78: Synchronize access to shared mutable data(對共享可變數據的同步訪問)
- Item 79: Avoid excessive synchronization(避免過度同步)
- Item 80: Prefer executors, tasks, and streams to threads(Executor、task、流優于直接使用線程)
- Item 81: Prefer concurrency utilities to wait and notify(并發實用工具優于 wait 和 notify)
- Item 82: Document thread safety(文檔應包含線程安全屬性)
- Item 83: Use lazy initialization judiciously(明智地使用延遲初始化)
- Item 84: Don’t depend on the thread scheduler(不要依賴線程調度器)
- Chapter 12. Serialization(序列化)
- Item 85: Prefer alternatives to Java serialization(優先選擇 Java 序列化的替代方案)
- Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)
- Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式)
- Item 88: Write readObject methods defensively(防御性地編寫 readObject 方法)
- Item 89: For instance control, prefer enum types to readResolve(對于實例控制,枚舉類型優于 readResolve)
- Item 90: Consider serialization proxies instead of serialized instances(考慮以序列化代理代替序列化實例)