使用枚举类型替代整型常量

This commit is contained in:
尉勇强2 2020-04-01 20:16:17 +08:00
parent 1f4172cbe7
commit 3a137f7013
13 changed files with 400 additions and 0 deletions

View File

@ -38,3 +38,14 @@
#### ch05.[泛型](ch05泛型)
26. [不要使用原始类型](ch05泛型/26.不要使用原始类型.md)
27. [消除非检查警告](ch05泛型/27.消除非检查警告.md)
28. [列表优于数组](ch05泛型/28.列表优于数组.md)
29. [优先考虑泛型](ch05泛型/29.优先考虑泛型.md)
30. [优先使用泛型方法](ch05泛型/30.优先使用泛型方法.md)
31. [使用限定通配符来增加API的灵活性](ch05泛型/31.使用限定通配符来增加API的灵活性.md)
32. [合理地结合泛型和可变参数](ch05泛型/32.合理地结合泛型和可变参数.md)
33. [优先考虑类型安全的异构容器](ch05泛型/33.优先考虑类型安全的异构容器.md)
#### ch06.[枚举和注解](ch06枚举和注解)

View File

@ -0,0 +1,58 @@
## 使用枚举类型替代整型常量
枚举是其合法值由一组固定的常量组成的一种类型。
例如一年中的季节,太阳系中的行星或一副扑克牌中的花色。
在将枚举类型添加到 Java 之前,表示枚举类型的常见模式是声明一组名为 `int` 的常量,每个类型的成员都有一个常量。如 [Item34Example01.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example01.java)。
这种被称为 `int` 枚举模式的技术有许多缺点:
- 它没有提供类型安全的方式,也没有提供任何表达力。
如果你将一个 `Apple` 传递给一个需要 `Orange` 的方法,那么编译器不会出现警告,还会用 == 运算符比较 `Apple``Orange`
- 使用 `int` 枚举的程序很脆弱。
因为 `int` 枚举是编译时常量,所以它们的 `int` 值被编译到使用它们的客户端中。 如果与 `int` 枚举关联的值发生更改,则必须重新编译其客户端。 如果没有,客户仍然会运行,但他们的行为将是不正确的。
- 没有简单的方法将 `int` 枚举常量转换为可打印的字符串。
如果你打印这样一个常量或者从调试器中显示出来,你看到的只是一个数字,这不是很有用。 没有可靠的方法来迭代组中的所有 `int` 枚举常量,甚至无法获得 `int` 枚举组的大小。
幸运的是Java 提供了一种避免 `int``String` 枚举模式的所有缺点的替代方法,并提供了许多额外的好处。 它是枚举类型。如:[Item34Example02.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example02.java)
```java
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
```
Java 的枚举类型是完整的类,比其他语言中的其他语言更强大,其枚举本质本上是 `int` 值。
Java 枚举类型背后的基本思想很简单:它们是通过公共静态 `final` 属性为每个枚举常量导出一个实例的类。
具有相同名称常量的枚举类型可以和平共存,因为每种类型都有其自己的名称空间。 可以在枚举类型中添加或重新排序常量,而无需重新编译其客户端,因为导出常量的属性在枚举类型与其客户端之间提供了一层隔离:常量值不会编译到客户端,因为它们位于 `int` 枚举模式中。 最后,可以通过调用其 `toString` 方法将枚举转换为可打印的字符串。
- [Item34Example03.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example03.java):对于丰富的枚举类型的一个很好的例子,考虑我们太阳系的八颗行星。 每个行星都有质量和半径,从这两个属性可以计算出它的表面重力。 从而在给定物体的质量下,计算出一个物体在行星表面上的重量。
- [Item34Example04.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example04.java):此代码有效,但不是很漂亮。 如果没有 `throw` 语句,就不能编译。更糟的是,代码很脆弱。 如果添加新的枚举常量,但忘记向 `switch` 语句添加相应的条件,枚举仍然会编译,但在尝试应用新操作时,它将在运行时失败。
- [Item34Example05.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example05.java):在枚举类型中声明一个抽象的 apply 方法,并用常量特定的类主体中的每个常量的具体方法重写它。 这种方法被称为特定于常量constant-specific的方法实现。
- [Item34Example06.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example06.java):特定于常量的方法实现与特定于常量的数据结合使用。
- [Item34Example07.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example07.java):枚举类型具有自动生成的 `valueOf(String)` 方法,该方法将常量名称转换为常量本身。 如果在枚举类型中重写 `toString` 方法,请考虑编写 `fromString` 方法将自定义字符串表示法转换回相应的枚举类型。
特定于常量的方法实现的一个缺点是它们使得难以在枚举常量之间共享代码。
例如,考虑一个代表工资包中的工作天数的枚举。 该枚举有一个方法,根据工人的基本工资(每小时)和当天工作的分钟数计算当天工人的工资。 在五个工作日内,任何超过正常工作时间的工作都会产生加班费; 在两个周末的日子里,所有工作都会产生加班费。
- [Item34Example08.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example08.java):使用 `switch` 语句,通过将多个 `case` 标签应用于两个代码片段中的每一个。
这段代码无可否认是简洁的,但从维护的角度来看是危险的。 假设你给枚举添加了一个元素,可能是一个特殊的值来表示一个假期,但忘记在 `switch` 语句中添加一个相应的 `case` 条件。 该程序仍然会编译,但付费方法会默默地为工作日支付相同数量的休假日,与普通工作日相同。
- [Item34Example09.java](EnumsAnnotations/src/main/java/com/jueee/item34/Item34Example09.java):使用特定于常量的方法实现安全地执行工资计算,为每个常量重复加班工资计算,或将计算移至两个辅助方法,一个用于工作日,另一个用于周末,并调用适当的辅助方法来自每个常量。 这两种方法都会产生相当数量的样板代码,大大降低了可读性并增加了出错机会。
将加班费计算移入私有嵌套枚举中,并将此策略枚举的实例传递给 `PayrollDay` 枚举的构造方法。 然后,`PayrollDay` 枚举将加班工资计算委托给策略枚举,从而无需在 `PayrollDay` 中实现 `switch` 语句或特定于常量的方法实现。 虽然这种模式不如 `switch` 语句简洁,但它更安全,更灵活。
一般而言,枚举通常在性能上与 `int` 常数相当。 枚举的一个小小的性能缺点是加载和初始化枚举类型存在空间和时间成本,但在实践中不太可能引人注意。
那么你应该什么时候使用枚举呢? 任何时候使用枚举都需要一组常量,这些常量的成员在编译时已知。 当然,这包括“天然枚举类型”,如行星,星期几和棋子。 但是它也包含了其它你已经知道编译时所有可能值的集合,例如菜单上的选项,操作代码和命令行标志。 **一个枚举类型中的常量集不需要一直保持不变**。 枚举功能是专门设计用于允许二进制兼容的枚举类型的演变。
总之,枚举类型优于 `int` 常量的优点是令人信服的。 枚举更具可读性,更安全,更强大。 许多枚举不需要显式构造方法或成员,但其他人则可以通过将数据与每个常量关联并提供行为受此数据影响的方法而受益。 使用单一方法关联多个行为可以减少枚举。 在这种相对罕见的情况下,更喜欢使用常量特定的方法来枚举自己的值。 如果一些(但不是全部)枚举常量共享共同行为,请考虑策略枚举模式。

View File

@ -0,0 +1,32 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
### VS Code ###
.vscode/
/chromedriver.exe

View File

@ -0,0 +1,25 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.jueee</groupId>
<artifactId>EnumsAnnotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>EnumsAnnotations</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,13 @@
package com.jueee.item34;
public class Item34Example01 {
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;
}

View File

@ -0,0 +1,9 @@
package com.jueee.item34;
public class Item34Example02 {
public enum Apple { FUJI, PIPPIN, GRANNY_SMITH }
public enum Orange { NAVEL, TEMPLE, BLOOD }
}

View File

@ -0,0 +1,47 @@
package com.jueee.item34;
public class Item34Example03 {
// 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
}
}
public static void main(String[] args) {
// 将一个物体在地球上的重量任何单位打印一个漂亮的表格显示该物体在所有八个行星上的重量以相同单位
double earthWeight = 18.3;
double mass = earthWeight / Planet.EARTH.surfaceGravity();
for (Planet p : Planet.values()) {
System.out.printf("Weight on %s is %f%n", p, p.surfaceWeight(mass));
}
}
}

View File

@ -0,0 +1,29 @@
package com.jueee.item34;
public class Item34Example04 {
// 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);
}
}
public static void main(String[] args) {
double x = Double.parseDouble("13");
double y = Double.parseDouble("2");
for (Operation op : Operation.values()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
}

View File

@ -0,0 +1,22 @@
package com.jueee.item34;
public class Item34Example05 {
// 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);
}
public static void main(String[] args) {
double x = Double.parseDouble("13");
double y = Double.parseDouble("2");
for (Operation op : Operation.values()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
}

View File

@ -0,0 +1,51 @@
package com.jueee.item34;
public class Item34Example06 {
// 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);
}
public static void main(String[] args) {
double x = Double.parseDouble("13");
double y = Double.parseDouble("2");
for (Operation op : Operation.values()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
}

View File

@ -0,0 +1,28 @@
package com.jueee.item34;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.jueee.item34.Item34Example06.Operation;
public class Item34Example07 {
// Implementing a fromString method on an enum type
private static final Map<String, Operation> stringToEnum =
Stream.of(Operation.values()).collect(Collectors.toMap(Object::toString, e -> e));
// Returns Operation for string, if any
public static Optional<Operation> fromString(String symbol) {
return Optional.ofNullable(stringToEnum.get(symbol));
}
public static void main(String[] args) {
System.out.println(fromString("+")); // Optional[+]
System.out.println(fromString("+").get()); // +
System.out.println(fromString("a")); // Optional.empty
System.out.println(fromString("a").get()); // Exception in thread "main" java.util.NoSuchElementException: No value present
}
}

View File

@ -0,0 +1,28 @@
package com.jueee.item34;
public class Item34Example08 {
// 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;
}
}
}

View File

@ -0,0 +1,47 @@
package com.jueee.item34;
public class Item34Example09 {
// 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;
}
// Default
PayrollDay() {
this(PayType.WEEKDAY);
}
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);
}
}
}
}