Java Class.cast()和重载

时间:2014-01-09 21:54:59

标签: java class casting overloading double-dispatch

我试图为一个小服务器编写数据包监听器代码。我是Java的新手,这是我第一次搞乱网络。整个想法是它重新接收数据包,将数据包ID与它的类匹配,将输入流传递给数据包的构造函数,以便可以构造它然后将它提供给packetHander,它将具有每个数据包的重叠方法。要使用将数据包ID映射到每个类的数组,并使用一个名为decode的方法来构造数据包,来实现这个im。问题是handlePacket超载它的行为不符合预期。让我们看一些代码。

我在线程中运行数据包侦听器,run方法如下所示:

public void run() {
    try {
        int packet_id;
        while ((packet_id = istream.readInt()) != -1) {
            plugin.getServer().getConsoleSender().sendMessage("[Comm] Recived packet " + packet_id);
            Packet packet = decode(packet_id, istream);

            plugin.getServer().getConsoleSender().sendMessage("[Comm] Packet is " + Class.forName(packet.getClass().getName()));
                            plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Packet00ReqIdentify.class.cast(packet).getClass().getName());
            plugin.getServer().getConsoleSender().sendMessage("[Comm] Class is " + Class.forName(packet.getClass().getName()).getName());

            handlePacket(packet);
        }
    } catch (IOException | NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | ClassNotFoundException e) {
        e.printStackTrace();
    }
}

decode和handlePacket方法如下所示:

private void handlePacket(Packet00ReqIdentify packet) throws IOException {
    plugin.getServer().getConsoleSender().sendMessage("[Comm] Got it!");
}
private void handlePacket(Packet packet) {
    plugin.getServer().getConsoleSender().sendMessage("[Comm] Woops!");
}

private Packet decode(int packet_id, PacketInputStream istream) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException, IOException {
    Class<? extends Packet> packet_class = packets_ids.get(packet_id);
    try {
        Constructor<?> packet_constructor = packet_class.getConstructor(PacketInputStream.class);
        return Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_constructor.newInstance(istream));
    } catch (NoSuchMethodException e) {
        return  Class.forName(packet_class.getName()).asSubclass(Packet.class).cast(packet_class.newInstance());
    }
}

packets_ids它是一个数组,包含对每个数据包类的引用,由他们的id标记:

private static ArrayList<Class<? extends Packet>> packets_ids;

以这种方式初始化:

private static void registerPacket(int id, Class<? extends Packet> oclass) {
    packets_ids.add(id, oclass);
}

static {
    packets_ids = new ArrayList<Class<? extends Packet>>();
    registerPacket(Packet00ReqIdentify.assigned_pid, Packet00ReqIdentify.class);
    registerPacket(Packet01Identify.assigned_pid, Packet01Identify.class);
    registerPacket(Packet02Heartbeat.assigned_pid, Packet02Heartbeat.class);
}

如果我执行此操作并测试它发送类型为00的数据包,我得到这个:

17:37:49 [INFO] [Comm] Connection established to localhost:11000
17:37:49 [INFO] [Comm] Recived packet 0
17:37:49 [INFO] [Comm] Packet is class com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Class is com.gamerarg.commclient.protocol.Packet00ReqIdentify
17:37:49 [INFO] [Comm] Woops!

因此,这意味着packet00尚未被&#34; handlePacket(Packet00ReqIdentify数据包)&#34;所覆盖。如果我对&#34;数据包&#34;进行显式转换在handlePacket中调用它有效。所以问题是:

  • 为什么这不起作用?当我打印两个类名时,我得到相同的。

  • 我怎样才能让它发挥作用?我现在一直在努力工作6到7个小时,阅读,谷歌搜索,尝试和查看其他人的代码。一个更简单的解决方案是使用数据包ID进行切换但我想要更优雅的东西。 Maybye我错了基本想法,这就是我发布代码的原因,我对这个主题中更有经验的人的建议和想法持开放态度,包括对主题材料的推荐。 / p>

谢谢!

3 个答案:

答案 0 :(得分:3)

在每个Packet子类中,实现一个方法public void handle(),它可以完成处理数据包所需的工作。任

  • handle()
  • 中设置Packet的默认实施方式
  • handle()中将Packet声明为摘要,并将Packet设为抽象类,
  • handle()中声明Packet并使Packet成为界面。

然后替换

handlePacket(packet);

packet.handle();

这是行动中的多态性。它将在运行时工作,检查packet引用的对象的类,并调用handle方法的正确版本。

如果handle()需要访问原始PacketListener,请将其声明为public void handle(PacketListener listener)并将其称为packet.handle(this);

答案 1 :(得分:2)

这是你的问题:

Packet packet = decode(packet_id, istream);

--snip--

handlePacket(packet);

由于packet被定义为Packet,它被路由到handlePacket(Packet packet)方法,即使运行时类型是Packet的子类。

答案 2 :(得分:2)

你可以这样做(需要java8)

static Map<Class<?>, Consumer<?>> handlers = new HashMap<>();
void handlePacket(Packet packet)
{
    Consumer<Packet> handler = (Consumer<Packet>)handlers.get(packet.getClass());
    handler.accept(packet);
}

static
{
    handlers.put(Packet00ReqIdentify.class, (Packet00ReqIdentify packet)->{
        System.out.println("Packet00ReqIdentify");
    });
    handlers.put(Packet01Identify.class, (Packet01Identify packet)->{
        System.out.println("Packet01Identify");
    });
    // etc.
}

这个用例“双重发送”很频繁,我们应该为它做一个通用的工具,比如

public class DoubleDispatch<T, R>
{
    public R invoke(T obj){...}

    public <C extends T> void register(Class<C> type, Function<C,R> func){...}
}

可以用来解决这个问题:

    DoubleDispatch<Packet,Void> dd = new DoubleDispatch<>();
    dd.register(Packet00ReqIdentify.class, packet->{
        System.out.println("Packet00ReqIdentify");
        return null;
    });
    // etc

    ...
    dd.invoke(packet);

如果没有lambda表达式,我们可以使用匿名内部类来实现类似

的东西
    dd.new Handler<Packet00ReqIdentify>(){
        public Void handle(Packet00ReqIdentify obj) {
            System.out.println("Packet00ReqIdentify");
            return null;
        }
    };