1

Is there a way to create an enum abstraction with different values based on enum names?

Here is a class where I would find something like abstract enum useful:

public class StatusUpdater {
public foo(bool one, bool two, bool three, AbstractEnum status) {
    if (one) {
        caseOne(status);
    }

    if (two) {
        caseTwo(status);
    }

    if (three) {
        caseThree(status);
    }
}

private void caseOne(CaseOneEnum status) {
    status.getValue(); //return case one value + case one implementation
}

private void caseTwo(CaseTwoEnum status) {
    status.getValue(); //return case two value + case two implementation
}

private void caseThree(CaseThreeEnum status) {
    status.getValue(); //return case three value + case three implementation
}

}

One Of the concrete implementation of enum looks like this:

public enum CaseOneEnum {
STATUS_ONE("one"),
STATUS_TWO("two");

private final String status;

CaseOneEnum(final String status) {
    this.status = status;
}

@Override
String getValue(){return status;}

Another implementation will have same enum names, but different values:

public enum CaseTwoEnum {
STATUS_ONE("oneTwo"),
STATUS_TWO("twoTwo");

private final String status;

CaseTwoEnum(final String status) {
    this.status = status;
}

@Override
String getValue(){return status;}

Calling the main method should look something like this:

updater.foo(true, true, false, AbstractEnum.STATUS_ONE);

Is there a way where I could create some "abstract" enum which i could pass to foo() and after checking the case take the concrete implementation of this enum. Enum names for all the concrete enums will stay the same, but values will differ. I would imagine an "abstract" enum something like this:

public enum AbstractEnum {
STATUS_ONE,
STATUS_TWO;

@Override
String getValue();

Is there a way of achieving something like that in a neat way?

LakyJ
  • 43
  • 9
  • 4
    You can use an interface instead of `AbstractEnum`, can't you? Enums can implement an interface. – ernest_k Nov 05 '19 at 09:17
  • 1
    May not be what you're looking for, but an enum can have abstract methods that are implemented by each constant: https://stackoverflow.com/questions/7413872/can-an-enum-have-abstract-methods – Slaw Nov 05 '19 at 09:19
  • This [question](https://stackoverflow.com/questions/2164124/java-enums-switch-statements-vs-visitor-pattern-on-enums-performance-benefit) may be of help. One of the answers suggest to check the _Effective_ _Java_ book, which I would suggest too. – francesco foresti Nov 05 '19 at 09:25
  • @ernest_k yes, but can I access status name though interface? I would need something like InterfaceEnum.STATUS_ONE to pass to main class foo(). How should this look like? – LakyJ Nov 05 '19 at 09:49
  • @LakyJ No. Not if your methods are changed to accept the interface type too (they have different names, so shouldn't be a problem) – ernest_k Nov 05 '19 at 09:51
  • @ernest_k but if i change methods to accept interface type I will need to cast them to concrete type, otherwise I will not get the concrete enum values. – LakyJ Nov 05 '19 at 09:54
  • @LakyJ No. The interface will declare the `getValue()` method, so there's no need for `caseOne`, `caseTwo`... methods to declare the parameters as of `EnumOne` or `EnumTwo` (all of them just take the interface type, and maybe you'll need just one `case...` method, unless their actual implementation is different). If you show us how you're calling the `foo()` method, I'll show you what I mean. – ernest_k Nov 05 '19 at 09:57
  • @ernest_k Updated the question based on comments. Have a look – LakyJ Nov 05 '19 at 10:03

3 Answers3

2

indeed all enums are descendants of object Enum and this constitutes already a generic type that you may use to implement a method signature independent from actual enum implementation.

Your code then shows that for each "case" you know exactly which enum to expect and thus you can simply class cast the input object when invoking the dedicated method.

I took your code and modified it a little bit to demonstrate what I'm suggesting.

Two enums implementations

public enum CaseOneEnum {
    STATUS_ONE("one"),
    STATUS_TWO("two");

    private final String status;

    CaseOneEnum(final String status) {
        this.status = status;
    }

    String getValue() {
        return status;
    }
}

public enum CaseTwoEnum {
     STATUS_THREE("three"),
     STATUS_FOUR("four");

     private final String status;

     CaseTwoEnum(final String status) {
         this.status = status;
     }

     String getValue() {
         return status;
     }
 }

The StatusUpdater class

public class StatusUpdater {

    public String foo(int caze, Enum<?> status) {

        if (caze == 1) {
            return caseOne((CaseOneEnum) status);
        }

        if (caze == 2) {
            return caseTwo((CaseTwoEnum) status);
        }
        return null;
    }

    private String caseOne(CaseOneEnum status) {
        return status.getValue();
    }

    private String caseTwo(CaseTwoEnum status) {
        return status.getValue();
    }
}

Most important thing to be noticed is the foo method signature exposing a parameter of type Enum and the class cast when each case method is invoked.

And finally a JUnit to demonstrate.

public class StatusUpdaterTest {

    @Test
    public void testStatusUpdaterCase1() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseOneEnum.STATUS_ONE.getValue(), updater.foo(1, CaseOneEnum.STATUS_ONE));
}

    @Test
    public void testStatusUpdaterCase2() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseOneEnum.STATUS_TWO.getValue(), updater.foo(1, CaseOneEnum.STATUS_TWO));
    }

    @Test
    public void testStatusUpdaterCase3() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseTwoEnum.STATUS_THREE.getValue(), updater.foo(2, CaseTwoEnum.STATUS_THREE));
    }

    @Test
    public void testStatusUpdaterCase4() {
        StatusUpdater updater = new StatusUpdater();
        assertEquals(CaseTwoEnum.STATUS_FOUR.getValue(), updater.foo(2, CaseTwoEnum.STATUS_FOUR));
    }
}

Hope this helps!

Cheers

Sergio Arrighi
  • 530
  • 1
  • 4
  • 15
1

Here's a solution using an interface. Because enums can implement an interface, you can get rid of the overloaded methods (unless their implementations differ).

interface MyEnumInterface {
    String getValue();
}

With that, your StatusUpdater class becomes:

public class StatusUpdater {
    public void foo(MyEnumInterface status) {
        anyCase(status);
    }

    //You can duplicate this with caseOne, caseTwo methods if
    //implementations are actually different
    private void anyCase(MyEnumInterface status) {
        status.getValue(); 
    }
}

Then you call your foo method in this way:

updater.foo(1, CaseOneEnum.STATUS_ONE);
updater.foo(1, CaseTwoEnum.STATUS_ONE);

And then all that's left is making your enums implement the interface (they already have the method):

enum CaseOneEnum implements MyEnumInterface {
    STATUS_ONE("one"), 
    STATUS_TWO("two");

    private final String status;

    CaseOneEnum(final String status) {
        this.status = status;
    }

    @Override
    public String getValue() {
        return status;
    }
}

//other enums implement the
ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • This would seem a correct answer if I have formulated the question more clear... I will update the question in a minute. But the idea of all this is that calling the main function should not bind to any concrete implementation of enum. In fact, multiple enum implementations will be used in the main class. Check the updated question in a minute. – LakyJ Nov 05 '19 at 10:31
  • @LakyJ I suppose that's what I meant with *"implementations are actually different"*. Then you can use the `if` blocks from your example. The main point in this answer is that you can use an interface for abstraction across those enums and you don't need enum-specific methods (except if methods are implemented differently, which seems the case although wasn't illustrated). – ernest_k Nov 05 '19 at 11:44
0

You are mixing up enumerations, which define constants, and associated data. The reason you want to define an "abstract enum", is, that there is only one set of constants, so the actual conclusion is that there should be only one enum.

While enum types allow to hold associated data directly, you should use this feature only for canonical associations. When, like in your case, there are different sets of associated data, you shouldn't store them in the enum type, as that allows only one-to-one relationships. Further, instead of creating another redundant enum type defining identical constants, store the data in an EnumMap, which is specifically designed to associate data with enum constants.

Define a simple enum type as

public enum YourActualEnum {
    STATUS_ONE,
    STATUS_TWO
}

and a class having different associated data, depending on the use case:

public class StatusUpdater {
    private static final EnumMap<YourActualEnum,String> CASE1, CASE2, CASE3;
    static {
        CASE1 = new EnumMap<>(YourActualEnum.class);
        CASE1.put(YourActualEnum.STATUS_ONE, "one");
        CASE1.put(YourActualEnum.STATUS_TWO, "two");
        CASE2 = new EnumMap<>(YourActualEnum.class);
        CASE2.put(YourActualEnum.STATUS_ONE, "oneTwo");
        CASE2.put(YourActualEnum.STATUS_TWO, "twoTwo");
        CASE3 = new EnumMap<>(YourActualEnum.class);
        CASE3.put(YourActualEnum.STATUS_ONE, "oneThree");
        CASE3.put(YourActualEnum.STATUS_TWO, "twoThree");
    }
    public void foo(boolean one, boolean two, boolean three, YourActualEnum status) {
        if (one) {
            caseOne(status, CASE1);
        }

        if (two) {
            caseTwo(status, CASE2);
        }

        if (three) {
            caseThree(status, CASE3);
        }
    }

    private void caseOne(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }

    private void caseTwo(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }

    private void caseThree(YourActualEnum status, EnumMap<YourActualEnum,String> m) {
        m.get(status);
    }
}
Holger
  • 285,553
  • 42
  • 434
  • 765