Могут ли дженерики и (супер?) Типовые токены помочь в создании безопасного для типов агрегатора новостей?

У меня есть это основноеNews интерфейс

interface News {
    String getHeader();
    String getText();
}

и конкретные классы, такие какSportsNews а такжеFinancialNews предоставить конкретные методы, такие какgetStockPrice(), getSport() и так далее. Новости предназначены для отправки на

interface Subscriber<N extends News> {
    void onNews(N news);
}

Проблема в том, как зарегистрировать и поддерживать подписку. Первым подходом, который я попробовал, было использование центральногоAggregator, сохраняя карту междуClass<T> объекты иSet<Subscriber<T>>Но вскоре такой подход оказался нежизнеспособным. Вот желаемый API

public class Aggregator {

    public <N extends News> void subscribe(Subscriber<N> subscriber) {
        // TODO somehow (super type token) extract N and 
        // add the item to the set retrieved by getSubscribersFor()
    }

    public <N extends News> void dispatch(N news) {
        for (Subscriber<N> subscriber: getSubscribersFor(news.getClass())) {
            subscriber.onNews(news);
        }
    }

    private <N extends News> Set<Subscriber<N>> getSubscribersFor(Class<N> k) {
        // TODO retrieve the Set for the specified key from the Map
    }
}

Есть ли альтернатива безопасному типу? Может ли Java решить эту проблему вообще? я кладуэто небольшое демо онлайн чтобы помочь вам лучше понять, в чем проблема на самом деле.

ОБНОВИТЬ

Альтернативой было бы сделатьAggregator сам параметризован с фактическим типом новостей. Это было бы хорошо, за исключением того, что это проблема курицы и яйца: теперь нужно найти способ получить агрегатор. В Java нет возможности выразить следующее

interface News {
    static Aggregator<CurrentClass> getAggregator();
}
static метод не может бытьabstractнет никакого способа сослаться натекущий тип в аргументе типа
 Raffaele23 окт. 2012 г., 23:34
Извините, я редактировал код несколько раз, в зависимости от моей текущей попытки. забыватьПочему он не компилируется, а просто заставляет его работать :) Сохранить API (subscribe а такжеdispatch) все остальное можно свободно менять
 Raffaele23 окт. 2012 г., 23:13
@MiserableVariableAggregator не компилируется Я поставил подписи для описания желаемого API, но определенно задал этот вопрос, потому что не могу его скомпилировать ... Либо один метод работает, либо другой, в этом проблема
 Raffaele23 окт. 2012 г., 22:13
Нет ничего плохогоAggregator не компилируется, потому чтоgetSubscribersFor() ничего не возвращает и должен, но вот в чем вопрос ...
 Miserable Variable23 окт. 2012 г., 23:12
Я получаю ошибку вdispatch: несовместимые типы / найдено: Подписчик <захват № 847 из? расширяет Новости> / требуется: подписчик <N>
 Miserable Variable23 окт. 2012 г., 23:31
Возможно, я неправильно понял. я думал, что ты сказалAggregator не компилируется потому чтоgetSubscribersFor, Это не единственная причина, по которой он не компилируется, другая (более важная) ошибка заключается в том, чтоgetSubscribersFor(news.getClass() не возвращаетSet<Subscriber<N>>, потому чтоnews может быть любой другой подтипN

Ответы на вопрос(4)

Решение Вопроса

gle, написанная и используемая Google), я рекомендую прокрутить вниз и сначала посмотреть на другое решение.

Ванильная Ява

Во-первых, начните с добавления метода, чтобы получить класс от ваших подписчиков:

public interface Subscriber<N extends News> {
    void onNews(N news);
    Class<N> getSupportedNewsType();
}

Тогда при реализации:

public class MySubscriber implements Subscriber<MyNews> {

    // ...

    public Class<MyNews> getSupportedNewsType() {
        return MyNews.class;
    }
}

В свой агрегатор включите карту, где ключи и значения не напечатаны:

private Map<Class<?>, Set<Subscriber<?>> subscribersByClass = ... ;

Также обратите внимание, что в Guava есть многопользовательская реализация, которая сделает этот ключ для множества значений. Просто Google "Guava Multimap", и вы найдете его.

Чтобы зарегистрировать подписчика:

public <N extends News> void register(Subscriber<N> subscriber) {
    // The method used here creates a new set and puts it if one doesn't already exist
    Set<Subscriber<?>> subscribers = getSubscriberSet(subscriber.getSupportedNewsType());
    subscribers.add(subscriber);
}

И отправить:

@SuppressWarnings("unchecked");
public <N extends News> void dispatch(N news) {
    Set<Subscriber<?>> subs = subscribersByClass.get(news.getClass());
    if (subs == null)
        return;

    for (Subscriber<?> sub : subs) {
        ((Subscriber<N>) sub).onNews(news);
    }
}

Обратите внимание на актерский состав здесь. Это будет безопасно из-за характера дженериков междуregister метод иSubscriber интерфейс, при условии, что никто не делает что-то смехотворно неправильно, например, необработанную типизацию, такую какimplements Subscriber (нет общего аргумента).SuppressWarnings аннотация подавляет предупреждения об этом приведении от компилятора.

И ваш личный метод для получения подписчиков:

private Set<Subscriber<?>> getSubscriberSet(Class<?> clazz) {
    Set<Subscriber<?>> subs = subscribersByClass.get(news.getClass());
    if (subs == null) {
        subs = new HashSet<Subscriber<?>>();
        subscribersByClass.put(subs);
    }
    return subs;
}

Твойprivate методы и поля не обязательно должны быть безопасными. В любом случае это не вызовет никаких проблем, так как универсальные элементы Java реализованы посредством стирания, так что все наборы здесь будут просто набором объектов в любом случае. Попытка обеспечить их безопасность типов приведет только к неприятным, ненужным приведениям, которые не влияют на его правильность.

какаяделает Дело в том, что вашpublic методы являются типобезопасными. То, как дженерики объявлены вSubscriber и публичные методы наAggregatorЕдинственный способ сломать его - через необработанные типы, как я уже говорил выше. Короче говоря, каждыйSubscriber передан для регистрациигарантированный принимать типы, которые вы регистрируете, до тех пор, пока нет небезопасных приведений или необработанной типизации.

Использование гуавы

Кроме того, вы можете взглянуть на ГуаваEventBus, Это будет проще, ИМО, для того, что вы пытаетесь сделать.

Гуава-хEventBus класс использует управляемую аннотациями диспетчеризацию событий вместо управляемой интерфейсом. Это действительно просто. У тебя не будетSubscriber интерфейс больше. Вместо этого ваша реализация будет выглядеть так:

public class MySubscriber {
    // ...

    @Subscribe
    public void anyMethodNameYouWant(MyNews news) {
        // Handle news
    }
}

@Subscribe аннотирующие сигналы для гуавыEventBus что он должен помнить этот метод позже для отправки. Затем, чтобы зарегистрировать его и отправить события, используйтеEventBus isntance:

public class Aggregator {
    private EventBus eventBus = new EventBus();

    public void register(Object obj) {
        eventBus.register(obj);
    }

    public void dispatch(News news) {
        eventBus.dispatch(news);
    }
}

Это автоматически найдет методы, которые принимаютnews Возьмите и отправьте за вас. Вы даже можете подписаться более одного раза в одном классе:

public class MySubscriber {
    // ...

    @Subscribe
    public void anyMethodNameYouWant(MyNews news) {
        // Handle news
    }

    @Subscribe
    public void anEntirelyDifferentMethod(MyNews news) {
        // Handle news
    }
}

Или для нескольких типов в пределах одного подписчика:

public class MySubscriber {
    // ...

    @Subscribe
    public void handleNews(MyNews news) {
        // Handle news
    }

    @Subscribe
    public void handleNews(YourNews news) {
        // Handle news
    }
}

И, наконец,EventBus уважает иерархические структуры, поэтому, если у вас есть класс, который расширяетMyNews, такие какMyExtendedNews, то отправкаMyExtendedNews мероприятия также будут переданы тем, кто заботится оMyNews Мероприятия. То же самое касается интерфейсов. Таким образом, вы даже можете создать глобального подписчика:

public class GlobalSubscriber {
    // ...

    @Subscribe
    public void handleAllTheThings(News news) {
        // Handle news
    }
}
 Raffaele23 окт. 2012 г., 23:42
Это для проекта Android, и Guava сделает пакет слишком большим. В любом случае +1. Всего пара моментов: 1.getSupportedNewsType() не требуется, поскольку агрегатор может легко извлекать сам параметр типа программно, поэтому нет необходимости переопределять его у каждого подписчика 2. Поскольку я не могу использоватьMultimapМне нужно найти способ получить соответствующийSet<Subscriber<N>> от моегоMap, Это то, что мой личный методgetSubscribersFor(Class) был для
 Brian23 окт. 2012 г., 23:46
Проще говоря, во время выполнения Java не заботится о типе подписчика. То, что вы хотите, это проверка во время компиляции. (Вы не получите подтверждение во время выполнения из-за удаления). Неважно, что вашgetSubscribersFor метод возвращает, потому что в конечном итоге это будет набор объектов, которые все имеютonNews метод, который принимает объекты. Пока ваши публичные подписи методов безопасны от типа, основной метод их хранения не имеет значения. Я обновлю свой ответ, чтобы отразить это немного лучше.
 Brian07 нояб. 2012 г., 20:43
@irreputableEventBus использует типы, доступные во время выполнения, а не во время компиляции, поэтомуvoid foo(T) было быvoid foo(Object) во время выполнения. Это значит, чтоEventBus не будет обращаться с этим какNumber, но, какObject.
 irreputable24 окт. 2012 г., 02:38
@ Брайан интересно. Насколько хорошо Guava обрабатывает дженерики? например,class Parent<T>{ @Subscribe void foo(T){...} } class Child extends Parent<Number>{}, сейчас зарегистрируйтесьnew Child(), а затем отправитьInteger, что происходит?
 Raffaele23 окт. 2012 г., 23:52
Я думаю, вы поняли. Весь смысл использования универсальных типов вместо необработанных типов везде состоит в том, чтобы гарантировать, что, если что-то не так, это дает сбой как можно скорее (то есть во время компиляции). В этом случае, кажется, я могу использовать только дженерики, чтобы сделатьSubscriber API выглядит лучше, потому что везде я использовал непроверенные приведения (потенциально небезопасные)

subscriber.getClass(), поClass.getGenericSuperclass/getGenericInterfaces()затем осмотрите их, чтобы извлечьN на самом деле, поParameterizedType.getActualTypeArguments()

Например

publ,ic class SportsLover implements Subscriber<SportsNews>
{
    void onNews(SportsNews news){ ... }
}

if subscriber is an instance of SportsLover

Class clazz = subscriber.getClass();   // SportsLover.class

// the super type: Subscriber<SportsNews>
Type superType = clazz.getGenericInterfaces()[0];  

// the type arg: SportsNews
Type typeN = ((ParameterizedType)superType).getgetActualTypeArguments()[0];  

Class clazzN = (Class)typeN;  

Это работает для простых случаев.

Для более сложного случая нам понадобятся более сложные алгоритмы для типов.

 irreputable23 окт. 2012 г., 22:32
Вам нужно отражение, чтобы извлечь N для любого данного объекта подписчика. это то, что продемонстрировал мой кодclassN изsubscriber.
 irreputable23 окт. 2012 г., 22:18
вашей проблеме не нужен "токен типа"
 Raffaele23 окт. 2012 г., 22:28
Я не знаю, что нужно моей проблеме. Отражение? Дженерики? Полиморфизм? Я открытый, я бы просто хотелAggregator Скомпилировать. Ваше решение предлагает, как извлечь аргумент типа: это хорошо, но это не единственная проблема. Пробоватьсделай так, чтобы это работало
 Raffaele23 окт. 2012 г., 22:12
Да, я прочитал это вСтатья гафтера, Это не самая сложная часть, на данный момент. Можете ли вы рассказать об остальном?
 Raffaele23 окт. 2012 г., 22:36
ОК, но это только первый шаг к решению проблемы. Теперь мне нужно как-то держать карту междуClassэс иSet<Subscriber>S, и динамически положить и получитьSubscriber в утвержденном виде в безопасном виде:это это настоящая проблема. Проверьте предоставленную ссылку

Способ можно было бы использоватьTypeToken держатьN

 Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>(){}.getType() 

Чтобы ваш код работал

Объявите свой класс как

public static class Aggregator<N extends News>

Изменить подпись метода на

 private Set<Subscriber<N>> getSubscribersFor() {

И вы сделали.

 Raffaele23 окт. 2012 г., 22:29
Это не проблема. Мне не нужен внешний класс, потому что я уже знаю, что реальные подписчики реализуют универсальный интерфейс и могут использовать его для извлечения типа. Чтобы ответить на вопрос, нужно сделатьэто работай
 Amit Deshpande23 окт. 2012 г., 22:37
@Raffaele Проверьте обновление
 Raffaele23 окт. 2012 г., 22:45
Aggregator не должен быть параметризован, это должен быть контейнер разнородных новостей. Альтернатива (может быть, я забыл упомянуть об этом) делаетstatic метод в каждомNews подтип для возврата параметризованного агрегатора, но все же нет способа обеспечить это на уровне языка - я имею в виду,News должен объявить что-то вродеabstract static Aggregator<this.class> getAggregator(), что в Java запрещено несколькими способами

Выбудем нужно отправитьclass параметр дляdispatch, Следующие компиляции для меня, не уверен, что это отвечает вашим потребностям:

import java.util.Set;

interface News {
    String getHeader();
    String getText();
}

interface SportsNews extends News {}

interface Subscriber<N extends News> {
    void onNews(N news);
}


class Aggregator {

    public <N extends News> void subscribe(Subscriber<N> subscriber, Class<N> clazz) {
        // TODO somehow (super type token) extract N and 
        // add the item to the set retrieved by getSubscribersFor()
    }

    public <N extends News> void dispatch(N item, Class<N> k) {
        Set<Subscriber<N>> l = getSubscribersFor(k);
        for (Subscriber<N> s : l) {
            s.onNews(item);
        }
    }

    private <N extends News> Set<Subscriber<N>> getSubscribersFor(Class<N> k) {
        return null;
        // TODO retrieve the Set for the specified key from the Map
    }
}
 Raffaele24 окт. 2012 г., 00:26
Понятно - тогда пропущены вещи спецификации, это моя вина :) этот код предназначен для использования в простом проекте, поэтому можно предположить, что этот элементне может быть подтипомN, Однако этот ответ подчеркивает, что некоторые тесты, которые я написал, действительно испортили, поэтому +1
 Raffaele23 окт. 2012 г., 23:48
Я не понимаю этого.Class<N> аргументы вообще не нужны: вsubscribe Вы можете извлечь аргумент черезParameterizedType, И вdispatch вы просто бросилиClass<N> объект, возвращенныйitem.getClass(), Я что-то пропустил?
 Miserable Variable24 окт. 2012 г., 00:31
Рад, что это было полезно, я тоже узнал кое-что новое о дженериках, но я не совсем уверен, что :)
 Miserable Variable24 окт. 2012 г., 00:16
Я не уверен, что я что-то упустил тоже :) но тот факт, чтоitem может бытьподтип изN означает, что вы не можете просто привести его кClass<N>

Ваш ответ на вопрос