Can generics and (super?) Tokens create a built-in news aggregator?

I have this basic News interface

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

and specific classes such as SportsNews and FinancialNews to provide specific methods such as getStockPrice() , getSport() and so on. Newsletters are intended to be sent to

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

The problem is how to register and maintain subscriptions. The first approach I tried consisted of using a central Aggregator , preserving the map between Class<T> and Set<Subscriber<T>> objects, but soon this approach showed non-viability. Here is the required 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 } } 

Is there an alternative to the safe type? Can Java solve this problem at all? I put this small demo online version to help you better understand what the problem is.

UPDATE

An alternative would be to make the Aggregator itself a parameter with the actual type of news. This would be normal, except that this is a problem with the chicken and the egg: now you need to find a way to get the aggregator. There is no way in Java to express the following

 interface News { static Aggregator<CurrentClass> getAggregator(); } 
  • static method cannot be abstract
  • there is no way to refer to the current type in a type argument
+6
source share
4 answers

Here is what I will do. If you can use Guava (a Google library written and used by Google), I recommend that you first scroll and look at another solution.

Vanilla java

First, start by adding a method to get the class from your subscribers:

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

Then upon implementation:

 public class MySubscriber implements Subscriber<MyNews> { // ... public Class<MyNews> getSupportedNewsType() { return MyNews.class; } } 

In the aggregator, turn on the card in which the keys and values ​​are not entered:

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

Also note that Guava has a multi-mode implementation that will use this key for several things you will need. Just google "guava multimap" and you will find it.

To register a subscriber:

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

And send:

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

Pay attention to the cast here. This will be safe due to the nature of the generics between the register method and the Subscriber interface if no one does something ridiculously wrong, such as raw-typing, such as implements Subscriber (there is no common argument). Summary SuppressWarnings suppresses warnings about this cast from the compiler.

And your private method for extracting subscribers:

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

Your private methods and fields should not be type safe. In any case, this will not cause any problems, since Java generators are implemented using erasure, so all sets here will be just a set of objects. An attempt to make them safe types will only lead to unpleasant, unnecessary throws that have nothing to do with its correctness.

The important thing is that your public methods are type safe. The way to declare generics in Subscriber and the public methods on Aggregator , the only way to break it down is to use raw types, as I said above. In short, each Subscriber passed to the register is guaranteed to accept the types that you register it until unsafe discards or raw typing are detected.


Using Guava

Alternatively, you can take a look at the Guava EventBus . That would be easier, IMO, for what you are trying to do.

The Guava EventBus class uses annotation-dependent event dispatching instead of an interface driven one. It is very simple. You will no longer have the Subscriber interface. Instead, your implementation will look like this:

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

The @Subscribe annotation @Subscribe for the Guava EventBus that it must remember this method later for sending. Then, to register it and send events, use 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); } } 

This will automatically find the methods that accept the news object and send you a message. You can even subscribe more than once in the same class:

 public class MySubscriber { // ... @Subscribe public void anyMethodNameYouWant(MyNews news) { // Handle news } @Subscribe public void anEntirelyDifferentMethod(MyNews news) { // Handle news } } 

Or for several types inside the same subscriber:

 public class MySubscriber { // ... @Subscribe public void handleNews(MyNews news) { // Handle news } @Subscribe public void handleNews(YourNews news) { // Handle news } } 

Finally, the EventBus is a hierarchical structure, so if you have a class that extends MyNews , such as MyExtendedNews , then sending MyExtendedNews events MyExtendedNews also be sent to those who care about MyNews events. The same goes for interfaces. This way you can even create a global subscriber:

 public class GlobalSubscriber { // ... @Subscribe public void handleAllTheThings(News news) { // Handle news } } 
+4
source

You need to send the class parameter to dispatch . The following compilations are for me, not sure if this suits your needs:

 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 } } 
+1
source

You can get the super types subscriber.getClass() , Class.getGenericSuperclass/getGenericInterfaces() , and then check them to extract which N really is, ParameterizedType.getActualTypeArguments()

for instance

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

This works for simple cases.

For a more complex case, we need more complex type algorithms.

0
source

As a method, TypeToken can be used to store N

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

To make your code work

Declare your class as

 public static class Aggregator<N extends News> 

Change method signature to

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

And you're done.

0
source

All Articles