Java 枚举可以被子类化以添加新元素吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1414755/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can enums be subclassed to add new elements?
提问by Mike
I want to take an existing enum and add more elements to it as follows:
我想采用现有的枚举并向其添加更多元素,如下所示:
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/
Is this possible in Java?
这在Java中可能吗?
采纳答案by Jon Skeet
No, you can't do this in Java. Aside from anything else, d
would then presumably be an instance of A
(given the normal idea of "extends"), but users who only knew about A
wouldn't know about it - which defeats the point of an enum being a well-known set of values.
不,您不能在 Java 中执行此操作。除了其他任何东西,d
然后大概是一个实例A
(考虑到“扩展”的正常想法),但只知道的用户A
不会知道它 - 这使枚举成为众所周知的一组值。
If you could tell us more about how you want to usethis, we could potentially suggest alternative solutions.
如果您能告诉我们更多有关您想如何使用它的信息,我们可能会建议替代解决方案。
回答by JodaStephen
The recommended solution to this is the extensible enum pattern.
对此的推荐解决方案是可扩展枚举模式。
This involves creating an interface and using that where you currently use the enum. Then make the enum implement the interface. You can add more constants by making that new enum also extend the interface.
这涉及创建一个接口并在您当前使用枚举的地方使用它。然后让枚举实现接口。您可以通过使新枚举也扩展接口来添加更多常量。
回答by Tom Hawtin - tackline
Enums represent a complete enumeration of possible values. So the (unhelpful) answer is no.
枚举代表可能值的完整枚举。所以(无益的)答案是否定的。
As an example of a real problem take weekdays, weekend days and, the union, days of week. We could define all days within days-of-week but then we would not be able to represent properties special to either weekdays and weekend-days.
作为实际问题的一个例子,以工作日、周末以及联合、一周中的几天为例。我们可以定义一周中的所有天数,但是我们将无法表示工作日和周末的特殊属性。
What we could do, is have three enum types with a mapping between weekdays/weekend-days and days-of-week.
我们可以做的是拥有三种枚举类型,并在工作日/周末天和星期几之间进行映射。
public enum Weekday {
MON, TUE, WED, THU, FRI;
public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay {
SAT, SUN;
public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
Alternatively, we could have an open-ended interface for day-of-week:
或者,我们可以为星期几提供一个开放式界面:
interface Day {
...
}
public enum Weekday implements Day {
MON, TUE, WED, THU, FRI;
}
public enum WeekendDay implements Day {
SAT, SUN;
}
Or we could combine the two approaches:
或者我们可以结合这两种方法:
interface Day {
...
}
public enum Weekday implements Day {
MON, TUE, WED, THU, FRI;
public DayOfWeek toDayOfWeek() { ... }
}
public enum WeekendDay implements Day {
SAT, SUN;
public DayOfWeek toDayOfWeek() { ... }
}
public enum DayOfWeek {
MON, TUE, WED, THU, FRI, SAT, SUN;
public Day toDay() { ... }
}
回答by dsummersl
Having had this same problem myself I'd like to post my perspective. I think that there are a couple motivating factors for doing something like this:
我自己也遇到过同样的问题,我想发表一下我的观点。我认为做这样的事情有几个激励因素:
- You want to have some related enum codes, but in different classes. In my case I had a base class with several codes defined in an associated enum. At some later date (today!) I wanted to provide some new functionality to the base class, which also meant new codes for the enum.
- The derived class would support both the base classes' enum as well as its own. No duplicate enum values! So: how to have an enum for the subclass that includes the enum's of its parent along with its new values.
- 你想要一些相关的枚举代码,但在不同的类中。就我而言,我有一个基类,其中包含在关联枚举中定义的多个代码。稍后(今天!)我想为基类提供一些新功能,这也意味着枚举的新代码。
- 派生类将支持基类的枚举以及它自己的枚举。没有重复的枚举值!所以:如何为子类创建一个枚举,其中包括其父类的枚举及其新值。
Using an interface doesn't really cut it: you can accidentally get duplicate enum values. Not desirable.
使用接口并不能真正解决问题:您可能会意外获得重复的枚举值。不可取。
I ended up just combining the enums: this ensures that there cannot be any duplicate values, at the expense of being less tightly tied to its associated class. But, I figured the duplicate issue was my main concern...
我最终只是组合了枚举:这确保了不会有任何重复的值,代价是与其关联的类的联系不那么紧密。但是,我认为重复的问题是我最关心的问题......
回答by Waldemar Wosiński
enum A {a,b,c}
enum B extends A {d}
/*B is {a,b,c,d}*/
can be written as:
可以写成:
public enum All {
a (ClassGroup.A,ClassGroup.B),
b (ClassGroup.A,ClassGroup.B),
c (ClassGroup.A,ClassGroup.B),
d (ClassGroup.B)
...
- ClassGroup.B.getMembers()contains {a,b,c,d}
- ClassGroup.B.getMembers()包含 {a,b,c,d}
How it can be useful:Let say we want something like: We have events and we are using enums. Those enums can be grouped by similar processing. If we have operation with many elements, then some events starts operation, some are just step and other end the operation. To gather such operation and avoid long switch case we can group them as in example and use:
它如何有用:假设我们想要这样的东西:我们有事件并且我们正在使用枚举。这些枚举可以通过类似的处理进行分组。如果我们有很多元素的操作,那么一些事件开始操作,一些只是步骤,而另一些则结束操作。为了收集此类操作并避免长切换案例,我们可以将它们分组为示例并使用:
if(myEvent.is(State_StatusGroup.START)) makeNewOperationObject()..
if(myEnum.is(State_StatusGroup.STEP)) makeSomeSeriousChanges()..
if(myEnum.is(State_StatusGroup.FINISH)) closeTransactionOrSomething()..
Example:
例子:
public enum AtmOperationStatus {
STARTED_BY_SERVER (State_StatusGroup.START),
SUCCESS (State_StatusGroup.FINISH),
FAIL_TOKEN_TIMEOUT (State_StatusGroup.FAIL,
State_StatusGroup.FINISH),
FAIL_NOT_COMPLETE (State_StatusGroup.FAIL,
State_StatusGroup.STEP),
FAIL_UNKNOWN (State_StatusGroup.FAIL,
State_StatusGroup.FINISH),
(...)
private AtmOperationStatus(StatusGroupInterface ... pList){
for (StatusGroupInterface group : pList){
group.addMember(this);
}
}
public boolean is(StatusGroupInterface with){
for (AtmOperationStatus eT : with.getMembers()){
if( eT .equals(this)) return true;
}
return false;
}
// Each group must implement this interface
private interface StatusGroupInterface{
EnumSet<AtmOperationStatus> getMembers();
void addMember(AtmOperationStatus pE);
}
// DEFINING GROUPS
public enum State_StatusGroup implements StatusGroupInterface{
START, STEP, FAIL, FINISH;
private List<AtmOperationStatus> members = new LinkedList<AtmOperationStatus>();
@Override
public EnumSet<AtmOperationStatus> getMembers() {
return EnumSet.copyOf(members);
}
@Override
public void addMember(AtmOperationStatus pE) {
members.add(pE);
}
static { // forcing initiation of dependent enum
try {
Class.forName(AtmOperationStatus.class.getName());
} catch (ClassNotFoundException ex) {
throw new RuntimeException("Class AtmEventType not found", ex);
}
}
}
}
//Some use of upper code:
if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.FINISH)) {
//do something
}else if (p.getStatus().is(AtmOperationStatus.State_StatusGroup.START)) {
//do something
}
Add some more advanced:
添加一些更高级的:
public enum AtmEventType {
USER_DEPOSIT (Status_EventsGroup.WITH_STATUS,
Authorization_EventsGroup.USER_AUTHORIZED,
ChangedMoneyAccountState_EventsGroup.CHANGED,
OperationType_EventsGroup.DEPOSIT,
ApplyTo_EventsGroup.CHANNEL),
SERVICE_DEPOSIT (Status_EventsGroup.WITH_STATUS,
Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
ChangedMoneyAccountState_EventsGroup.CHANGED,
OperationType_EventsGroup.DEPOSIT,
ApplyTo_EventsGroup.CHANNEL),
DEVICE_MALFUNCTION (Status_EventsGroup.WITHOUT_STATUS,
Authorization_EventsGroup.TERMINAL_AUTHORIZATION,
ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED,
ApplyTo_EventsGroup.DEVICE),
CONFIGURATION_4_C_CHANGED(Status_EventsGroup.WITHOUT_STATUS,
ApplyTo_EventsGroup.TERMINAL,
ChangedMoneyAccountState_EventsGroup.DID_NOT_CHANGED),
(...)
At above if we have some fail (myEvent.is(State_StatusGroup.FAIL)) then iterating by previous events we can easily check if we must revert money transfer by:
在上面,如果我们有一些失败 (myEvent.is(State_StatusGroup.FAIL)) 然后通过之前的事件进行迭代,我们可以轻松地检查是否必须通过以下方式恢复汇款:
if(myEvent2.is(ChangedMoneyAccountState_EventsGroup.CHANGED)) rollBack()..
It can be useful for:
它可用于:
- including explicite meta-data about processing logic, less to remember
- implementing some of multi-inheritance
- we don't want to use class structures, ex. for sending short status messages
- 包括关于处理逻辑的显式元数据,较少记住
- 实现一些多继承
- 我们不想使用类结构,例如。用于发送短状态消息
回答by sulai
I tend to avoid enums, because they are not extensible. To stay with the example of the OP, if A is in a library and B in your own code, you can't extend A if it is an enum. This is how I sometimes replace enums:
我倾向于避免使用枚举,因为它们不可扩展。继续以 OP 为例,如果 A 在库中,而 B 在您自己的代码中,则不能扩展 A 如果它是枚举。这就是我有时替换枚举的方式:
// access like enum: A.a
public class A {
public static final A a = new A();
public static final A b = new A();
public static final A c = new A();
/*
* In case you need to identify your constant
* in different JVMs, you need an id. This is the case if
* your object is transfered between
* different JVM instances (eg. save/load, or network).
* Also, switch statements don't work with
* Objects, but work with int.
*/
public static int maxId=0;
public int id = maxId++;
public int getId() { return id; }
}
public class B extends A {
/*
* good: you can do like
* A x = getYourEnumFromSomeWhere();
* if(x instanceof B) ...;
* to identify which enum x
* is of.
*/
public static final A d = new A();
}
public class C extends A {
/* Good: e.getId() != d.getId()
* Bad: in different JVMs, C and B
* might be initialized in different order,
* resulting in different IDs.
* Workaround: use a fixed int, or hash code.
*/
public static final A e = new A();
public int getId() { return -32489132; };
}
There are some pits to avoid, see the comments in the code. Depending on your needs, this is a solid, extensible alternative to enums.
有一些坑要避免,看代码中的注释。根据您的需要,这是一个可靠的、可扩展的枚举替代方案。
回答by ChrisCantrell
Under the covers your ENUM is just a regular class generated by the compiler. That generated class extends java.lang.Enum
. The technical reason you can't extend the generated class is that the generated class is final
. The conceptual reasons for it being final are discussed in this topic. But I'll add the mechanics to the discussion.
在幕后,您的 ENUM 只是编译器生成的常规类。生成的类扩展了java.lang.Enum
. 您不能扩展生成的类的技术原因是生成的类是final
. 本主题讨论了其成为最终版本的概念性原因。但我会在讨论中添加机制。
Here is a test enum:
这是一个测试枚举:
public enum TEST {
ONE, TWO, THREE;
}
The resulting code from javap:
来自 javap 的结果代码:
public final class TEST extends java.lang.Enum<TEST> {
public static final TEST ONE;
public static final TEST TWO;
public static final TEST THREE;
static {};
public static TEST[] values();
public static TEST valueOf(java.lang.String);
}
Conceivably you could type this class on your own and drop the "final". But the compiler prevents you from extending "java.lang.Enum" directly. You could decide NOT to extend java.lang.Enum, but then your class and its derived classes would not be an instanceof java.lang.Enum ... which might not really matter to you any way!
可以想象,您可以自己键入这个类并删除“final”。但是编译器会阻止您直接扩展“java.lang.Enum”。您可以决定不扩展 java.lang.Enum,但是您的类及其派生类将不会是 java.lang.Enum 的实例……这对您来说可能并不重要!
回答by Laurent Caillette
This is how I enhance the enum inheritance pattern with runtime check in static initializer.
The BaseKind#checkEnumExtender
checks that "extending" enum declares all the values of the base enum in exactly the same way so #name()
and #ordinal()
remain fully compatible.
这就是我如何通过静态初始化程序中的运行时检查来增强枚举继承模式。在BaseKind#checkEnumExtender
该“延长”枚举声明以完全相同的方式基本枚举的所有值的检查,以便#name()
和#ordinal()
保持完全兼容。
There is still copy-paste involved for declaring values but the program fails fast if somebody added or modified a value in the base class without updating extending ones.
声明值仍然涉及复制粘贴,但是如果有人在基类中添加或修改值而不更新扩展值,程序会很快失败。
Common behavior for different enums extending each other:
不同枚举相互扩展的常见行为:
public interface Kind {
/**
* Let's say we want some additional member.
*/
String description() ;
/**
* Standard {@code Enum} method.
*/
String name() ;
/**
* Standard {@code Enum} method.
*/
int ordinal() ;
}
Base enum, with verifying method:
基本枚举,带有验证方法:
public enum BaseKind implements Kind {
FIRST( "First" ),
SECOND( "Second" ),
;
private final String description ;
public String description() {
return description ;
}
private BaseKind( final String description ) {
this.description = description ;
}
public static void checkEnumExtender(
final Kind[] baseValues,
final Kind[] extendingValues
) {
if( extendingValues.length < baseValues.length ) {
throw new IncorrectExtensionError( "Only " + extendingValues.length + " values against "
+ baseValues.length + " base values" ) ;
}
for( int i = 0 ; i < baseValues.length ; i ++ ) {
final Kind baseValue = baseValues[ i ] ;
final Kind extendingValue = extendingValues[ i ] ;
if( baseValue.ordinal() != extendingValue.ordinal() ) {
throw new IncorrectExtensionError( "Base ordinal " + baseValue.ordinal()
+ " doesn't match with " + extendingValue.ordinal() ) ;
}
if( ! baseValue.name().equals( extendingValue.name() ) ) {
throw new IncorrectExtensionError( "Base name[ " + i + "] " + baseValue.name()
+ " doesn't match with " + extendingValue.name() ) ;
}
if( ! baseValue.description().equals( extendingValue.description() ) ) {
throw new IncorrectExtensionError( "Description[ " + i + "] " + baseValue.description()
+ " doesn't match with " + extendingValue.description() ) ;
}
}
}
public static class IncorrectExtensionError extends Error {
public IncorrectExtensionError( final String s ) {
super( s ) ;
}
}
}
Extension sample:
扩展示例:
public enum ExtendingKind implements Kind {
FIRST( BaseKind.FIRST ),
SECOND( BaseKind.SECOND ),
THIRD( "Third" ),
;
private final String description ;
public String description() {
return description ;
}
ExtendingKind( final BaseKind baseKind ) {
this.description = baseKind.description() ;
}
ExtendingKind( final String description ) {
this.description = description ;
}
}
回答by Guillaume Husta
In case you missed it, there's a chapter in the excellent Joshua Bloch's book "Java Effective, 2nd edition".
如果你错过了,在 Joshua Bloch 的优秀著作“ Java Effective, 2nd edition”中有一章。
- Chapter 6 - Enums and Annotations
- Item 34 : Emulate extensible enums with interfaces
- 第 6 章 - 枚举和注释
- 第 34 项:使用接口模拟可扩展枚举
Extract here.
在这里提取。
Just the conclusion :
只是结论:
A minor disadvantage of the use of interfaces to emulate extensible enums is that implementations cannot be inherited from one enum type to another. In the case of our Operation example, the logic to store and retrieve the symbol associated with an operation is duplicated in BasicOperation and ExtendedOperation. In this case it doesn't matter because very little code is duplicated. If there were a larger amount of shared functionality, you could encapsulate it in a helper class or a static helper method to eliminate the code duplication.
In summary, while you cannot write an extensible enum type, you can emulate it by writing an interface to go with a basic enum type that implements the interface. This allows clients to write their own enums that implement the interface. These enums can then be used wherever the basic enum type can be used, assuming APIs are written in terms of the interface.
使用接口模拟可扩展枚举的一个小缺点是实现不能从一种枚举类型继承到另一种枚举类型。在我们的 Operation 示例中,存储和检索与操作关联的符号的逻辑在 BasicOperation 和 ExtendedOperation 中重复。在这种情况下,这无关紧要,因为重复的代码很少。如果有大量的共享功能,您可以将其封装在辅助类或静态辅助方法中,以消除代码重复。
总之,虽然您无法编写可扩展的枚举类型,但您可以通过编写一个接口来模拟它,以配合实现该接口的基本枚举类型。这允许客户端编写自己的枚举来实现接口。然后可以在可以使用基本枚举类型的任何地方使用这些枚举,假设 API 是根据接口编写的。
回答by Juan Pablo G
Here is a way how I found how to extend a enum into other enum, is a very straighfoward approach:
这是我如何找到如何将枚举扩展到其他枚举的方法,是一种非常直接的方法:
Suposse you have a enum with common constants:
假设您有一个带有通用常量的枚举:
public interface ICommonInterface {
String getName();
}
public enum CommonEnum implements ICommonInterface {
P_EDITABLE("editable"),
P_ACTIVE("active"),
P_ID("id");
private final String name;
EnumCriteriaComun(String name) {
name= name;
}
@Override
public String getName() {
return this.name;
}
}
then you can try to do a manual extends in this way:
那么您可以尝试以这种方式进行手动扩展:
public enum SubEnum implements ICommonInterface {
P_EDITABLE(CommonEnum.P_EDITABLE ),
P_ACTIVE(CommonEnum.P_ACTIVE),
P_ID(CommonEnum.P_ID),
P_NEW_CONSTANT("new_constant");
private final String name;
EnumCriteriaComun(CommonEnum commonEnum) {
name= commonEnum.name;
}
EnumCriteriaComun(String name) {
name= name;
}
@Override
public String getName() {
return this.name;
}
}
of course every time you need to extend a constant you have to modify your SubEnum files.
当然,每次您需要扩展一个常量时,您都必须修改您的 SubEnum 文件。