Extending enums

2019-07-22 04:13发布

I have a number of enums that each have the same fields and the same methods.

public enum AddressSubType {
    DOM("dom"), INTL("intl"), POSTAL("postal");

    private final String keyword;
    private AddressSubType(String keyword) {
        this.keyword = keyword;
    }
    public String getKeyword() {
        return keyword;
    }
    @Override
    public String toString() {
        return keyword;
    }
}

public enum EmailSubType {
    INTERNET("internet"), X400("x.400");

    private final String keyword;
    private EmailSubType(String keyword) {
        this.keyword = keyword;
    }
    public String getKeyword() {
        return keyword;
    }
    @Override
    public String toString() {
        return keyword;
    }
}

Is there a way for these enums to share the fields and methods (like a parent class)? I know that it's not possible to extend enums. Thanks.

标签: java
5条回答
地球回转人心会变
2楼-- · 2019-07-22 04:26

I will be the one to say it. This is an awful idea.

You should use enum types any time you need to represent a fixed set of constants. That includes natural enum types such as the planets in our solar system and data sets where you know all possible values at compile time—for example, the choices on a menu, command line flags, and so on. source

The enum does not care about anything else except the hard coded values inside. Typically when one decides to group things in an Object Oriented way, they make sure that all of the objects are related. By virtue of being an enum these files are no more related than two classes that are subtypes of Object. If you are looking to have shared functionality between all enums in your domain you will want to look at some static functions, or a utility class as it is often referred to (this has its own series of issues at the end of the day). Essentially the class will have a series of functions that encapsulate all the shared logic, the signature will generally look like so:

function foo(Enum enumeration)
查看更多
放我归山
3楼-- · 2019-07-22 04:31

There's not much you can do in this case, and even in a more complex example, the best place to put common code might be in a utility class that all the enums could use, or in a separate class that would be included in the enums via composition (each enum would have an instance of that class, perhaps called Keyword).

If the code for the toString method were complex and you didn't want to restate it in each enum, or move it to a contained object, Java 8 has a mechanism that you could use. It is overkill in this example. You could define an interface that your enums would all use. The state (keyword) must still live in your enums, since interfaces cannot have state, but starting with Java 8 you can provide default implementations of methods:

public interface Common {
    String getKeyword();

    String toString() default {
        return getKeyword();
    }

    String toDescriptiveString() default {
        char firstLetter = getKeyword().charAt(0);
        boolean vowel =
            firstLetter == 'a' || firstLetter == 'e' ||
            firstLetter == 'i' || firstLetter == 'o' ||
            firstLetter == 'u' || firstLetter == 'x';
        // the letter x is pronounced with an initial vowel sound (eks)
        return (vowel?"an ":"a ") + getKeyword() + " address";
    }
}

Your enums would implement this interface:

public enum AddressSubType implements Common {
    DOM("dom"), INTL("intl"), POSTAL("postal");

    private final String keyword;

    private AddressSubType(String keyword) {
        this.keyword = keyword;
    }

    @Override
    public String getKeyword() {
        return keyword;
    }

    @Override
    public String toString() {
        return Common.super.toString();
    }
}

public enum EmailSubType implements Common {
    INTERNET("internet"), X400("x.400");

    private final String keyword;

    private EmailSubType(String keyword) {
        this.keyword = keyword;
    }

    @Override
    public String getKeyword() {
        return keyword;
    }

    @Override
    public String toString() {
        return Common.super.toString();
    }
}

Notice the strange new syntax in the toString methods. The rule for default methods in interfaces is that method resolution always prefers class methods over interfaces. So even though we provide a default implementation of toString in Common, the one in the enum will take precedence, and the one in Object would if there wasn't one in the enum. So if you want to use a default method from an interface that supersedes one of the methods from Object (like toString, or hashCode, or equals), then you have to call it explicitly with this new interface.super.method() syntax.

We don't have to jump through any extra hoops for the toDescriptiveString method, though. That one is brand new in interface Common, and it isn't provided by our enums, so they get the default implementation provided by the interface. (If they wanted to override it with their own method, they could, just like any other inherited method.)

We can use the default methods like any other methods of an object, of course:

public class Test {
    public static void main(String[] args) {
        for (AddressSubType a : AddressSubType.values()) {
            System.out.println(a.toDescriptiveString());
        }
        for (EmailSubType e : EmailSubType.values()) {
            System.out.println(e.toDescriptiveString());
        }
    }
}

Which prints out:

a dom address
an intl address
a postal address
an internet address
an x.400 address

In this case, however, if it wasn't for the rather verbose toDescriptiveString method, the enum classes wouldn't be a bit shorter with interface Common than they would be without. Default methods in interfaces will really shine when it comes to adding new functionality to existing interfaces, something not possible without breaking all implementers of an interface in previous versions of Java.

All of this is based on the as-yet-incomplete Java SE 8 with Lambda. You can download a pre-release build, but be aware that it is a work in progress.

查看更多
孤傲高冷的网名
4楼-- · 2019-07-22 04:40

I would probably combine them into a single enum object where some are initialized with an "Postal" flag set to true and some have the "email" flag set to true since the two are really just different "types" of addresses.

You can then have it return iterators for either if you wish to access them separately or you can iterate over the whole thing.

You may also find some of the rest of your code becoming simplified, for instance just having a collection of "Address"es and checking at runtime to see if a given address is email or postal.

But it depends on how similar they really are.

查看更多
\"骚年 ilove
5楼-- · 2019-07-22 04:44

You can declare an interface that they both can implement. This would allow you to pass either enum type as an argument to a method that only cares about specific methods on that inerface. However, this will only allow you to "share" the method signatures, not the fields or the method implementations.

If your enums are as trivial as in the given example, you don't have any significant amount of code repetition, so this probably isn't a problem. If you find that your methods have more complex, repetitive code, you should consider delegating that responsibility to a separate class.

If you really want to model an inheritance pattern (e.g. EmailAddress "is a" Address), then you'll need to get away from enums. You could just use some static fields to simulate the enum pattern, but have each of them be an instance of a specific class.

查看更多
唯我独甜
6楼-- · 2019-07-22 04:52

You could create a Value class

public class Value {

  private final String keyword;

  private Value(String keyword) {
    this.keyword = keyword;
  }
  public String getKeyword() {
    return keyword;
  }
  @Override
  public String toString() {
    return keyword;
  }
}

Then you can create Classes with public static final values like this :

public class AddressSubType extend Value {

  public static final AddressSubType DOM = new AddressSubType("DOM");
  public static final AddressSubType INTL = new AddressSubType("intl");
  ...

  private AddressSubType(String keyword) {
     super(keyword);
  }
}
查看更多
登录 后发表回答