1. Overview
1.概述
The Java enum type provides a language-supported way to create and use constant values. By defining a finite set of values, the enum is more type safe than constant literal variables like String or int.
Java enum类型提供了一种语言支持的方式来创建和使用常量值。通过定义有限的值集,enum比String或int等常量字面变量的类型更安全。
However, enum values are required to be valid identifiers, and we’re encouraged to use SCREAMING_SNAKE_CASE by convention.
然而,enum值需要是有效的标识符,我们被鼓励按惯例使用SCREAMING_SNAKE_CASE。
Given those limitations, the enum value alone is not suitable for human-readable strings or non-string values.
鉴于这些限制,单独的enum值不适合用于人类可读的字符串或非字符串值。
In this tutorial, we’ll use the enum features as a Java class to attach the values we want.
在本教程中,我们将使用enum功能作为一个Java类来附加我们想要的值。
2. Using Java Enum as a Class
2.将JavaEnum作为一个类
We often create an enum as a simple list of values. For example, here are the first two rows of the periodic table as a simple enum:
我们经常将enum创建为一个简单的值列表。例如,这里是周期表的前两行,作为一个简单的enum。
public enum Element {
H, HE, LI, BE, B, C, N, O, F, NE
}
Using the syntax above, we’ve created ten static, final instances of the enum named Element. While this is very efficient, we have only captured the element symbols. And while the uppercase form is appropriate for Java constants, it’s not how we normally write the symbols.
使用上面的语法,我们已经创建了10个静态的、名为enum的Element的最终实例。虽然这非常有效,但我们只捕获了元素的符号。虽然大写字母的形式适合于Java常量,但这不是我们通常写符号的方式。
Furthermore, we’re also missing other properties of the periodic table elements, like the name and atomic weight.
此外,我们还缺少周期表元素的其他属性,如名称和原子量。
Although the enum type has special behavior in Java, we can add constructors, fields and methods as we do with other classes. Because of this, we can enhance our enum to include the values we need.
尽管enum类型在Java中具有特殊的行为,但我们可以像对待其他类那样添加构造函数、字段和方法。正因为如此,我们可以增强我们的enum,以包括我们需要的值。
3. Adding a Constructor and a Final Field
3.添加一个构造函数和一个最终字段
Let’s start by adding the element names.
让我们从添加元素名称开始。
We’ll set the names into a final variable using a constructor:
我们将使用构造函数将这些名字设置到一个最终变量中。
public enum Element {
H("Hydrogen"),
HE("Helium"),
// ...
NE("Neon");
public final String label;
private Element(String label) {
this.label = label;
}
}
First of all, we notice the special syntax in the declaration list. This is how a constructor is invoked for enum types. Although it’s illegal to use the new operator for an enum, we can pass constructor arguments in the declaration list.
首先,我们注意到声明列表中的特殊语法。这就是为enum类型调用构造函数的方式。尽管对enum使用new操作符是非法的,但我们可以在声明列表中传递构造函数参数。
We then declare an instance variable label. There are a few things to note about that.
然后我们声明一个实例变量label。这其中有几件事需要注意。
First, we chose the label identifier instead of the name. Although the member field name is available to use, let’s choose label to avoid confusion with the predefined Enum.name() method.
首先,我们选择了label标识符而不是name。尽管成员字段name可以使用,我们还是选择label,以避免与预定义的Enum.name()方法混淆。
Second, our label field is final. While fields of an enum do not have to be final, in most cases we don’t want our labels to change. In the spirit of enum values being constant, this makes sense.
第二,我们的label字段是final。虽然enum的字段不一定是final,但在大多数情况下,我们不希望我们的标签发生变化。本着enum值是恒定的精神,这是有道理的。
Finally, the label field is public, so we can access the label directly:
最后,label字段是公开的,所以我们可以直接访问标签。
System.out.println(BE.label);
On the other hand, the field can be private, accessed with a getLabel() method. For the purpose of brevity, this article will continue to use the public field style.
另一方面,该字段可以是private,用getLabel()方法访问。为了简洁起见,本文将继续使用公共字段的样式。
4. Locating Java Enum Values
4.定位Java的Enum值
Java provides a valueOf(String) method for all enum types.
Java为所有enum类型提供了一个valueOf(String)方法。
Thus, we can always get an enum value based on the declared name:
因此,我们总是可以根据声明的名字得到一个enum值。
assertSame(Element.LI, Element.valueOf("LI"));
However, we may want to look up an enum value by our label field as well.
然而,我们可能也想通过我们的标签字段来查询一个enum值。
To do that, we can add a static method:
要做到这一点,我们可以添加一个静态方法。
public static Element valueOfLabel(String label) {
for (Element e : values()) {
if (e.label.equals(label)) {
return e;
}
}
return null;
}
The static valueOfLabel() method iterates the Element values until it finds a match. It returns null if no match is found. Conversely, an exception could be thrown instead of returning null.
静态的valueOfLabel()方法会遍历Element的值,直到找到一个匹配的。如果没有找到匹配,它将返回null。反之,可以抛出一个异常,而不是返回null。
Let’s see a quick example using our valueOfLabel() method:
让我们看一个使用我们的valueOfLabel()方法的快速例子。
assertSame(Element.LI, Element.valueOfLabel("Lithium"));
5. Caching the Lookup Values
5.缓存查询值
We can avoid iterating the enum values by using a Map to cache the labels.
我们可以通过使用一个Map来缓存标签,从而避免迭代enum值。
To do this, we define a static final Map and populate it when the class loads:
要做到这一点,我们定义一个static final Map,并在类加载时填充它。
public enum Element {
// ... enum values
private static final Map<String, Element> BY_LABEL = new HashMap<>();
static {
for (Element e: values()) {
BY_LABEL.put(e.label, e);
}
}
// ... fields, constructor, methods
public static Element valueOfLabel(String label) {
return BY_LABEL.get(label);
}
}
As a result of being cached, the enum values are iterated only once, and the valueOfLabel() method is simplified.
由于被缓存,enum值只被迭代一次,并且valueOfLabel()方法被简化。
As an alternative, we can lazily construct the cache when it is first accessed in the valueOfLabel() method. In that case, map access must be synchronized to prevent concurrency problems.
作为一个替代方案,我们可以在valueOfLabel()方法中第一次访问缓存时懒洋洋地构建缓存。在这种情况下,地图访问必须是同步的,以防止并发问题。
6. Attaching Multiple Values
6.附加多个数值
The Enum constructor can accept multiple values.
Enum构造函数可以接受多个值。。
To illustrate, let’s add the atomic number as an int and the atomic weight as a float:
为了说明这一点,让我们把原子序数作为int,把原子量作为float。
public enum Element {
H("Hydrogen", 1, 1.008f),
HE("Helium", 2, 4.0026f),
// ...
NE("Neon", 10, 20.180f);
private static final Map<String, Element> BY_LABEL = new HashMap<>();
private static final Map<Integer, Element> BY_ATOMIC_NUMBER = new HashMap<>();
private static final Map<Float, Element> BY_ATOMIC_WEIGHT = new HashMap<>();
static {
for (Element e : values()) {
BY_LABEL.put(e.label, e);
BY_ATOMIC_NUMBER.put(e.atomicNumber, e);
BY_ATOMIC_WEIGHT.put(e.atomicWeight, e);
}
}
public final String label;
public final int atomicNumber;
public final float atomicWeight;
private Element(String label, int atomicNumber, float atomicWeight) {
this.label = label;
this.atomicNumber = atomicNumber;
this.atomicWeight = atomicWeight;
}
public static Element valueOfLabel(String label) {
return BY_LABEL.get(label);
}
public static Element valueOfAtomicNumber(int number) {
return BY_ATOMIC_NUMBER.get(number);
}
public static Element valueOfAtomicWeight(float weight) {
return BY_ATOMIC_WEIGHT.get(weight);
}
}
Similarly, we can add any values we want to the enum, such as the proper case symbols, “He”, “Li” and “Be”, for example.
同样,我们可以向enum添加任何我们想要的值,例如,适当的大小写符号,”He”、”Li “和 “Be”,等等。
Moreover, we can add computed values to our enum by adding methods to perform operations.
此外,我们可以通过添加方法来执行操作,将计算值添加到我们的enum。
7. Controlling the Interface
7.控制界面
As a result of adding fields and methods to our enum, we’ve changed its public interface. Therefore our code, which uses the core Enum name() and valueOf() methods, will be unaware of our new fields.
由于向我们的enum添加了字段和方法,我们已经改变了它的公共接口。因此,我们的代码,即使用核心的Enum name()和valueOf()方法,将不知道我们的新字段。
The static valueOf() method is already defined for us by the Java language, so we can’t provide our own valueOf() implementation.
static valueOf()方法已经由Java语言为我们定义,所以我们不能提供我们自己的valueOf()实现。
Similarly, because the Enum.name() method is final, we can’t override it either.
同样地,由于Enum.name()方法是最终的,我们也不能覆盖它。
As a result, there’s no practical way to utilize our extra fields using the standard Enum API. Instead, let’s look at some different ways to expose our fields.
因此,使用标准的Enum API来利用我们的额外字段是不现实的。相反,让我们看看有哪些不同的方法可以暴露我们的字段。
7.1. Overriding toString()
7.1.重写toString()
Overriding toString() may be an alternative to overriding name():
重写toString()可以替代重写name()。
@Override
public String toString() {
return this.label;
}
By default, Enum.toString() returns the same value as Enum.name().
默认情况下,Enum.toString()返回与Enum.name()相同的值。
7.2. Implementing an Interface
7.2.实现一个接口
The enum type in Java can implement interfaces. While this approach is not as generic as the Enum API, interfaces do help us generalize.
Java中的enum类型可以实现接口。虽然这种方法不像Enum API那样通用,但接口确实可以帮助我们实现通用。
Let’s consider this interface:
让我们考虑一下这个界面。
public interface Labeled {
String label();
}
For consistency with the Enum.name() method, our label() method does not have a get prefix.
为了与Enum.name()方法保持一致,我们的label()方法没有一个get前缀。
And because the valueOfLabel() method is static, we do not include it in our interface.
因为valueOfLabel()方法是静态的,我们不把它包括在我们的接口中。
Finally, we can implement the interface in our enum:
最后,我们可以在我们的enum中实现该接口。
public enum Element implements Labeled {
// ...
@Override
public String label() {
return label;
}
// ...
}
One benefit of this approach is that the Labeled interface can be applied to any class, not just enum types. Instead of relying on the generic Enum API, we now have a more context-specific API.
这种方法的一个好处是,Labeled接口可以应用于任何类,而不仅仅是enum类型。我们现在不再依赖通用的Enum API,而是有了一个更加针对上下文的API。
8. Conclusion
8.结论
In this article, we’ve explored many features of the Java Enum implementation. By adding constructors, fields and methods, we see that the enum can do a lot more than literal constants.
在这篇文章中,我们已经探索了Java Enum实现的许多特性。通过添加构造函数、字段和方法,我们看到enum可以做的事情比字面常数多得多。
As always, the full source code for this article can be found over on GitHub.
一如既往,本文的完整源代码可以在GitHub上找到。