Raf*_*ele 6 java generics reflection
我有这个基本的News
界面
interface News {
String getHeader();
String getText();
}
Run Code Online (Sandbox Code Playgroud)
和具体类一样,SportsNews
并FinancialNews
提供特定的方法,如getStockPrice()
,getSport()
等等.新闻旨在发送给
interface Subscriber<N extends News> {
void onNews(N news);
}
Run Code Online (Sandbox Code Playgroud)
问题是如何注册和维护订阅.我尝试的第一种方法是使用中心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
}
}
Run Code Online (Sandbox Code Playgroud)
有什么替代品可以安全吗?Java可以解决这个问题吗?我把这个小小的演示放在网上,以帮助你更好地理解问题的真正含义.
UPDATE
另一种方法是使Aggregator
自己用实际新闻类型进行参数化.这没关系,除了它是一个鸡和蛋的问题:现在需要找到一种方法来检索聚合器.在Java中,没有办法表达以下内容
interface News {
static Aggregator<CurrentClass> getAggregator();
}
Run Code Online (Sandbox Code Playgroud)
static
方法不能 abstract
这就是我要做的。如果您可以使用 Guava(由 Google 编写和使用的 Google 库),我建议您向下滚动并首先查看其他解决方案。
首先,首先添加一个从订阅者处获取类的方法:
public interface Subscriber<N extends News> {
void onNews(N news);
Class<N> getSupportedNewsType();
}
Run Code Online (Sandbox Code Playgroud)
然后在实施时:
public class MySubscriber implements Subscriber<MyNews> {
// ...
public Class<MyNews> getSupportedNewsType() {
return MyNews.class;
}
}
Run Code Online (Sandbox Code Playgroud)
在聚合器中,包含一个未键入键和值的映射:
private Map<Class<?>, Set<Subscriber<?>> subscribersByClass = ... ;
Run Code Online (Sandbox Code Playgroud)
另请注意,Guava 有一个多重映射实现,可以为您执行此键到多个值的操作。只要谷歌“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);
}
Run Code Online (Sandbox Code Playgroud)
并发送:
@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);
}
}
Run Code Online (Sandbox Code Playgroud)
注意这里的演员阵容。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;
}
Run Code Online (Sandbox Code Playgroud)
您的private
方法和字段不需要类型安全。无论如何,它不会造成任何问题,因为 Java 的泛型是通过擦除实现的,所以这里的所有集合无论如何都只是一组对象。试图使它们类型安全只会导致令人讨厌的、不必要的强制转换,而这与其正确性无关。
重要的是你的public
方法是类型安全的。泛型的声明方式Subscriber
和公共方法的方式Aggregator
,打破它的唯一方法是通过原始类型,就像我上面所说的那样。简而言之,只要不存在不安全的强制转换或原始类型,每个Subscriber
传递给寄存器的都保证接受您正在注册的类型。
或者,您可以看看 Guava 的EventBus
。IMO,对于您想要做的事情来说,这会更容易。
Guava 的EventBus
类使用注释驱动的事件调度而不是接口驱动。这真的很简单。您将不再有Subscriber
界面。相反,您的实现将如下所示:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
}
Run Code Online (Sandbox Code Playgroud)
该@Subscribe
注释向 Guava 发出信号EventBus
,表明它应该记住该方法以便稍后进行调度。然后要注册它并分派事件,请使用EventBus
实例:
public class Aggregator {
private EventBus eventBus = new EventBus();
public void register(Object obj) {
eventBus.register(obj);
}
public void dispatch(News news) {
eventBus.dispatch(news);
}
}
Run Code Online (Sandbox Code Playgroud)
这将自动找到接受该对象的方法news
并为您进行调度。您甚至可以在同一课程中多次订阅:
public class MySubscriber {
// ...
@Subscribe
public void anyMethodNameYouWant(MyNews news) {
// Handle news
}
@Subscribe
public void anEntirelyDifferentMethod(MyNews news) {
// Handle news
}
}
Run Code Online (Sandbox Code Playgroud)
或者对于同一订阅者内的多种类型:
public class MySubscriber {
// ...
@Subscribe
public void handleNews(MyNews news) {
// Handle news
}
@Subscribe
public void handleNews(YourNews news) {
// Handle news
}
}
Run Code Online (Sandbox Code Playgroud)
最后,EventBus
尊重层次结构,因此如果您有一个扩展的类MyNews
,例如MyExtendedNews
,那么调度MyExtendedNews
事件也将传递给那些关心MyNews
事件的类。接口也是如此。通过这种方式,你甚至可以创建一个全局订阅者:
public class GlobalSubscriber {
// ...
@Subscribe
public void handleAllTheThings(News news) {
// Handle news
}
}
Run Code Online (Sandbox Code Playgroud)