## Chapter 6. Enums and Annotations(枚舉和注解)
### Item 34: Use enums instead of int constants(用枚舉類型代替 int 常量)
An enumerated type is a type whose legal values consist of a fixed set of constants, such as the seasons of the year, the planets in the solar system, or the suits in a deck of playing cards. Before enum types were added to the language, a common pattern for representing enumerated types was to declare a group of named int constants, one for each member of the type:
枚舉類型是這樣一種類型:它合法的值由一組固定的常量組成,如:一年中的季節、太陽系中的行星或撲克牌中的花色。在枚舉類型被添加到 JAVA 之前,表示枚舉類型的一種常見模式是聲明一組 int 的常量,每個類型的成員都有一個:
```
// The int enum pattern - severely deficient!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
```
This technique, known as the int enum pattern, has many shortcomings. It provides nothing in the way of type safety and little in the way of expressive power. The compiler won’t complain if you pass an apple to a method that expects an orange, compare apples to oranges with the == operator, or worse:
這種技術稱為 int 枚舉模式,它有許多缺點。它沒有提供任何類型安全性,并且幾乎不具備表現力。如果你傳遞一個蘋果給方法,希望得到一個橘子,使用 == 操作符比較蘋果和橘子時編譯器并不會提示錯誤,或更糟的情況:
```
// Tasty citrus flavored applesauce!
int i = (APPLE_FUJI - ORANGE_TEMPLE) / APPLE_PIPPIN;
```
Note that the name of each apple constant is prefixed with APPLE_ and the name of each orange constant is prefixed with ORANGE_. This is because Java doesn’t provide namespaces for int enum groups. Prefixes prevent name clashes when two int enum groups have identically named constants, for example between ELEMENT_MERCURY and PLANET_MERCURY.
注意,每個 apple 常量的名稱都以 APPLE_ 為前綴,每個 orange 常量的名稱都以 ORANGE_ 為前綴。這是因為 Java 不為這些 int 枚舉提供名稱空間。當兩組 int 枚舉具有相同的命名常量時,前綴可以防止名稱沖突,例如 ELEMENT_MERCURY 和 PLANET_MERCURY 之間的沖突。
Programs that use int enums are brittle. Because int enums are constant variables [JLS, 4.12.4], their int values are compiled into the clients that use them [JLS, 13.1]. If the value associated with an int enum is changed, its clients must be recompiled. If not, the clients will still run, but their behavior will be incorrect.
使用 int 枚舉的程序很脆弱。因為 int 枚舉是常量變量 [JLS, 4.12.4],所以它們的值被編譯到使用它們的客戶端中 [JLS, 13.1]。如果與 int 枚舉關聯的值發生了更改,則必須重新編譯客戶端。如果不重新編譯,客戶端仍然可以運行,但是他們的行為將是錯誤的。
There is no easy way to translate int enum constants into printable strings. If you print such a constant or display it from a debugger, all you see is a number, which isn’t very helpful. There is no reliable way to iterate over all the int enum constants in a group, or even to obtain the size of an int enum group.
沒有一種簡單的方法可以將 int 枚舉常量轉換為可打印的字符串。如果你打印這樣的常量或從調試器中顯示它,你所看到的只是一個數字,這不是很有幫助。沒有可靠的方法可以遍歷組中的所有 int 枚舉常量,甚至無法獲得組的大小。
You may encounter a variant of this pattern in which String constants are used in place of int constants. This variant, known as the String enum pattern, is even less desirable. While it does provide printable strings for its constants, it can lead naive users to hard-code string constants into client code instead of using field names. If such a hard-coded string constant contains a typographical error, it will escape detection at compile time and result in bugs at runtime. Also, it might lead to performance problems, because it relies on string comparisons.
可能會遇到這種模式的另一種形式:使用 String 常量代替 int 常量。這種稱為 String 枚舉模式的變體甚至更不可取。雖然它確實為常量提供了可打印的字符串,但是它可能會導致不知情的用戶將字符串常量硬編碼到客戶端代碼中,而不是使用字段名。如果這樣一個硬編碼的 String 常量包含一個排版錯誤,它將在編譯時躲過檢測,并在運行時導致錯誤。此外,它可能會導致性能問題,因為它依賴于字符串比較。
Luckily, Java provides an alternative that avoids all the shortcomings of the int and string enum patterns and provides many added benefits. It is the enum type [JLS, 8.9]. Here’s how it looks in its simplest form:
幸運的是,Java 提供了一種替代方案,它避免了 int 和 String 枚舉模式的所有缺點,并提供了許多額外的好處。它就是枚舉類型 [JLS, 8.9]。下面是它最簡單的形式:
```
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
```
On the surface, these enum types may appear similar to those of other languages, such as C, C++, and C#, but appearances are deceiving. Java’s enum types are full-fledged classes, far more powerful than their counterparts in these other languages, where enums are essentially int values.
從表面上看,這些枚舉類型可能與其他語言(如 C、c++ 和 c#)的枚舉類型類似,但不能只看表象。Java 的枚舉類型是成熟的類,比其他語言中的枚舉類型功能強大得多,在其他語言中的枚舉本質上是 int 值。
The basic idea behind Java’s enum types is simple: they are classes that export one instance for each enumeration constant via a public static final field. Enum types are effectively final, by virtue of having no accessible constructors. Because clients can neither create instances of an enum type nor extend it, there can be no instances but the declared enum constants. In other words, enum types are instance-controlled (page 6). They are a generalization of singletons (Item 3), which are essentially single-element enums.
Java 枚舉類型背后的基本思想很簡單:它們是通過 public static final 修飾的字段為每個枚舉常量導出一個實例的類。枚舉類型實際上是 final 類型,因為沒有可訪問的構造函數。客戶端既不能創建枚舉類型的實例,也不能繼承它,所以除了聲明的枚舉常量之外,不能有任何實例。換句話說,枚舉類型是實例受控的類(參閱第 6 頁,[Item-1](/Chapter-2/Chapter-2-Item-1-Consider-static-factory-methods-instead-of-constructors.md))。它們是單例([Item-3](/Chapter-2/Chapter-2-Item-3-Enforce-the-singleton-property-with-a-private-constructor-or-an-enum-type.md))的推廣應用,單例本質上是單元素的枚舉。
Enums provide compile-time type safety. If you declare a parameter to be of type Apple, you are guaranteed that any non-null object reference passed to the parameter is one of the three valid Apple values. Attempts to pass values of the wrong type will result in compile-time errors, as will attempts to assign an expression of one enum type to a variable of another, or to use the == operator to compare values of different enum types.
枚舉提供編譯時類型的安全性。如果將參數聲明為 Apple 枚舉類型,則可以保證傳遞給該參數的任何非空對象引用都是三個有效 Apple 枚舉值之一。嘗試傳遞錯誤類型的值將導致編譯時錯誤,將一個枚舉類型的表達式賦值給另一個枚舉類型的變量,或者使用 == 運算符比較不同枚舉類型的值同樣會導致錯誤。
Enum types with identically named constants coexist peacefully because each type has its own namespace. You can add or reorder constants in an enum type without recompiling its clients because the fields that export the constants provide a layer of insulation between an enum type and its clients: constant values are not compiled into the clients as they are in the int enum patterns. Finally, you can translate enums into printable strings by calling their toString method.
名稱相同的枚舉類型常量能和平共存,因為每種類型都有自己的名稱空間。你可以在枚舉類型中添加或重新排序常量,而無需重新編譯其客戶端,因為導出常量的字段在枚舉類型及其客戶端之間提供了一層隔離:常量值不會像在 int 枚舉模式中那樣編譯到客戶端中。最后,你可以通過調用枚舉的 toString 方法將其轉換為可打印的字符串。
In addition to rectifying the deficiencies of int enums, enum types let you add arbitrary methods and fields and implement arbitrary interfaces. They provide high-quality implementations of all the Object methods (Chapter 3), they implement Comparable (Item 14) and Serializable (Chapter 12), and their serialized form is designed to withstand most changes to the enum type.
除了糾正 int 枚舉的不足之外,枚舉類型還允許添加任意方法和字段并實現任意接口。它們提供了所有 Object 方法的高質量實現(參閱 Chapter 3),還實現了 Comparable([Item-14](/Chapter-3/Chapter-3-Item-14-Consider-implementing-Comparable.md))和 Serializable(參閱 Chapter 12),并且它們的序列化形式被設計成能夠適應枚舉類型的可變性。
So why would you want to add methods or fields to an enum type? For starters, you might want to associate data with its constants. Our Apple and Orange types, for example, might benefit from a method that returns the color of the fruit, or one that returns an image of it. You can augment an enum type with any method that seems appropriate. An enum type can start life as a simple collection of enum constants and evolve over time into a full-featured abstraction.
那么,為什么要向枚舉類型添加方法或字段呢?首先,你可能希望將數據與其常量關聯起來。例如,我們的 Apple 和 Orange 類型可能受益于返回水果顏色的方法,或者返回水果圖像的方法。你可以使用任何適當的方法來擴充枚舉類型。枚舉類型可以從枚舉常量的簡單集合開始,并隨著時間的推移演變為功能齊全的抽象。
For a nice example of a rich enum type, consider the eight planets of our solar system. Each planet has a mass and a radius, and from these two attributes you can compute its surface gravity. This in turn lets you compute the weight of an object on the planet’s surface, given the mass of the object. Here’s how this enum looks. The numbers in parentheses after each enum constant are parameters that are passed to its constructor. In this case, they are the planet’s mass and radius:
對于富枚舉類型來說,有個很好的例子,考慮我們太陽系的八顆行星。每顆行星都有質量和半徑,通過這兩個屬性你可以計算出它的表面引力。反過來,可以給定物體的質量,讓你計算出一個物體在行星表面的重量。這個枚舉是這樣的。每個枚舉常量后括號中的數字是傳遞給其構造函數的參數。在本例中,它們是行星的質量和半徑:
```
// Enum type with data and behavior
public enum Planet {
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
private final double mass; // In kilograms
private final double radius; // In meters
private final double surfaceGravity; // In m / s^2
// Universal gravitational constant in m^3 / kg s^2
private static final double G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
public double mass() { return mass; }
public double radius() { return radius; }
public double surfaceGravity() { return surfaceGravity; }
public double surfaceWeight(double mass) {
return mass * surfaceGravity; // F = ma
}
}
```
It is easy to write a rich enum type such as Planet. **To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields.** Enums are by their nature immutable, so all fields should be final (Item 17). Fields can be public, but it is better to make them private and provide public accessors (Item 16). In the case of Planet, the constructor also computes and stores the surface gravity, but this is just an optimization. The gravity could be recomputed from the mass and radius each time it was used by the surfaceWeight method, which takes an object’s mass and returns its weight on the planet represented by the constant. While the Planet enum is simple, it is surprisingly powerful. Here is a short program that takes the earth weight of an object (in any unit) and prints a nice table of the object’s weight on all eight planets (in the same unit):
編寫一個富枚舉類型很容易,如上述的 Planet。**要將數據與枚舉常量關聯,可聲明實例字段并編寫一個構造函數,該構造函數接受數據并將其存儲在字段中。** 枚舉本質上是不可變的,因此所有字段都應該是 final([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。字段可以是公共的,但是最好將它們設置為私有并提供公共訪問器([Item-16](/Chapter-4/Chapter-4-Item-16-In-public-classes-use-accessor-methods-not-public-fields.md))。在 Planet 的例子中,構造函數還計算和存儲表面重力,但這只是一個優化。每一次使用 surfaceWeight 方法時,都可以通過質量和半徑重新計算重力。surfaceWeight 方法獲取一個物體的質量,并返回其在該常數所表示的行星上的重量。雖然 Planet 枚舉很簡單,但它的力量驚人。下面是一個簡短的程序,它獲取一個物體的地球重量(以任何單位表示),并打印一個漂亮的表格,顯示該物體在所有 8 個行星上的重量(以相同的單位表示):
```
public class WeightTable {
public static void main(String[] args) {
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Weight on %s is %f%n",p, p.surfaceWeight(mass));
}
}
```
Note that Planet, like all enums, has a static values method that returns an array of its values in the order they were declared. Note also that the toString method returns the declared name of each enum value, enabling easy printing by println and printf. If you’re dissatisfied with this string representation, you can change it by overriding the toString method. Here is the result of running our WeightTable program (which doesn’t override toString) with the command line argument 185:
請注意,Planet 和所有枚舉一樣,有一個靜態 values() 方法,該方法按照聲明值的順序返回其值的數組。還要注意的是,toString 方法返回每個枚舉值的聲明名稱,這樣就可以通過 println 和 printf 輕松打印。如果你對這個字符串表示不滿意,可以通過重寫 toString 方法來更改它。下面是用命令行運行我們的 WeightTable 程序(未覆蓋 toString)的結果:
```
Weight on MERCURY is 69.912739
Weight on VENUS is 167.434436
Weight on EARTH is 185.000000
Weight on MARS is 70.226739
Weight on JUPITER is 467.990696
Weight on SATURN is 197.120111
Weight on URANUS is 167.398264
Weight on NEPTUNE is 210.208751
```
Until 2006, two years after enums were added to Java, Pluto was a planet. This raises the question “what happens when you remove an element from an enum type?” The answer is that any client program that doesn’t refer to the removed element will continue to work fine. So, for example, our WeightTable program would simply print a table with one fewer row. And what of a client program that refers to the removed element (in this case, Planet.Pluto)? If you recompile the client program, the compilation will fail with a helpful error message at the line that refers to the erstwhile planet; if you fail to recompile the client, it will throw a helpful exception from this line at runtime. This is the best behavior you could hope for, far better than what you’d get with the int enum pattern.
直到 2006 年,也就是枚舉被添加到 Java 的兩年后,冥王星還是一顆行星。這就提出了一個問題:「從枚舉類型中刪除元素時會發生什么?」答案是,任何不引用被刪除元素的客戶端程序將繼續正常工作。例如,我們的 WeightTable 程序只需打印一個少一行的表。那么引用被刪除元素(在本例中是 Planet.Pluto)的客戶端程序又如何呢?如果重新編譯客戶端程序,編譯將失敗,并在引用該「過時」行星的行中顯示一條有用的錯誤消息;如果你未能重新編譯客戶端,它將在運行時從這行拋出一個有用的異常。這是你所希望的最佳行為,比 int 枚舉模式要好得多。
Some behaviors associated with enum constants may need to be used only from within the class or package in which the enum is defined. Such behaviors are best implemented as private or package-private methods. Each constant then carries with it a hidden collection of behaviors that allows the class or package containing the enum to react appropriately when presented with the constant. Just as with other classes, unless you have a compelling reason to expose an enum method to its clients, declare it private or, if need be, package-private (Item 15).
與枚舉常量相關的一些行為可能只需要在定義枚舉的類或包中使用。此類行為最好以私有或包私有方法來實現。然后,每個常量都帶有一個隱藏的行為集合,允許包含枚舉的類或包在使用該常量時做出適當的反應。與其他類一樣,除非你有充分的理由向其客戶端公開枚舉方法,否則將其聲明為私有的,或者在必要時聲明為包私有([Item-15](/Chapter-4/Chapter-4-Item-15-Minimize-the-accessibility-of-classes-and-members.md))。
If an enum is generally useful, it should be a top-level class; if its use is tied to a specific top-level class, it should be a member class of that top-level class (Item 24). For example, the java.math.RoundingMode enum represents a rounding mode for decimal fractions. These rounding modes are used by the BigDecimal class, but they provide a useful abstraction that is not fundamentally tied to BigDecimal. By making RoundingMode a top-level enum, the library designers encourage any programmer who needs rounding modes to reuse this enum, leading to increased consistency across APIs.
通常,如果一個枚舉用途廣泛,那么它應該是頂級類;如果它被綁定到一個特定的頂級類使用,那么它應該是這個頂級類([Item-24](/Chapter-4/Chapter-4-Item-24-Favor-static-member-classes-over-nonstatic.md))的成員類。例如,java.math.RoundingMode 枚舉表示小數部分的舍入模式。BigDecimal 類使用這些四舍五入模式,但是它們提供了一個有用的抽象,這個抽象與 BigDecimal 沒有本質上的聯系。通過使 RoundingMode 成為頂級枚舉,庫設計人員支持任何需要舍入模式的程序員復用該枚舉,從而提高 API 之間的一致性。
The techniques demonstrated in the Planet example are sufficient for most enum types, but sometimes you need more. There is different data associated with each Planet constant, but sometimes you need to associate fundamentally different behavior with each constant. For example, suppose you are writing an enum type to represent the operations on a basic four-function calculator and you want to provide a method to perform the arithmetic operation represented by each constant. One way to achieve this is to switch on the value of the enum:
Planet 示例中演示的技術對于大多數枚舉類型來說已經足夠了,但有時還需要更多。每個行星常數都有不同的數據,但有時你需要將基本不同的行為與每個常數聯系起來。例如,假設你正在編寫一個枚舉類型來表示一個基本的四則運算計算器上的操作,并且你希望提供一個方法來執行由每個常量表示的算術操作。實現這一點的一種方式是用 switch 接收枚舉值:
```
// Enum type that switches on its own value - questionable
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE;
// Do the arithmetic operation represented by this constant
public double apply(double x, double y) {
switch(this) {
case PLUS: return x + y;
case MINUS: return x - y;
case TIMES: return x * y;
case DIVIDE: return x / y;
}
throw new AssertionError("Unknown op: "+ this);
}
}
```
This code works, but it isn’t very pretty. It won’t compile without the throw statement because the end of the method is technically reachable, even though it will never be reached [JLS, 14.21]. Worse, the code is fragile. If you add a new enum constant but forget to add a corresponding case to the switch, the enum will still compile, but it will fail at runtime when you try to apply the new operation.
這段代碼可以工作,但不是很漂亮。如果沒有 throw 語句,它將無法編譯,因為從理論上講,方法的結尾是可到達的,盡管它確實永遠不會到達 [JLS, 14.21]。更糟糕的是,代碼很脆弱。如果你添加了一個新的枚舉常量,但忘記向 switch 添加相應的 case,則枚舉仍將編譯,但在運行時嘗試應用新操作時將失敗。
Luckily, there is a better way to associate a different behavior with each enum constant: declare an abstract apply method in the enum type, and override it with a concrete method for each constant in a constant-specific class body. Such methods are known as constant-specific method implementations:
幸運的是,有一種更好的方法可以將不同的行為與每個枚舉常量關聯起來:在枚舉類型中聲明一個抽象的 apply 方法,并用一個特定于常量的類體中的每個常量的具體方法覆蓋它。這些方法稱為特定常量方法實現:
```
// Enum type with constant-specific method implementations
public enum Operation {
PLUS {public double apply(double x, double y){return x + y;}},
MINUS {public double apply(double x, double y){return x - y;}},
TIMES {public double apply(double x, double y){return x * y;}},
DIVIDE{public double apply(double x, double y){return x / y;}};
public abstract double apply(double x, double y);
}
```
If you add a new constant to the second version of Operation, it is unlikely that you’ll forget to provide an apply method, because the method immediately follows each constant declaration. In the unlikely event that you do forget, the compiler will remind you because abstract methods in an enum type must be overridden with concrete methods in all of its constants.
如果你在 Operation 枚舉的第二個版本中添加一個新常量,那么你不太可能忘記提供一個 apply 方法,因為該方法緊跟每個常量聲明。在不太可能忘記的情況下,編譯器會提醒你,因為枚舉類型中的抽象方法必須用其所有常量中的具體方法覆蓋。
Constant-specific method implementations can be combined with constantspecific data. For example, here is a version of Operation that overrides the toString method to return the symbol commonly associated with the operation:
特定常量方法實現可以與特定于常量的數據相結合。例如,下面是 Operation 枚舉的一個版本,它重寫 toString 方法來返回與操作相關的符號:
**譯注:原文 constantspecific data 應修改為 constant-specific data ,譯為「特定常量數據」**
```
// Enum type with constant-specific class bodies and data
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
};
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
@Override
public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
```
The toString implementation shown makes it easy to print arithmetic expressions, as demonstrated by this little program:
重寫的 toString 實現使得打印算術表達式變得很容易,如下面的小程序所示:
```
public static void main(String[] args) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values())
System.out.printf("%f %s %f = %f%n",x, op, y, op.apply(x, y));
}
```
Running this program with 2 and 4 as command line arguments produces the following output:
以 2 和 4 作為命令行參數運行這個程序將產生以下輸出:
```
2.000000 + 4.000000 = 6.000000
2.000000 - 4.000000 = -2.000000
2.000000 * 4.000000 = 8.000000
2.000000 / 4.000000 = 0.500000
```
Enum types have an automatically generated valueOf(String) method that translates a constant’s name into the constant itself. If you override the toString method in an enum type, consider writing a fromString method to translate the custom string representation back to the corresponding enum. The following code (with the type name changed appropriately) will do the trick for any enum, so long as each constant has a unique string representation:
枚舉類型有一個自動生成的 valueOf(String) 方法,該方法將常量的名稱轉換為常量本身。如果在枚舉類型中重寫 toString 方法,可以考慮編寫 fromString 方法將自定義字符串表示形式轉換回相應的枚舉。只要每個常量都有唯一的字符串表示形式,下面的代碼(類型名稱適當更改)就可以用于任何枚舉:
```
// Implementing a fromString method on an enum type
private static final Map<String, Operation> stringToEnum =Stream.of(values()).collect(toMap(Object::toString, e -> e));
// Returns Operation for string, if any
public static Optional<Operation> fromString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
```
Note that the Operation constants are put into the stringToEnum map from a static field initialization that runs after the enum constants have been created. The previous code uses a stream (Chapter 7) over the array returned by the values() method; prior to Java 8, we would have created an empty hash map and iterated over the values array inserting the string-to-enum mappings into the map, and you can still do it that way if you prefer. But note that attempting to have each constant put itself into a map from its own constructor does not work. It would cause a compilation error, which is good thing because if it were legal, it would cause a NullPointerException at runtime. Enum constructors aren’t permitted to access the enum’s static fields, with the exception of constant variables (Item 34). This restriction is necessary because static fields have not yet been initialized when enum constructors run. A special case of this restriction is that enum constants cannot access one another from their constructors.
注意,Operation 枚舉的常量是從創建枚舉常量之后運行的靜態字段初始化中放入 stringToEnum 的。上述代碼在 values() 方法返回的數組上使用流(參閱第 7 章);在 Java 8 之前,我們將創建一個空 HashMap,并遍歷值數組,將自定義字符串與枚舉的映射插入到 HashMap 中,如果你愿意,你仍然可以這樣做。但是請注意,試圖讓每個常量通過構造函數將自身放入 HashMap 中是行不通的。它會導致編譯錯誤,這是好事,因為如果合法,它會在運行時導致 NullPointerException。枚舉構造函數不允許訪問枚舉的靜態字段,常量變量除外([Item-34](/Chapter-6/Chapter-6-Item-34-Use-enums-instead-of-int-constants.md))。這個限制是必要的,因為在枚舉構造函數運行時靜態字段還沒有初始化。這種限制的一個特殊情況是枚舉常量不能從它們的構造函數中相互訪問。
Also note that the fromString method returns an `Optional<String>`. This allows the method to indicate that the string that was passed in does not represent a valid operation, and it forces the client to confront this possibility (Item 55).
還要注意 fromString 方法返回一個 `Optional<String>`。這允許該方法提示傳入的字符串并非有效操作,并強制客戶端處理這種可能([Item-55](/Chapter-8/Chapter-8-Item-55-Return-optionals-judiciously.md))。
A disadvantage of constant-specific method implementations is that they make it harder to share code among enum constants. For example, consider an enum representing the days of the week in a payroll package. This enum has a method that calculates a worker’s pay for that day given the worker’s base salary (per hour) and the number of minutes worked on that day. On the five weekdays, any time worked in excess of a normal shift generates overtime pay; on the two weekend days, all work generates overtime pay. With a switch statement, it’s easy to do this calculation by applying multiple case labels to each of two code fragments:
特定常量方法實現的一個缺點是,它們使得在枚舉常量之間共享代碼變得更加困難。例如,考慮一個表示一周當中計算工資發放的枚舉。枚舉有一個方法,該方法根據工人的基本工資(每小時)和當天的工作分鐘數計算工人當天的工資。在五個工作日內,任何超過正常輪班時間的工作都會產生加班費;在兩個周末,所有的工作都會產生加班費。使用 switch 語句,通過多個 case 標簽應用于每一類情況,可以很容易地進行計算:
```
// Enum that switches on its value to share code - questionable
enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY, SUNDAY;
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minutesWorked, int payRate) {
int basePay = minutesWorked * payRate;
int overtimePay;
switch(this) {
case SATURDAY:
case SUNDAY: // Weekend
overtimePay = basePay / 2;
break;
default: // Weekday
overtimePay = minutesWorked <= MINS_PER_SHIFT ?0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
}
return basePay + overtimePay;
}
}
```
**譯注 1:該例子中,加班的每分鐘工資為工作日每分鐘工資(payRate)的一半**
**譯注 2:原文中 pay 方法存在問題,說明如下:**
```
// 基本工資 basePay 不應該直接將工作時間參與計算,如果工作日存在加班的情況,會將加班時間也計入基本工資計算。假設在周一工作 10 小時,假設每分鐘 1 元:
/*
修改前:
基本工資 basePay = minutesWorked * payRate=10*60*1=600(不應該將 2 小時加班也計入正常工作時間)
加班工資 overtimePay = (minutesWorked - MINS_PER_SHIFT) * payRate / 2=2*60*1/2=60
合計= basePay + overtimePay=660
修改后:
基本工資 basePay = MINS_PER_SHIFT * payRate=8*60*1=480(基本工資最高只能按照 8 小時計算)
加班工資 overtimePay = (minutesWorked - MINS_PER_SHIFT) * payRate / 2=2*60*1/2=60
合計= basePay + overtimePay=540
*/
// 修改后代碼:
int pay(int minutesWorked, int payRate) {
int basePay = 0;
int overtimePay;
switch (this) {
case SATURDAY:
case SUNDAY: // Weekend
overtimePay = minutesWorked * payRate / 2;
break;
default: // Weekday
basePay = minutesWorked <= MINS_PER_SHIFT ? minutesWorked * payRate : MINS_PER_SHIFT * payRate;
overtimePay = minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
}
return basePay + overtimePay;
}
```
This code is undeniably concise, but it is dangerous from a maintenance perspective. Suppose you add an element to the enum, perhaps a special value to represent a vacation day, but forget to add a corresponding case to the switch statement. The program will still compile, but the pay method will silently pay the worker the same amount for a vacation day as for an ordinary weekday.
不可否認,這段代碼非常簡潔,但是從維護的角度來看,它是危險的。假設你向枚舉中添加了一個元素,可能是一個表示假期的特殊值,但是忘記向 switch 語句添加相應的 case。這個程序仍然會被編譯,但是 pay 方法會把假期默認當做普通工作日并支付工資。
To perform the pay calculation safely with constant-specific method implementations, you would have to duplicate the overtime pay computation for each constant, or move the computation into two helper methods, one for weekdays and one for weekend days, and invoke the appropriate helper method from each constant. Either approach would result in a fair amount of boilerplate code, substantially reducing readability and increasing the opportunity for error.
為了使用特定常量方法實現安全地執行工資計算,你必須為每個常量復制加班費計算,或者將計算移動到兩個輔助方法中,一個用于工作日,一個用于周末,再從每個常量調用適當的輔助方法。任何一種方法都會導致相當數量的樣板代碼,極大地降低可讀性并增加出錯的機會。
The boilerplate could be reduced by replacing the abstract overtimePay method on PayrollDay with a concrete method that performs the overtime calculation for weekdays. Then only the weekend days would have to override the method. But this would have the same disadvantage as the switch statement: if you added another day without overriding the overtimePay method, you would silently inherit the weekday calculation.
用工作日加班計算的具體方法代替發薪日的抽象加班法,可以減少樣板。那么只有周末才需要重寫該方法。但是這與 switch 語句具有相同的缺點:如果你在不覆蓋 overtimePay 方法的情況下添加了另一天,那么你將默默地繼承工作日的計算。
What you really want is to be forced to choose an overtime pay strategy each time you add an enum constant. Luckily, there is a nice way to achieve this. The idea is to move the overtime pay computation into a private nested enum, and to pass an instance of this strategy enum to the constructor for the PayrollDay enum. The PayrollDay enum then delegates the overtime pay calculation to the strategy enum, eliminating the need for a switch statement or constantspecific method implementation in PayrollDay. While this pattern is less concise than the switch statement, it is safer and more flexible:
你真正想要的是在每次添加枚舉常量時被迫選擇加班費策略。幸運的是,有一個很好的方法可以實現這一點。其思想是將加班費計算移到私有嵌套枚舉中,并將此策略枚舉的實例傳遞給 PayrollDay 枚舉的構造函數。然后 PayrollDay 枚舉將加班費計算委托給策略枚舉,從而消除了在 PayrollDay 中使用 switch 語句或特定于常量的方法實現的需要。雖然這種模式不如 switch 語句簡潔,但它更安全,也更靈活:
```
// The strategy enum pattern
enum PayrollDay {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDay(PayType payType) { this.payType = payType; }
PayrollDay() { this(PayType.WEEKDAY); } // Default
int pay(int minutesWorked, int payRate) {
return payType.pay(minutesWorked, payRate);
}
// The strategy enum type
private enum PayType {
WEEKDAY {
int overtimePay(int minsWorked, int payRate) {
return minsWorked <= MINS_PER_SHIFT ? 0 :(minsWorked - MINS_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
int overtimePay(int minsWorked, int payRate) {
return minsWorked * payRate / 2;
}
};
abstract int overtimePay(int mins, int payRate);
private static final int MINS_PER_SHIFT = 8 * 60;
int pay(int minsWorked, int payRate) {
int basePay = minsWorked * payRate;
return basePay + overtimePay(minsWorked, payRate);
}
}
}
```
**譯注:上述代碼 pay 方法也存將加班時間計入基本工資計算的問題,修改如下:**
```
int pay(int minsWorked, int payRate) {
int basePay = minsWorked <= MINS_PER_SHIFT ? minsWorked * payRate : MINS_PER_SHIFT * payRate;
return basePay + overtimePay(minsWorked, payRate);
}
```
If switch statements on enums are not a good choice for implementing constant-specific behavior on enums, what are they good for? **Switches on enums are good for augmenting enum types with constant-specific behavior.** For example, suppose the Operation enum is not under your control and you wish it had an instance method to return the inverse of each operation. You could simulate the effect with the following static method:
如果在枚舉上實現特定常量的行為時 switch 語句不是一個好的選擇,那么它們有什么用呢?**枚舉中的 switch 有利于擴展具有特定常量行為的枚舉類型。** 例如,假設 Operation 枚舉不在你的控制之下,你希望它有一個實例方法來返回每個操作的逆操作。你可以用以下靜態方法模擬效果:
```
// Switch on an enum to simulate a missing method
public static Operation inverse(Operation op) {
switch(op) {
case PLUS: return Operation.MINUS;
case MINUS: return Operation.PLUS;
case TIMES: return Operation.DIVIDE;
case DIVIDE: return Operation.TIMES;
default: throw new AssertionError("Unknown op: " + op);
}
}
```
You should also use this technique on enum types that are under your control if a method simply doesn’t belong in the enum type. The method may be required for some use but is not generally useful enough to merit inclusion in the enum type.
如果一個方法不屬于枚舉類型,那么還應該在你控制的枚舉類型上使用這種技術。該方法可能適用于某些特殊用途,但通常如果沒有足夠的好處,就不值得包含在枚舉類型中。
Enums are, generally speaking, comparable in performance to int constants. A minor performance disadvantage of enums is that there is a space and time cost to load and initialize enum types, but it is unlikely to be noticeable in practice.
一般來說,枚舉在性能上可與 int 常量相比。枚舉在性能上有一個小缺點,加載和初始化枚舉類型需要花費空間和時間,但是在實際應用中這一點可能不太明顯。
So when should you use enums? **Use enums any time you need a set of constants whose members are known at compile time.** Of course, this includes “natural enumerated types,” such as the planets, the days of the week, and the chess pieces. But it also includes other sets for which you know all the possible values at compile time, such as choices on a menu, operation codes, and command line flags. **It is not necessary that the set of constants in an enum type stay fixed for all time.** The enum feature was specifically designed to allow for binary compatible evolution of enum types.
那么什么時候應該使用枚舉呢?**在需要一組常量時使用枚舉,這些常量的成員在編譯時是已知的。** 當然,這包括「自然枚舉類型」,如行星、星期幾和棋子。但是它還包括其他在編譯時已知所有可能值的集合,例如菜單上的選項、操作代碼和命令行標志。**枚舉類型中的常量集沒有必要一直保持固定。** 枚舉的特性是專門為枚舉類型的二進制兼容進化而設計的。
In summary, the advantages of enum types over int constants are compelling. Enums are more readable, safer, and more powerful. Many enums require no explicit constructors or members, but others benefit from associating data with each constant and providing methods whose behavior is affected by this data. Fewer enums benefit from associating multiple behaviors with a single method. In this relatively rare case, prefer constant-specific methods to enums that switch on their own values. Consider the strategy enum pattern if some, but not all, enum constants share common behaviors.
總之,枚舉類型相對于 int 常量的優勢是毋庸置疑的。枚舉更易于閱讀、更安全、更強大。許多枚舉不需要顯式構造函數或成員,但有些枚舉則受益于將數據與每個常量關聯,并提供行為受數據影響的方法。將多個行為與一個方法關聯起來,這樣的枚舉更少。在這種相對少見的情況下,相對于使用 switch 的枚舉,特定常量方法更好。如果枚舉常量有一些(但不是全部)共享公共行為,請考慮策略枚舉模式。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-6/Chapter-6-Introduction.md)**
- **Next Item(下一條目):[Item 35: Use instance fields instead of ordinals(使用實例字段替代序數)](/Chapter-6/Chapter-6-Item-35-Use-instance-fields-instead-of-ordinals.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(考慮以序列化代理代替序列化實例)