【发布时间】:2011-02-12 04:02:12
【问题描述】:
我刚刚发现 Java 允许枚举实现接口。什么是一个好的用例?
【问题讨论】:
我刚刚发现 Java 允许枚举实现接口。什么是一个好的用例?
【问题讨论】:
这是一个示例(在 Effective Java 2nd Edition 中可以找到类似/更好的示例):
public interface Operator {
int apply (int a, int b);
}
public enum SimpleOperators implements Operator {
PLUS {
int apply(int a, int b) { return a + b; }
},
MINUS {
int apply(int a, int b) { return a - b; }
};
}
public enum ComplexOperators implements Operator {
// can't think of an example right now :-/
}
现在获取简单 + 复杂运算符的列表:
List<Operator> operators = new ArrayList<Operator>();
operators.addAll(Arrays.asList(SimpleOperators.values()));
operators.addAll(Arrays.asList(ComplexOperators.values()));
所以在这里你使用一个接口来模拟可扩展的枚举(如果不使用接口是不可能的)。
【讨论】:
枚举不仅仅必须表示被动集(例如颜色)。它们可以表示具有功能的更复杂的对象,因此您可能希望向这些对象添加更多功能 - 例如您可能有Printable、Reportable 等接口以及支持这些接口的组件。
【讨论】:
这里几个人给出的Comparable 示例是错误的,因为Enum 已经实现了它。你甚至不能覆盖它。
一个更好的例子是拥有一个定义数据类型的接口。你可以有一个枚举来实现简单的类型,也可以有普通的类来实现复杂的类型:
interface DataType {
// methods here
}
enum SimpleDataType implements DataType {
INTEGER, STRING;
// implement methods
}
class IdentifierDataType implements DataType {
// implement interface and maybe add more specific methods
}
【讨论】:
有一个我经常使用的案例。我有一个带有静态方法的IdUtil 类来处理实现一个非常简单的Identifiable 接口的对象:
public interface Identifiable<K> {
K getId();
}
public abstract class IdUtil {
public static <T extends Enum<T> & Identifiable<S>, S> T get(Class<T> type, S id) {
for (T t : type.getEnumConstants()) {
if (Util.equals(t.getId(), id)) {
return t;
}
}
return null;
}
public static <T extends Enum<T> & Identifiable<S>, S extends Comparable<? super S>> List<T> getLower(T en) {
List<T> list = new ArrayList<>();
for (T t : en.getDeclaringClass().getEnumConstants()) {
if (t.getId().compareTo(en.getId()) < 0) {
list.add(t);
}
}
return list;
}
}
如果我创建一个Identifiable enum:
public enum MyEnum implements Identifiable<Integer> {
FIRST(1), SECOND(2);
private int id;
private MyEnum(int id) {
this.id = id;
}
public Integer getId() {
return id;
}
}
然后我可以通过它的id 得到它:
MyEnum e = IdUtil.get(MyEnum.class, 1);
【讨论】:
由于枚举可以实现接口,它们可以用于严格执行单例模式。尝试将标准类设为单例允许...
枚举作为单例有助于防止这些安全问题。这可能是让 Enums 充当类并实现接口的促成原因之一。只是猜测。
更多讨论请参见https://stackoverflow.com/questions/427902/java-enum-singleton 和Singleton class in java。
【讨论】:
for inheriting from your singleton and overriding your singleton's methods with something else。您可以使用final class 来防止这种情况发生
它是可扩展性所必需的——如果有人使用您开发的 API,您定义的枚举是静态的;它们不能被添加或修改。但是,如果你让它实现一个接口,使用 API 的人可以使用相同的接口开发他们自己的枚举。然后,您可以使用枚举管理器注册此枚举,该管理器将枚举与标准接口结合在一起。
编辑:@Helper 方法有一个完美的例子。考虑让其他库定义新的运算符,然后告诉管理器类“嘿,这个枚举存在——注册它”。否则,您只能在自己的代码中定义运算符 - 没有可扩展性。
【讨论】:
枚举只是伪装的类,所以在大多数情况下,你可以用一个类做的任何事情都可以用一个枚举来做。
我想不出一个枚举不应该实现接口的原因,同时我也想不出一个很好的理由。
我会说,一旦你开始向枚举添加接口或方法之类的东西,你真的应该考虑将它变成一个类。当然,我确信做非传统枚举的事情是有道理的,而且由于限制是人为的,我赞成让人们在那里做他们想做的事。
【讨论】:
上面提到策略的帖子没有足够强调使用枚举的策略模式的轻量级实现会给你带来什么:
public enum Strategy {
A {
@Override
void execute() {
System.out.print("Executing strategy A");
}
},
B {
@Override
void execute() {
System.out.print("Executing strategy B");
}
};
abstract void execute();
}
您可以将所有策略放在一个地方,而无需为每个策略单独编译单元。只需:
Strategy.valueOf("A").execute();
让 java 读起来几乎就像一种美味的松散类型语言!
【讨论】:
例如,如果您有一个 Logger 枚举。然后你应该在接口中有调试、信息、警告和错误等记录器方法。它使您的代码松散耦合。
【讨论】:
最常见的用法是将两个枚举的值合并到一个组中并以类似方式对待它们。例如,查看如何join Fruits and Vegatables。
【讨论】:
对我来说,将枚举与接口一起使用的最佳用例之一是谓词过滤器。这是弥补 apache 集合缺乏类型的一种非常优雅的方法(如果不能使用其他库)。
import java.util.ArrayList;
import java.util.Collection;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
public class Test {
public final static String DEFAULT_COMPONENT = "Default";
enum FilterTest implements Predicate {
Active(false) {
@Override
boolean eval(Test test) {
return test.active;
}
},
DefaultComponent(true) {
@Override
boolean eval(Test test) {
return DEFAULT_COMPONENT.equals(test.component);
}
}
;
private boolean defaultValue;
private FilterTest(boolean defautValue) {
this.defaultValue = defautValue;
}
abstract boolean eval(Test test);
public boolean evaluate(Object o) {
if (o instanceof Test) {
return eval((Test)o);
}
return defaultValue;
}
}
private boolean active = true;
private String component = DEFAULT_COMPONENT;
public static void main(String[] args) {
Collection<Test> tests = new ArrayList<Test>();
tests.add(new Test());
CollectionUtils.filter(tests, FilterTest.Active);
}
}
【讨论】:
另一种可能性:
public enum ConditionsToBeSatisfied implements Predicate<Number> {
IS_NOT_NULL(Objects::nonNull, "Item is null"),
IS_NOT_AN_INTEGER(item -> item instanceof Integer, "Item is not an integer"),
IS_POSITIVE(item -> item instanceof Integer && (Integer) item > 0, "Item is negative");
private final Predicate<Number> predicate;
private final String notSatisfiedLogMessage;
ConditionsToBeSatisfied(final Predicate<Number> predicate, final String notSatisfiedLogMessage) {
this.predicate = predicate;
this.notSatisfiedLogMessage = notSatisfiedLogMessage;
}
@Override
public boolean test(final Number item) {
final boolean isNotValid = predicate.negate().test(item);
if (isNotValid) {
log.warn("Invalid {}. Cause: {}", item, notSatisfiedLogMessage);
}
return predicate.test(item);
}
}
并使用:
Predicate<Number> p = IS_NOT_NULL.and(IS_NOT_AN_INTEGER).and(IS_POSITIVE);
【讨论】:
在 jar 文件中创建常量时,让用户扩展枚举值通常很有帮助。我们使用枚举作为 PropertyFile 键并卡住了,因为没有人可以添加任何新的!下面会更好。
给定:
public interface Color {
String fetchName();
}
和:
public class MarkTest {
public static void main(String[] args) {
MarkTest.showColor(Colors.BLUE);
MarkTest.showColor(MyColors.BROWN);
}
private static void showColor(Color c) {
System.out.println(c.fetchName());
}
}
罐子里可以有一个枚举:
public enum Colors implements Color {
BLUE, RED, GREEN;
@Override
public String fetchName() {
return this.name();
}
}
用户可以扩展它来添加自己的颜色:
public enum MyColors implements Color {
BROWN, GREEN, YELLOW;
@Override
public String fetchName() {
return this.name();
}
}
【讨论】:
枚举就像 Java 类,它们可以有构造函数、方法等。唯一不能用它们做的事情是 new EnumName()。这些实例是在您的枚举声明中预定义的。
【讨论】:
enum Foo extends SomeOtherClass 怎么样?所以和普通班不完全一样,事实上,完全不同。
这就是我为什么...
我已经用 Enum 的值填充了 JavaFX ComboBox。我有一个 Identifiable 接口(指定一种方法:identify),它允许我指定任何对象如何向我的应用程序标识自己以进行搜索。这个接口使我能够扫描任何类型对象的列表(对象可用于标识的任何字段)以进行标识匹配。
我想在我的 ComboBox 列表中找到一个身份值的匹配项。为了在包含 Enum 值的 ComboBox 上使用此功能,我必须能够在我的 Enum 中实现 Identifiable 接口(碰巧,在 Enum 的情况下实现它是微不足道的)。
【讨论】:
我在接口中使用了一个内部枚举来描述一种策略,以从那里保持实例控制(每个策略都是一个单例)。
public interface VectorizeStrategy {
/**
* Keep instance control from here.
*
* Concrete classes constructors should be package private.
*/
enum ConcreteStrategy implements VectorizeStrategy {
DEFAULT (new VectorizeImpl());
private final VectorizeStrategy INSTANCE;
ConcreteStrategy(VectorizeStrategy concreteStrategy) {
INSTANCE = concreteStrategy;
}
@Override
public VectorImageGridIntersections processImage(MarvinImage img) {
return INSTANCE.processImage(img);
}
}
/**
* Should perform edge Detection in order to have lines, that can be vectorized.
*
* @param img An Image suitable for edge detection.
*
* @return the VectorImageGridIntersections representing img's vectors
* intersections with the grids.
*/
VectorImageGridIntersections processImage(MarvinImage img);
}
枚举实现策略这一事实很方便允许枚举类充当其封闭实例的代理。它也实现了接口。
它是一种 strategyEnumProxy :P clent 代码如下所示:
VectorizeStrategy.ConcreteStrategy.DEFAULT.processImage(img);
如果它没有实现它本来的接口:
VectorizeStrategy.ConcreteStrategy.DEFAULT.getInstance().processImage(img);
【讨论】: