5

I have the following interface:

public interface Moon {
    // Enums can not extend any class in Java,
    // thus I am implementing an interface
    double getMass();
    double getDiameter();
}

that is implemented by my enums (I omitted the constructor and methods implementation)

public enum Saturn implements Moon {
    ATLAS(30.2, 6.6),
    PAN(28.2, 4.95);

    private double Diameter;
    private double Mass;

    // constructor and methods implementation
}

and similarly enums for other planets:

public enum Mars implements Moon {
    PHOBOS(22.2, 1.08),
    DEIMOS(12.6, 2);

    private double Diameter;
    private double Mass;

    // constructor and methods implementation
}

Question

Having two strings:

String planetName = "Mars";
String moonName = "PHOBOS";

How can I obtain the particular enum in a smart way?

In the case of only one enum class it is a simple use of the valueOf method, but how can I check all enums that implement a particular interface?

Moon something = <??planetName??>.valueOf(moonName);
Boann
  • 48,794
  • 16
  • 117
  • 146
matandked
  • 1,527
  • 4
  • 26
  • 51

5 Answers5

2

You can use a mapping of planet names to related enum types, and use that to perform a lookup.

For example, using a map (there are surely alternative approaches):

Map<String, Class<? extends Enum>> planetMoonTypes = new HashMap<>();

planetMoonTypes.put("Saturn", Saturn.class);
planetMoonTypes.put("Mars", Mars.class);

With such a map, you can lookup the "PHOBOS" value using:

Moon phobos = (Moon) Enum.valueOf(planetMoonTypes.get("Mars"), "PHOBOS");

The above will return Mars.PHOBOS


EDIT: Regarding the ability to add non-Moon enums. You can encapsulate the map and force proper bounds. For example, the following uses a method for that:

class MoonRegistry {

    private final Map<String, Class<? extends Enum>> 
              planetMoonTypes = new HashMap<>();

    {
        this.registerEnum("Saturn", Saturn.class);
        this.registerEnum("Mars", Mars.class);
    }

    public <T extends Enum<T> & Moon> void registerEnum(
            String planetName, Class<T> cls) {
        this.planetMoonTypes.put(planetName, cls);
    }

    public Moon findByPlanetAndName(String planet, String name) {
        return (Moon) Enum.valueOf(this.planetMoonTypes.get(planet), name);
    }
}

Again, this solution cannot be fully type-safe because there are multiple enum subtypes (as you can see, the raw Enum type is in the type of the instance field). But if this is properly encapsulated and your code uses no raw types outside this class, you can ensure that only enum types implementing Moon are added.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • This looks nice and clean. However, as I can see this map ensures that classes in map are enumeration (`extends enum`), but it doesn't limit to the classes that implements `Moon` interface. Thus I could add potentially any enumeration to this map without warnings. – matandked Dec 29 '18 at 15:56
  • @matandked That's right. You could extend the solution with a generic method to make it appropriately bounded. I'll edit. – ernest_k Dec 29 '18 at 15:58
  • @matandked Check the edit (and of course the last remark :-)) – ernest_k Dec 29 '18 at 16:12
  • @Boann Thanks :-) I'll correct that - I have no idea how I managed to make that up :D – ernest_k Dec 29 '18 at 19:39
2

If you really want it to be dynamic, you could achieve this using Reflection, event if I don't recommend it. You could either

Ricola
  • 2,621
  • 12
  • 22
  • Honestly, that idea with getting all implementing classes via reflection is close to plain horrible. Stool, nice answer! – GhostCat Dec 29 '18 at 12:10
1

I suspect you have a weakness in your design that is causing you to look for complex ways to obtain the enum instance you need. Having a separate enum for each planet probably doesn't make a lot of sense when they are all unique moons. Better to rethink the design a bit. Something like:

interface Body {
    double getMass();
    double getDiameter();
}

enum Planet implements Body {
    MERCURY, VENUS...
}

enum Moon implements Body {
    ATLAS, PHOBOS, DEIMOS...

    public Planet getPlanet();
}

That way it's easy to get planets or moons by name. Or get all the moons of a planet with:

Arrays.stream(Moon.values())
    .filter(m -> m.getPlanet().equals(this))
    ...
sprinter
  • 27,148
  • 6
  • 47
  • 78
1

If all your enums are in the same package, then this solution could work.

String planetName = "Mars";
String moonName = "PHOBOS";

try {
    Moon moons[] = (Moon[]) Class.forName("your_package_name" + planetName).getEnumConstants();

    // The following line is not really needed, but I've added it as an 
    // illustration as to what you could do with the moons[]
    Moon planet = Arrays.stream(moons).filter(i -> i.toString().equals(moonName))
                          .findFirst().orElse(null);

} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
Nicholas K
  • 15,148
  • 7
  • 31
  • 57
1

Consider using an extra enum Plantes with its moons assigned as an enum property like this

public enum Planets {
    MARS(Mars.values()), SATURN(Saturn.values());

    private final Enum<?>[] Moons;

    private Planets(final Enum<?>[] Moons) {
        this.Moons = Moons;
    }

    public Moon getMoon(final String moon) {
        return (Moon) Arrays
                .stream(this.Moons)
                .filter(e -> e.name().equals(moon))
                .findFirst()
                .get();
    }
}

It takes the Moons from the already defined enums and assigned it to an enum for each planet. There is also a getMoon() (function valueOf() is already used by enum itself), which searches for a moon.

You can use it like this:

    // Outputs 22.2
    System.out.println("" + Planets.valueOf("MARS").getMoon("PHOBOS").getDiameter());
kwarnke
  • 1,424
  • 1
  • 15
  • 10