2

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.

Community
  • 1
  • 1
Michael
  • 34,873
  • 17
  • 75
  • 109
  • 2
    See http://stackoverflow.com/questions/11163968/how-to-add-common-methods-for-a-few-java-enums-abstract-class-ancestor – Miserable Variable Jun 26 '12 at 00:06
  • @MiserableVariable Some helpful info there, thanks. – Michael Jun 26 '12 at 00:57
  • @trashgod No I don't want to combine them all into a single enum. I want to keep them separate because certain methods in my application should only accept one kind of enum. – Michael Jun 26 '12 at 00:57

5 Answers5

1

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.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • With the interface solution, you still have to write the method implementation in each enum and that's what I want to avoid doing. – Michael Jun 26 '12 at 00:54
  • @Michael: Yes, just like I said in my first paragraph. And, as I said in my second paragraph, single-line method implementations shouldn't be that big a problem, but if they are you can follow the pattern I mentioned in the third paragraph. – StriplingWarrior Jun 26 '12 at 02:51
1

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.

Bill K
  • 62,186
  • 18
  • 105
  • 157
  • No I don't want to combine them all into a single enum. I want to keep them separate because certain methods in my application should only accept one kind of enum value. – Michael Jun 26 '12 at 00:53
  • That is why you have the method check to see if it is the correct type. Furthermore you will probably find that an if() statement inside the method will be a better solution allowing you to remove a second method that you assumed would handle the other type. I could be wrong but this is how it always works out for me. Finally the different methods may actually be on the enum itself which observes the flag. This way you say "enumVar.createLetter()" and it creates either an email or a postal letter without you ever knowing or careing which. – Bill K Jun 26 '12 at 21:22
1

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);
  }
}
Simon LG
  • 2,907
  • 1
  • 17
  • 16
  • I don't like the idea of ditching enums, but I like this answer the best. All the shared code is in the parent class and the child classes contain static definitions of all the enum values. Constructors are kept private so instances can't be created outside of the class. Defining each enum value is a lot more verbose though because you have to create a new object (`public static final...` as opposed to `DOM("dom")`). – Michael Jun 26 '12 at 00:50
1

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)
Woot4Moo
  • 23,987
  • 16
  • 94
  • 151
  • I don't want the enums to share a common class necessarily because like you said, I don't want them to be treated in a polymorphic way (e.g. casting a child class to the parent class). – Michael Jun 26 '12 at 00:56
1

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.

David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • David thanks, didn't know that about Java 8. I still have Java 6 on my Mac, I'm afraid to install 7 because Apple hasn't "approved" it yet. Installing JVMs on Windows is so much simpler--they all go in the `Program Files\Java` folder. On Mac, the files seem to be all over the place. – Michael Jun 26 '12 at 01:06