Вопрос о шаблонах Java Generics
Я сталкиваюсь с некоторыми проблемами с Generics при использовании Google Guava превосходного Multimap. У меня есть обработчик типа, определенный как
public interface Handler<T extends Serializable> {
void handle(T t);
}
В другом классе я определил multimap, который отображает String в коллекцию обработчиков.
private Multimap<String, Handler<? extends Serializable>> multimap =
ArrayListMultimap.create();
Теперь, когда я пытаюсь сделать материал с помощью multimap, я получаю ошибки компилятора. Моя первая попытка выглядела так:
public <T extends Serializable> void doStuff1(String s, T t) {
Collection<Handler<T>> collection = multimap.get(s);
for (Handler<T> handler : collection) {
handler.handle(t);
}
}
что привело к следующей ошибке.
Type mismatch: cannot convert from Collection<Handler<? extends Serializable>>
to Collection<Handler<T>>
Впоследствии я попытался закодировать его следующим образом
public void doStuff2(String s, Serializable serializable) {
Collection<Handler<? extends Serializable>> collection = multimap.get(s);
for (Handler<? extends Serializable> handler : collection) {
handler.handle(serializable);
}
}
к сожалению, тоже не удалось:
The method handle(capture#1-of ? extends Serializable) in the type
Handler<capture#1-of ? extends Serializable> is not applicable for the arguments
(Serializable)
Любая помощь будет принята с благодарностью. Спасибо.
Update:
Единственным способом, которым я смог это исправить, является подавление предупреждений компилятора. Учитывая следующий обработчик:
public interface Handler<T extends Event> {
void handle(T t);
Class<T> getType();
}
Я могу написать шину событий как таковую.
public class EventBus {
private Multimap<Class<?>, Handler<?>> multimap = ArrayListMultimap.create();
public <T extends Event> void subscribe(Handler<T> handler) {
multimap.put(handler.getType(), handler);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void publish(Event event) {
Collection<Handler<?>> collection = multimap.get(event.getClass());
for (Handler handler : collection) {
handler.handle(event);
}
}
}
Думаю, нет способа справиться с этим с меньшим или даже без @SuppressWarnings?
Ответы
Ответ 1
Это будет работать лучше, если вы определите:
private Multimap<String, Handler<Serializable>> multimap =
ArrayListMultimap.create();
Обновление: объяснение вашей проблемы.
Когда у вас есть что-то вроде..
private Multimap<String, Handler<? extends Serializable>> multimap;
Это означает, что multimap может принимать ANY Handler<N>
где N Extends Serializable
. Предположим, что он будет содержать значение типа Handler<Foo>
и значение типа Handler<Bar>
. Foo
и Bar
не связаны друг с другом и не расширяются друг от друга.
Когда в вашей функции вы хотите использовать тип для представления типа всех возможных значений Handler<? extends Serializable>
, вы пытаетесь выразить тип, который в то же время Foo
и Bar
, но там не является таким типом.
Это объясняет вашу проблему с компилятором. Теперь удалите это "-1" и проголосуйте за мой ответ, если вы считаете, что я прав.
Ответ 2
Проблема в том, что типы могут быть разными:
private Multimap<String, Handler<? extends Serializable>> multimap =
ArrayListMultimap.create();
не позволит вам ничего добавлять к мультимарам, так как вы не знаете, что означает ?
. Например, вы можете иметь Multimap<String, Handler<String>>
и попытаться добавить Integer
, потому что оба реализуют Serializable
.
Изменить: На самом деле вышеприведенный параграф немного ошибочен. Вы должны иметь возможность добавлять обработчики к мультимарам, но поскольку параметры типа обработчиков неизвестны, вы не сможете использовать обработчики, как показано ниже.
В вашем методе doStuff1
вы определяете конкретный параметр T
, который может быть чем-то совершенно другим. Таким образом, компилятор не может определить, правильно ли это назначение: Collection<Handler<T>> collection = multimap.get(s);
(есть T
действительно тип обработчика, который вы получаете из мультимапа? - компилятор не знает).
Ваш второй подход действительно дает право на назначение, однако метод handle()
не будет работать, поскольку вы передаете Serializable
, который может быть чем угодно (String
, Integer
, что-то еще) и компилятор все еще не знает, соответствует ли тип обработчика (представьте себе a Handler<Number>
и вы передаете String
в doStuff2
).
У вас есть несколько альтернатив для исправления, каждый из которых имеет свои недостатки:
- Просто используйте
Multimap<String, Handler<Serializable>>
, который позволит вам передать любой объект Serializable
обработчику
- Используйте конкретный тип, например.
Multimap<String, Handler<String>>
, который ограничивает вас только обработчиками строк
- Получить параметр типа обработчика во время выполнения и бросить, что может быть подвержено ошибкам, если вы не получите его правильно
Ответ 3
Я немного изменил ваш обновленный код.
Теперь он работает без @SuppressWarnings.
Я сделал некоторые предположения, конечно.
Но я надеюсь, что это будет полезно.
public interface Handler<T extends Event> {
//void handle(T t);
// It seems you won't need dependency of T in this method implementation.
// So, you can use generic method here.
<S extends Event> void handle(S s);
Class<T> getType();
}
Измененный EventBus.
public class EventBus {
//private Multimap<Class<?>, Handler<?>> multimap = ArrayListMultimap.create();
// It seems you don't need anything except Event here.
// So. Logicaly more correct to use the bounded wildcard.
private Multimap<Class<? extends Event>, Handler<? extends Event>>
multimap = ArrayListMultimap.create();
//public <T extends Event> void subscribe(Handler<T> handler) {
// You don't need to use generic method here.
// Because T is never used in method implementation.
// Wildcard fits simpler.
public void subscribe(Handler<? extends Event> handler) {
multimap.put(handler.getType(), handler);
}
//@SuppressWarnings({ "rawtypes", "unchecked" })
public void publish(Event event) {
//Collection<Handler<?>> collection = multimap.get(event.getClass());
// The bounded wildcard again.
Collection<Handler<? extends Event>>
collection = multimap.get(event.getClass());
//for (Handler handler : collection) {
for (Handler<? extends Event> handler : collection) {
handler.handle(event);
}
}
}
И еще один код, просто чтобы завершить пример.
public class Main {
public static void main(String[] args) {
EventBus bus = new EventBus();
bus.subscribe(new Handler<MyEvent> () {
public <S extends Event> void handle(S s) {
System.out.println(s);
}
public Class<MyEvent> getType() {
return MyEvent.class;
}
});
bus.publish(new MyEvent());
}
}
class MyEvent implements Event {
// Event implementation ...
}
Вывод программы выглядит следующим образом:
[email protected]
Ответ 4
Ответ на две части. Firts, ваш метод "уничтожает" объекты, поэтому вы не можете использовать "extends"... вам нужно использовать "super" (PECS: Producer Extends, Consumer Super!).
Без изменения вашего обработчика это компилируется без предупреждений для меня:
private Multimap<String, Handler<? super Serializable>> multimap = ArrayListMultimap.create();
public void doStuff1(String s, Serializable t) {
Collection<Handler<? super Serializable>> collection = multimap.get(s);
for (Handler<? super Serializable> handler : collection) {
handler.handle(t);
}
}
Таким образом вы определяете multimap из строки в обработчик, который потребляет по меньшей мере Serializable.
Во-вторых, я часто использовал нечто похожее на вашу конструкцию:
Map<Class<?>, Handler<?>>
и где возвращаемый обработчик является потребителем класса. Основная проблема заключается в том, что у вас нет способа "добавить к родовому типу", когда вы знаете больше... Если вам нужно, всегда объявляйте новую переменную, можно поместить @SuppressWarning в объявление:
@SuppressWarnings("unchecked")
Handler<String> myHandler = (Handler<String>) getHandler();
Это работает, только если вы используете весь родовой тип. Если вам дан хендлер, и вы знаете, что то, что у вас есть, - это Handler > , единственный способ, которым вы можете его использовать, - это пройти через необработанный тип:
Handler<List> myLessSpecifiedHandler = getHandler();
@SuppressWarnings("unchecked")
Handler<List<String>> myHandler = (Handler<List<String>>) (Handler) myLessSpecifiedHandler;
Если вы этого не сделаете, вы получите сообщение об ошибке вместо предупреждения...
Да, дженерики вроде грязные..: -/