如何在Java中获取实现特定接口的适当枚举?

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();
}

由我的枚举实现(我省略了构造函数和方法的实现)
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
}

类似地,其他行星的枚举也是这样:

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

    private double Diameter;
    private double Mass;

    // constructor and methods implementation
}

问题

有两个字符串:

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

如何以聪明的方式获取特定的枚举?

如果只有一个枚举类,可以简单地使用valueOf方法,但是如何检查实现特定接口的所有枚举?

Moon something = <??planetName??>.valueOf(moonName);

太棒了!正是我需要的用例! - Ahmad Tn
5个回答

2
如果您真的想让它具有动态性,您可以使用反射来实现,即使我不推荐这样做。您可以选择:

说实话,通过反射获取所有实现类的想法几乎是可怕的。Stool,回答得好! - GhostCat

2
你可以使用行星名称到相关枚举类型的映射,并使用它来执行查找。
例如,使用一个映射(当然还有其他方法):
Map<String, Class<? extends Enum>> planetMoonTypes = new HashMap<>();

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

有了这样一张地图,您可以使用以下方式查找"PHOBOS"的值:

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

以上内容将返回 Mars.PHOBOS
编辑: 关于添加非 Moon 枚举的能力。您可以封装该映射并强制使用正确的边界。例如,以下示例使用了一种方法:
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);
    }
}

再次强调,这个解决方案不能完全保证类型安全,因为存在多种枚举子类型(正如您所见,原始的Enum类型在实例字段的类型中)。但是,如果这个方案被正确封装,并且您的代码在这个类之外不使用原始类型,您可以确保只添加实现了Moon接口的枚举类型。


这看起来很好,很干净。然而,我可以看到这个映射确保映射中的类是枚举(extends enum),但它并不限制实现 Moon 接口的类。因此,我可以潜在地添加任何枚举到这个映射中而没有警告。 - matandked
@matandked 没错。您可以使用通用方法扩展解决方案,使其适当地受限。我会进行编辑。 - ernest_k
@matandked 检查编辑(当然还有最后一条备注:-)) - ernest_k
@Boann 谢谢 :-) 我会纠正的 - 我不知道我是怎么想出来的 :D - ernest_k

1
我怀疑你的设计有缺陷,导致你需要寻找复杂的方法来获取所需的枚举实例。当所有卫星都是独特的时候,为每个行星单独创建一个枚举可能没有太多意义。最好重新考虑一下设计,例如:
interface Body {
    double getMass();
    double getDiameter();
}

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

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

    public Planet getPlanet();
}

这样就可以通过名称轻松获取行星或卫星。或者通过以下方式获取行星的所有卫星:
Arrays.stream(Moon.values())
    .filter(m -> m.getPlanet().equals(this))
    ...

这是个不错的建议。 - matandked

1
如果您的所有枚举都在同一个包中,那么这个解决方案可能适用。
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();
}

我们需要将所有内容转换为“Object”吗?难道不能使用“Moon”代替吗? - matandked
可以的。我已经编辑了答案来说明相同的内容。 - Nicholas K

1
考虑使用一个额外的enum Plantes,并将其卫星分配为枚举属性,如下所示。
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();
    }
}

它从已定义的枚举中获取月球,并将其分配给每个行星的枚举。还有一个getMoon()函数(valueOf()函数已被枚举本身使用),用于搜索月球。
您可以像这样使用它:
    // Outputs 22.2
    System.out.println("" + Planets.valueOf("MARS").getMoon("PHOBOS").getDiameter());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接