mirror of
https://github.com/Jueee/effective-Java.git
synced 2025-03-14 03:10:42 +08:00
使用接口模拟可扩展的枚举
This commit is contained in:
parent
242c7c8d58
commit
85d02fd51a
36
ch06枚举和注解/36.使用EnumSet替代位属性.md
Normal file
36
ch06枚举和注解/36.使用EnumSet替代位属性.md
Normal file
@ -0,0 +1,36 @@
|
||||
## 使用 EnumSet 替代位属性
|
||||
|
||||
如果枚举类型的元素主要用于集合中:
|
||||
|
||||
- [Item36Example01.java](EnumsAnnotations/src/main/java/com/jueee/item36/Item36Example01.java):传统上使用 int 枚举模式,将 2 的不同次幂赋值给每个常量。
|
||||
|
||||
这种表示方式允许你使用按位或(or)运算将几个常量合并到一个称为位域的集合中。
|
||||
|
||||
位域表示还允许你使用按位算术有效地执行集合运算,如并集和交集。
|
||||
|
||||
但是位域具有 `int` 枚举常量等的所有缺点:
|
||||
|
||||
- 当打印为数字时,解释位域比简单的 `int` 枚举常量更难理解。
|
||||
- 没有简单的方法遍历所有由位域表示的元素。
|
||||
- 必须预测在编写 API 时需要的最大位数,并相应地为位域(通常为 `int` 或 `long`)选择一种类型。
|
||||
|
||||
- [Item36Example02.java](EnumsAnnotations/src/main/java/com/jueee/item36/Item36Example02.java):使用枚举而不是 `int` 常量。
|
||||
|
||||
`java.util` 包提供了 `EnumSet` 类来有效地表示从单个枚举类型中提取的值集合。
|
||||
|
||||
这个类实现了 `Set` 接口,提供了所有其他 `Set` 实现的丰富性,类型安全性和互操作性。
|
||||
|
||||
请注意,`applyStyles` 方法采用`Set<Style>`而不是`EnumSet<Style>`参数。
|
||||
|
||||
尽管所有客户端都可能会将 `EnumSet` 传递给该方法,但接受接口类型而不是实现类型通常是很好的做法。
|
||||
|
||||
这允许一个不寻常的客户端通过其他 Set 实现的可能性。
|
||||
|
||||
总之,**仅仅因为枚举类型将被用于集合中,就没有理由用位域来表示它。**
|
||||
|
||||
`EnumSet` 类结合了位域的简洁性和性能以及枚举类型的所有优点。
|
||||
|
||||
截至 Java 9 ,`EnumSet` 的一个真正缺点是不能创建一个不可变的 `EnumSet`,但是在之后的发行版本中这可能会得到纠正。
|
||||
|
||||
同时,你可以用 `Collections.unmodifiableSet` 封装一个 `EnumSet`,但是简洁性和性能会受到影响。
|
||||
|
47
ch06枚举和注解/37.使用EnumMap替代序数索引.md
Normal file
47
ch06枚举和注解/37.使用EnumMap替代序数索引.md
Normal file
@ -0,0 +1,47 @@
|
||||
## 使用 EnumMap 替代序数索引
|
||||
|
||||
|
||||
|
||||
- [Item37Example01.java](EnumsAnnotations/src/main/java/com/jueee/item37/Item37Example01.java):使用 `ordinal` 方法来索引到数组或列表的代码。
|
||||
|
||||
这种方法是有效的,但充满了问题。
|
||||
|
||||
因为数组不兼容泛型,程序需要一个未经检查的转换,并且不会干净地编译。
|
||||
|
||||
由于该数组不知道索引代表什么,因此必须手动标记索引输出。
|
||||
|
||||
但是这种技术最严重的问题是,当你访问一个由枚举序数索引的数组时,你有责任使用正确的 `int` 值; `int` 不提供枚举的类型安全性。
|
||||
|
||||
如果你使用了错误的值,程序会默默地做错误的事情,如果你幸运的话,抛出一个 `ArrayIndexOutOfBoundsException` 异常。
|
||||
|
||||
- [Item37Example02.java](EnumsAnnotations/src/main/java/com/jueee/item37/Item37Example02.java):使用 `java.util.EnumMap`。
|
||||
|
||||
这段程序更简短,更清晰,更安全,运行速度与原始版本相当。
|
||||
|
||||
没有不安全的转换; 无需手动标记输出,因为 `map` 键是知道如何将自己转换为可打印字符串的枚举; 并且不可能在计算数组索引时出错。
|
||||
|
||||
`EnumMap` 与序数索引数组的速度相当,其原因是 `EnumMap` 内部使用了这样一个数组,但它对程序员的隐藏了这个实现细节,将 Map 的丰富性和类型安全性与数组的速度相结合。
|
||||
|
||||
请注意,`EnumMap` 构造方法接受键类`Class`型的 Class 对象:这是一个有限定的类型令牌(bounded type token),它提供运行时的泛型类型信息。
|
||||
|
||||
数组索引(两次)的数组,用序数来表示从两个枚举值的映射:
|
||||
|
||||
- [Item37Example03.java](EnumsAnnotations/src/main/java/com/jueee/item37/Item37Example03.java):使用一个数组来映射两个阶段到一个阶段转换。
|
||||
|
||||
编译器无法知道序数和数组索引之间的关系。
|
||||
|
||||
如果在转换表中出错或者在修改 `Phase` 或 `Phase.Transition` 枚举类型时忘记更新它,则程序在运行时将失败。
|
||||
|
||||
- [Item37Example04.java](EnumsAnnotations/src/main/java/com/jueee/item37/Item37Example04.java):用 `EnumMap` 做得更好。
|
||||
|
||||
因为每个阶段转换都由一对阶段枚举来索引,所以最好将关系表示为从一个枚举(from 阶段)到第二个枚举(to 阶段)到结果(阶段转换)的 `map`。
|
||||
|
||||
初始化阶段转换的 `map` 的代码有点复杂。`map` 的类型是 `Map<Phase, Map<Phase, Transition>>`,意思是「从(源)阶段映射到从(目标)阶段到阶段转换映射。」这个 `map` 的 `map` 使用两个收集器的级联序列进行初始化。
|
||||
|
||||
第一个收集器按源阶段对转换进行分组,第二个收集器使用从目标阶段到转换的映射创建一个 `EnumMap`。 第二个收集器 `((x, y) -> y))` 中的合并方法未使用;仅仅因为我们需要指定一个 `map` 工厂才能获得一个 `EnumMap`,并且 `Collectors` 提供伸缩式工厂,这是必需的。
|
||||
|
||||
**总之,使用序数来索引数组很不合适:改用 EnumMap。**
|
||||
|
||||
如果你所代表的关系是多维的,请使用 `EnumMap <...,EnumMap <... >>`。
|
||||
|
||||
应用程序员应该很少使用 `Enum.ordinal`,如果使用了,也是一般原则的特例。
|
29
ch06枚举和注解/38.使用接口模拟可扩展的枚举.md
Normal file
29
ch06枚举和注解/38.使用接口模拟可扩展的枚举.md
Normal file
@ -0,0 +1,29 @@
|
||||
## 使用接口模拟可扩展的枚举
|
||||
|
||||
利用枚举类型可以通过为 `opcode` 类型定义一个接口,并实现任意接口。
|
||||
|
||||
- [Operation.java](EnumsAnnotations/src/main/java/com/jueee/item38/Operation.java):可扩展版本的 `Operation` 类型。
|
||||
|
||||
- [BasicOperation.java](EnumsAnnotations/src/main/java/com/jueee/item38/BasicOperation.java):虽然枚举类型(`BasicOperation`)不可扩展,但接口类型(`Operation`)是可以扩展的,并且它是用于表示 API 中的操作的接口类型。
|
||||
|
||||
- [ExtendedOperation.java](EnumsAnnotations/src/main/java/com/jueee/item38/ExtendedOperation.java):定义前面所示的操作类型的扩展,包括指数运算和余数运算。
|
||||
|
||||
定义另一个实现此接口的枚举类型,并使用此新类型的实例来代替基本类型。
|
||||
|
||||
- [Item38Example01.java](EnumsAnnotations/src/main/java/com/jueee/item38/Item38Example01.java):传入整个扩展枚举类型,并使用其元素。
|
||||
|
||||
扩展的操作类型的类字面文字(`ExtendedOperation.class`)从 `main` 方法里传递给了 `test` 方法,用来描述扩展操作的集合。
|
||||
|
||||
这个类的字面文字用作限定的类型令牌。
|
||||
|
||||
`opEnumType` 参数中复杂的声明`(<T extends Enum<T> & Operation> Class<T>`)确保了 `Class` 对象既是枚举又是 `Operation` 的子类,这正是遍历元素和执行每个元素相关联的操作时所需要的。
|
||||
|
||||
- [Item38Example02.java](EnumsAnnotations/src/main/java/com/jueee/item38/Item38Example02.java):传递一个 `Collection<? extends Operation>`,这是一个限定通配符类型,而不是传递了一个 `class` 对象。
|
||||
|
||||
使用接口来模拟可扩展枚举的一个小缺点是,实现不能从一个枚举类型继承到另一个枚举类型。
|
||||
|
||||
如果实现代码不依赖于任何状态,则可以使用默认实现将其放置在接口中。
|
||||
|
||||
该条目中描述的模式在 `Java` 类库中有所使用。例如,`java.nio.file.LinkOption` 枚举类型实现了 `CopyOption` 和 `OpenOption` 接口。
|
||||
|
||||
**总之,虽然不能编写可扩展的枚举类型,但是你可以编写一个接口来配合实现接口的基本的枚举类型,来对它进行模拟。** 这允许客户端编写自己的枚举(或其它类型)来实现接口。如果 `API` 是根据接口编写的,那么在任何使用基本枚举类型实例的地方,都可以使用这些枚举类型实例。
|
@ -0,0 +1,62 @@
|
||||
package com.jueee.item37;
|
||||
|
||||
import java.awt.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public class Item37Example01 {
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public enum LifeCycle {
|
||||
ANNUAL("一年生"), PERENNIAL("多年生"), BIENNIAL("双年生");
|
||||
|
||||
private String action;
|
||||
|
||||
LifeCycle(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
public class Plant {
|
||||
|
||||
final String name;
|
||||
final LifeCycle lifeCycle;
|
||||
|
||||
Plant(String name, LifeCycle lifeCycle) {
|
||||
this.name = name;
|
||||
this.lifeCycle = lifeCycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// Using ordinal() to index into an array - DON'T DO THIS!
|
||||
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[LifeCycle.values().length];
|
||||
|
||||
for (int i = 0; i < plantsByLifeCycle.length; i++) {
|
||||
plantsByLifeCycle[i] = new HashSet<>();
|
||||
}
|
||||
|
||||
// Print the results
|
||||
for (int i = 0; i < plantsByLifeCycle.length; i++) {
|
||||
System.out.printf("%s: %s%n", LifeCycle.values()[i], plantsByLifeCycle[i]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package com.jueee.item37;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public class Item37Example02 {
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public enum LifeCycle {
|
||||
ANNUAL("一年生"), PERENNIAL("多年生"), BIENNIAL("双年生");
|
||||
|
||||
private String action;
|
||||
|
||||
LifeCycle(String action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getAction() {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
public class Plant {
|
||||
|
||||
final String name;
|
||||
final LifeCycle lifeCycle;
|
||||
|
||||
Plant(String name, LifeCycle lifeCycle) {
|
||||
this.name = name;
|
||||
this.lifeCycle = lifeCycle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Using an EnumMap to associate data with an enum
|
||||
Map<LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(LifeCycle.class);
|
||||
|
||||
for (LifeCycle lc : LifeCycle.values()) {
|
||||
plantsByLifeCycle.put(lc, new HashSet<>());
|
||||
}
|
||||
|
||||
System.out.println(plantsByLifeCycle);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.jueee.item37;
|
||||
|
||||
import java.awt.List;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public class Item37Example03 {
|
||||
|
||||
/**
|
||||
* Using ordinal() to index array of arrays - DON'T DO THIS!
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public enum Phase {
|
||||
SOLID, LIQUID, GAS;
|
||||
|
||||
public enum Transition {
|
||||
MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;
|
||||
// Rows indexed by from-ordinal, cols by to-ordinal
|
||||
private static final Transition[][] TRANSITIONS = {
|
||||
{ null, MELT, SUBLIME },
|
||||
{ FREEZE, null, BOIL },
|
||||
{ DEPOSIT, CONDENSE, null }
|
||||
};
|
||||
|
||||
// Returns the phase transition from one phase to another
|
||||
public static Transition from(Phase from, Phase to) {
|
||||
return TRANSITIONS[from.ordinal()][to.ordinal()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package com.jueee.item37;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public class Item37Example04 {
|
||||
|
||||
/**
|
||||
* Using a nested EnumMap to associate data with enum pairs
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public enum Phase {
|
||||
SOLID, LIQUID, GAS;
|
||||
|
||||
public enum Transition {
|
||||
MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID), BOIL(LIQUID, GAS), CONDENSE(GAS, LIQUID), SUBLIME(SOLID, GAS),
|
||||
DEPOSIT(GAS, SOLID);
|
||||
|
||||
private final Phase from;
|
||||
private final Phase to;
|
||||
|
||||
Transition(Phase from, Phase to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
// Initialize the phase transition map
|
||||
private static final Map<Phase, Map<Phase, Transition>> m = Stream.of(values())
|
||||
.collect(Collectors.groupingBy(t -> t.from, () -> new EnumMap<>(Phase.class),
|
||||
Collectors.toMap(t -> t.to, t -> t, (x, y) -> y, () -> new EnumMap<>(Phase.class))));
|
||||
|
||||
public static Transition from(Phase from, Phase to) {
|
||||
return m.get(from).get(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.jueee.item38;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public enum BasicOperation implements Operation {
|
||||
PLUS("+") {
|
||||
@Override
|
||||
public double apply(double x, double y) {
|
||||
return x + y;
|
||||
}
|
||||
},
|
||||
MINUS("-") {
|
||||
@Override
|
||||
public double apply(double x, double y) {
|
||||
return x - y;
|
||||
}
|
||||
},
|
||||
TIMES("*") {
|
||||
@Override
|
||||
public double apply(double x, double y) {
|
||||
return x * y;
|
||||
}
|
||||
},
|
||||
DIVIDE("/") {
|
||||
@Override
|
||||
public double apply(double x, double y) {
|
||||
return x / y;
|
||||
}
|
||||
};
|
||||
|
||||
private final String symbol;
|
||||
|
||||
BasicOperation(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return symbol;
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.jueee.item38;
|
||||
|
||||
//Emulated extension enum
|
||||
public enum ExtendedOperation implements Operation {
|
||||
EXP("^") {
|
||||
public double apply(double x, double y) {
|
||||
return Math.pow(x, y);
|
||||
}
|
||||
},
|
||||
REMAINDER("%") {
|
||||
public double apply(double x, double y) {
|
||||
return x % y;
|
||||
}
|
||||
};
|
||||
|
||||
private final String symbol;
|
||||
|
||||
ExtendedOperation(String symbol) {
|
||||
this.symbol = symbol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return symbol;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.jueee.item38;
|
||||
|
||||
public class Item38Example01 {
|
||||
|
||||
private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
|
||||
for (Operation op : opEnumType.getEnumConstants()) {
|
||||
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String[] testArgs = new String[] {"3.6", "4.9"};
|
||||
|
||||
double x = Double.parseDouble(testArgs[0]);
|
||||
double y = Double.parseDouble(testArgs[1]);
|
||||
test(ExtendedOperation.class, x, y);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.jueee.item38;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
public class Item38Example02 {
|
||||
|
||||
private static void test(Collection<? extends Operation> opSet, double x, double y) {
|
||||
for (Operation op : opSet) {
|
||||
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String[] testArgs = new String[] {"3.6", "4.9"};
|
||||
|
||||
double x = Double.parseDouble(testArgs[0]);
|
||||
double y = Double.parseDouble(testArgs[1]);
|
||||
test(Arrays.asList(ExtendedOperation.values()), x, y);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.jueee.item38;
|
||||
|
||||
public interface Operation {
|
||||
|
||||
double apply(double x, double y);
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user