【问题标题】:Instantiate class dynamically based on some constant in Java基于Java中的一些常量动态实例化类
【发布时间】:2015-07-07 20:33:47
【问题描述】:

我正在制作一个多人游戏,它大量使用可序列化的Event 类通过网络发送消息。我希望能够基于常量重构Event 的适当子类。

到目前为止,我选择了以下解决方案:

public class EventFactory {

    public static Event getEvent(int eventId, ByteBuffer buf) {
        switch (eventId){
        case Event.ID_A:
            return EventA.deserialise(buf);
        case Event.ID_B:
            return EventB.deserialise(buf);
        case Event.ID_C:
            return EventC.deserialise(buf);
        default:
            // Unknown Event ID
            return null;
        }
    }

}

但是,这让我觉得非常冗长,并且每次创建新的 Event 类型时都会添加一个新的“case”语句。

我知道实现此目的的其他 2 种方法,但似乎都没有更好*:

  1. 创建常量映射 -> 事件子类,并使用 clazz.newInstance() 实例化它们(使用空的构造函数),然后使用 clazz.initialiase(buf) 提供必要的参数。
  2. 创建常量映射 -> 事件子类,并使用反射在适当的类中查找和调​​用正确的方法。

有没有比我现在使用的更好的方法?我是否不明智地忽略上述替代方案?


*注意:在这种情况下,更好意味着更简单/更清洁,但不会过多影响速度。

【问题讨论】:

  • 事件有多少种?是否有不使用某些标准有线格式(例如 JSON 或 XML)的特殊原因?
  • 为什么不让Event实现Serializable,然后直接读写Event对象呢?
  • 目前有大约 20 种事件类型,但我会继续添加更多。目前我正在使用 ByteBuffers 手动序列化/反序列化事件,正如您在此处看到的那样,因为它快速且非常少;我想发送尽可能少的字节。我不能使用 Serializable,因为我是以数据包而不是流的形式发送数据。不过我对 JSON 很好奇 - 与速度和发送的数据量相比如何?

标签: java reflection instantiation


【解决方案1】:

您可以只使用HashMap<Integer,Event> 来获取 eventID 的正确事件。添加或删除事件将很容易,并且随着代码的增长,与 switch case 解决方案相比,这很容易维护,而且速度方面也应该比 switch case 解决方案更快。

   static
   {
         HashMap<Integer,Event> eventHandlerMap = new HashMap<>();

         eventHandlerMap.put(eventId_A, new EventHandlerA());
         eventHandlerMap.put(eventId_B, new EventHandlerB());
         ............
   }

代替你的 switch 语句现在你可以使用:

   Event event = eventHandlerMap.get(eventId);
   if(event!=null){
      event.deserialise(buf);
   }

【讨论】:

  • 我喜欢使用地图的想法,但这依赖于提前创建每个事件的实例(我使用的 'EventHandler' 应该是 'Event'),看起来就像占位符一样,并且依赖于空的构造函数,使其类似于我建议的第一个替代方案。
  • 您可以使用 eventid 到类名的映射,然后通过反射创建类。但反射通常被认为是缓慢的。顺便说一句,我试图强调使用哈希图的想法。
【解决方案2】:

如果你不怕反射,你可以使用:

    private static final Map<Integer, Method> EVENTID_METHOD_MAP = new LinkedHashMap<>();

    static {
        try {
            for (Field field : Event.class.getFields())
                if (field.getName().startsWith("ID_")) {
                    String classSuffix = field.getName().substring(3);
                    Class<?> cls = Class.forName("Event" + classSuffix);
                    Method method = cls.getMethod("deserialize", ByteBuffer.class);
                    EVENTID_METHOD_MAP.put(field.getInt(null), method);
                }
        } catch (IllegalAccessException|ClassNotFoundException|NoSuchMethodException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static Event getEvent(int eventId, ByteBuffer buf)
    throws InvocationTargetException, IllegalAccessException {
        return (Event) EVENTID_METHOD_MAP.get(eventId).invoke(null, buf);
    }

此解决方案要求int ID_N 始终映射到class EventN,其中N 可以是任何字符串,其中所有字符都为方法true 返回true。此外,class EventN 必须定义一个名为 deserialize 的静态方法,并带有一个返回 EventByteBuffer 参数。

您还可以在尝试获取其字段值之前检查field 是否为静态。我现在只是忘记了如何做到这一点。

【讨论】:

  • 我对此持开放态度。看起来并不算太吓人,但不知道利大于弊。这在速度方面与 switch 语句相比如何?
猜你喜欢
  • 2014-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-26
  • 2019-11-08
相关资源
最近更新 更多