Вопрос о шаблонах 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;

Если вы этого не сделаете, вы получите сообщение об ошибке вместо предупреждения...

Да, дженерики вроде грязные..: -/