Java Class.cast() и перегрузка

Я пытаюсь закодировать прослушиватель пакетов для небольшого сервера. Я очень плохо знаком с Java, и это первый раз, когда я возиться с сетью. Идея состоит в том, чтобы получить пакет, сопоставить идентификатор пакета с его классом, передать входной поток конструктору пакета, чтобы он мог быть сконструирован, а затем передать его пакету Hander, который будет иметь метод с наложением для каждого пакета. Чтобы достичь этого, я использую массив, который отображает идентификаторы пакетов на классы каждого из них, и использует метод, называемый декодированием, для создания пакета. Проблема заключается в перегрузке 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());
    }
}

packages_ids - это массив, который содержит ссылку на класс каждого пакета, проиндексированный по их идентификаторам:

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!

Таким образом, это означает, что package00 не был обработан "handlePacket(пакет Packet00ReqIdentify)". Если я сделаю явное приведение к "пакету" в вызове handlePacket, это сработает. Итак, вопросы:

  • Почему это не работает? Когда я печатаю имена классов для обоих, я получаю то же самое.

  • Как я могу заставить это работать? Я боролся с этим в течение 6 или 7 часов, читая, прибегая к помощи, пытаясь увидеть код от других. Одно более простое решение - сделать коммутатор, используя идентификатор пакета, но я хочу что-то более элегантное. Может, я ошибаюсь в базовой идее, поэтому я опубликовал код, я открыт для предложений и идей более опытных людей по этой теме, включая рекомендации материала по этой теме.

Спасибо!

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

Вот ваша проблема:

Packet packet = decode(packet_id, istream);

--snip--

handlePacket(packet);

поскольку packet определяется как Packet он направляется к handlePacket(Packet packet) метод, хотя тип времени выполнения является подклассом Packet.

Вы можете сделать это (требуется 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);

Без лямбда-выражения мы могли бы использовать анонимный внутренний класс, чтобы достичь чего-то вроде

    dd.new Handler<Packet00ReqIdentify>(){
        public Void handle(Packet00ReqIdentify obj) {
            System.out.println("Packet00ReqIdentify");
            return null;
        }
    };
Другие вопросы по тегам