【问题标题】:How can I simulate Enums that can be expanded at Runtime in Java?如何在 Java 中模拟可以在运行时扩展的枚举?
【发布时间】:2012-08-31 20:50:39
【问题描述】:


我正在编写一个程序来模拟一个名为《文明 4》的游戏中的城市。为了做到这一点,我有几个枚举来表示所述城市拥有的每个地块的地形、资源、改进等类型。

问题是我想编程以与粉丝制作的模组兼容,这可能会在游戏中添加需要被我的独立实用程序接受的东西。所以我想创建一个 Enum style 类来保存由加载的 mods 定义的新类型(因为 Enums 不能在运行时更改),这是在用户进入要加载的 mod 时在运行时创建的(这是一个 txt 文件,被解析以读取新增内容)

那么有没有办法模拟在运行时创建并添加到的枚举?我认为静态成员变量不能使用,因为它们是在运行时之前完成的......

【问题讨论】:

    标签: java class enums


    【解决方案1】:

    您可以让enum 实现一个接口。

    这样你可以在枚举中定义你的值,但新值可以是任何实现接口的类。


    另一种方法是在运行时使用字节码生成器或编译器 API 生成或加载 enum。我编写了一个库,以便更轻松地获取字符串并编译和加载它。

    http://vanillajava.blogspot.co.uk/2010_11_01_archive.html

    【讨论】:

    • 有趣...我认为实现接口的类不会添加到枚举中,而是用作外部存储?
    • 正确,这样可以混搭。
    【解决方案2】:

    嗯,Java 中的枚举是简单的类,其中语言保证已知对象的集合在编译时是已知的并受到限制。如果你想在运行时添加新的枚举字面量,你最终会得到常规的类。

    【讨论】:

    • 这对我来说很好。我有一组用于默认内容的枚举和一组用于存储运行时内容的类。但是我如何在运行时向一个类添加东西呢?当我想到它时,我可以让类对象成为所有对象都绑定到类的枚举(我认为这在 python 中是可能的,但不确定如何在 Java 中做到这一点)
    • 我不明白为什么你可以只为“预定义”实例创建一个带有一堆静态成员的常规类,然后在运行时创建该类的新实例?拥有枚举的目的是保证您提前知道一组值。
    • 因为这意味着要改造一切:p
    【解决方案3】:

    枚举的美妙之处在于,您可以在代码中编写人类可读的名称,这些名称在后台编译为数字,因为计算机更喜欢数字。以这个枚举为例:

    enum Season { WINTER, SPRING, SUMMER, AUTUMN }
    

    在幕后 WINTER 可能是 0(零),SPRING 是 1 等等。 要在运行时代码中复制此行为,您可以创建一个字符串列表,如下所示:

    List<String> seasons;
    seasons = new ArrayList<String>();
    seasons.add("Winter");
    seasons.add("Spring");
    ...
    

    这样您就可以将项目引用为数字,例如 seasons[1] 将等于“Spring”。 这个答案只是解决这个问题的众多方法之一。

    【讨论】:

    • 啊,你刚刚解决了一个将来会出现的问题。用户需要在界面上看到枚举,实例或数字不会削减它!不错的答案:D
    【解决方案4】:

    默认情况下,enum 类型只有一组值。 enum 类型中的值实际上声明为 static final,并且无法在运行时添加更多。

    话虽如此,您还可以使用其他模式来实现您想要的。让我们看一下使用接口和注册系统。我们将从Terrain 接口开始:

    public interface Terrain {
        int getId();
        String getName();
        int getSightBonus();
    }
    

    现在是enum、DefaultTerrain:

    public enum DefaultTerrain implements Terrain {
        PLAINS(0, "Plains", 1),
        HILLS(1, "Hills", -1),
        MOUNTAINS(2, "Mountains", -2);
    
        private int id;
        private String name;
        private int sightBonus;
    
        private DefaultTerrain(int id, String name, int sightBonus) {
            this.id = id;
            this.name = name;
            this.sightBonus = sightBonus;
        }
    
        public int getId() {return id;}
    
        public String getName() {return name;}
    
        public int getSightBonus() {return sightBonus;}
    }
    

    还有一个注册类,可以是静态实用程序类,也可以是单例类。

    public class TerrainManager {
        private static Map<Integer, Terrain> terrainsById = new HashMap<>();
    
        static {
            for (DefaultTerrain terrain : DefaultTerrain.values())
                register(terrain);
        }
    
        public static void register(Terrain terrain) {
            Integer id = terrain.getId();
            if (terrainsById.contains(terrain.getId()))
                throw new IllegalArgumentException("Terrain with id already exists: " + id);
            terrainsById.put(id, terrain);
        }
    
        public static Terrain getTerrainById(int id) {
            return terrainsById.get(id);
        }
    
        public static Set<Terrain> getAllTerrains() {
            return new HashSet<Terrain>(terrainsById.values());
        }
    }
    

    最后一节课是魔法发生的地方。大概模组制作者会在游戏的世界定义中使用某种标识符来表示“使用此图块”,对吧?在这种情况下,我称它为整数id,但实际上它可以是任何类型,只需相应地修改Map。在地图加载代码中,只需使用世界定义中的 ID 查找Terrain。当修改者添加新的Terrain 时,他们只需要实现Terrain 并将其注册到TerrainManager。

    static 初始化程序确保在添加任何其他内容之前添加您的 DefaultTerrain 对象。如果您使用单例,则可以将其放入类构造函数中。

    将此模式用于您希望用户添加的不同 enum 类型。除了enum,您还可以将它用于几乎任何其他类型。

    【讨论】:

    • 绝妙的答案!看起来确实是个不错的方法。如果 Java 中只存在运算符重载,我可以使 TerrainsManager 类重载 [] 以直接访问枚举。在游戏中,地形实际上是通过...枚举在 XML 中定义的!因此,您的 ID 可能会通过作为字符串来模拟它。不过,很棒的建议,我大约半小时前就考虑过自己使用这种方法。需要长时间的重新编码才能使当前代码适应,但值得!
    【解决方案5】:

    您将需要 CGLIB,http://cglib.sourceforge.net/

    【讨论】:

    • 谢谢,我以前没有使用过第三方库,我把它放在我的源文件中并正常包含(因为我不希望我的用户去拥有找到并安装一些额外的东西)。
    猜你喜欢
    • 2014-03-31
    • 1970-01-01
    • 2011-02-08
    • 1970-01-01
    • 2011-09-24
    • 1970-01-01
    • 2013-12-07
    • 2023-04-10
    相关资源
    最近更新 更多