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