避免对事件发布服务器的Java通用接口集合进行未经检查的强制转换
我正在尝试为我正在构建的Android应用程序创建一个轻量级、线程安全的应用程序内发布/订阅机制。我的基本方法是跟踪每个事件类型T的避免对事件发布服务器的Java通用接口集合进行未经检查的强制转换,java,generics,Java,Generics,我正在尝试为我正在构建的Android应用程序创建一个轻量级、线程安全的应用程序内发布/订阅机制。我的基本方法是跟踪每个事件类型T的IEventSubscriber列表,然后通过传递类型T的有效负载将事件发布到订阅对象 我使用泛型方法参数(我认为)确保以类型安全的方式创建订阅。因此,我非常确定,当我从订阅地图中获取订阅列表时,当发布事件时,我可以将其强制转换为IEventSubscriber列表,但是,这会生成未经检查的强制转换警告 我的问题是: 未经检查的演员在这里真的安全吗 我如何实际检查订
IEventSubscriber
列表,然后通过传递类型T的有效负载将事件发布到订阅对象
我使用泛型方法参数(我认为)确保以类型安全的方式创建订阅。因此,我非常确定,当我从订阅地图中获取订阅列表时,当发布事件时,我可以将其强制转换为IEventSubscriber
列表,但是,这会生成未经检查的强制转换警告
我的问题是:
IEventSubscriber
import java.util.concurrent.ConcurrentHashMap;
导入java.util.concurrent.ConcurrentMap;
导入java.util.concurrent.CopyOnWriteArraySet;
公共类事件管理器{
私有ConcurrentMap订阅=
新的ConcurrentHashMap();
公共布尔订阅(IEventSubscriber subscriber,
类事件(类){
CopyOnWriteArraySet existingSubscribers=订阅。
putIfAbsent(eventClass,新CopyOnWriteArraySet());
返回现有订阅服务器。添加(订阅服务器);
}
公共布尔removeSubscription(IEventSubscriber subscriber,
类事件(类){
CopyOnWriteArraySet现有订户=
获取(eventClass);
return existingSubscribers==null | |!existingSubscribers.remove(subscriber);
}
公共无效发布(T消息,类eventClass){
@抑制警告(“未选中”)
CopyOnWriteArraySet现有订户=
(CopyOnWriteArraySet)订阅。获取(eventClass);
if(existingSubscribers!=null){
对于(IEventSubscriber订户:现有订户){
订阅者。触发器(消息);
}
}
}
}
由于您的订阅
实现确保ConcurrentMap
中的每个类
键映射到正确的IEventSubscriber
,因此在发布
中从映射中检索时使用@SuppressWarnings(“未选中”)
是安全的
只需确保正确记录警告被抑制的原因,以便将来对类进行更改的任何开发人员都知道发生了什么
另见这些相关员额:
未经检查的演员在这里真的安全吗
完全正确。您的代码不会造成堆污染,因为subcribe的签名确保您只将适当编译时类型的IEventSubscriber放入映射中。它可能会在其他地方传播不安全的未经检查的铸件造成的堆污染,但对此您无能为力
我如何实际检查订户列表中的项目是否实现IEventSubscriber
通过将每个项目强制转换为IEventSubscriber
。您的代码已在以下行中执行此操作:
for (IEventSubscriber<T> subscriber: existingSubscribers) {
该代码明确检查每个项目是否为IEventSubscriber
,但不能检查它是否为IEventSubscriber
要实际检查IEventSubscriber
的类型参数,IEventSubscriber
需要帮助您。这是由于删除,特别是鉴于声明
class MyEventSubscriber<T> implements IEventSubscriber<T> { ... }
类MyEventSubscriber实现IEEventSubscriber{…}
以下表达式将始终为真:
new MyEventSubscriber<String>.getClass() == new MyEventSubscriber<Integer>.getClass()
new MyEventSubscriber.getClass()==new MyEventSubscriber.getClass()
假设(2)涉及一些令人讨厌的反思,你会在这里做什么
我会让代码保持原样。很容易推断强制转换是正确的,而且我觉得在没有警告的情况下重写它进行编译是不值得的。如果您确实希望重写,以下想法可能有用:
class SubscriberList<E> extends CopyOnWriteArrayList<E> {
final Class<E> eventClass;
public void trigger(Object event) {
E event = eventClass.cast(event);
for (IEventSubscriber<E> subscriber : this) {
subscriber.trigger(event);
}
}
}
class SubscriberList扩展了CopyOnWriteArrayList{
期末班;
公共无效触发器(对象事件){
E event=eventClass.cast(事件);
对于(IEventSubscriber订户:此){
订阅者。触发器(事件);
}
}
}
及
SubscriberList subscribers=(SubscriberList)subscriptions.get(eventClass);
触发器(消息);
不完全是这样。如果EventManager
类的所有客户端始终使用泛型,而从不使用rawtype,这将是安全的;i、 例如,如果您的客户端代码编译时没有与泛型相关的警告
但是,客户机代码不难忽略这些内容并插入预期类型错误的IEventSubscriber
:
EventManager manager = ...;
IEventSubscriber<Integer> integerSubscriber = ...; // subscriber expecting integers
// casting to a rawtype generates a warning, but will compile:
manager.subscribe((IEventSubscriber) integerSubscriber, String.class);
// the integer subscriber is now subscribed to string messages
// this will cause a ClassCastException when the integer subscriber tries to use "test" as an Integer:
manager.publish("test", String.class);
在上面的示例中,对于ClassA
,String
在编译时绑定到参数T
。ClassA
的所有实例将在IEventSubscriber
中为T
设置字符串。但是在ClassB
中,String
在运行时绑定到T
。ClassB
的实例可以具有T
的任何值。如果您的IEventSubscriber
实现在编译时与上面的ClassA
一样绑定参数T
,那么您可以通过以下方式在运行时获得该类型:
public <T> boolean subscribe(IEventSubscriber<T> subscriber, Class<T> eventClass) {
Class<? extends IEventSubscriber<T>> subscriberClass = subscriber.getClass();
// get generic interfaces implemented by subscriber class
for (Type type: subscriberClass.getGenericInterfaces()) {
ParameterizedType ptype = (ParameterizedType) type;
// is this interface IEventSubscriber?
if (IEventSubscriber.class.equals(ptype.getRawType())) {
// make sure T matches eventClass
if (!ptype.getActualTypeArguments()[0].equals(eventClass)) {
throw new ClassCastException("subscriber class does not match eventClass parameter");
}
}
}
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>());
return existingSubscribers.add(subscriber);
}
这避免了在每次方法调用时创建新的CopyOnWriteArraySet
,但是如果您有竞争条件,并且两个线程试图同时放入一个集合,putIfAbsent
仍会将创建的第一个集合返回给第二个线程,因此不存在覆盖它的危险。Java同时使用编译时和运行时类型检查。对于泛型,它相当依赖于编译时检查来确保
SubscriberList<?> subscribers = (SubscriberList<?>) subscriptions.get(eventClass);
subscribers.trigger(message);
EventManager manager = ...;
IEventSubscriber<Integer> integerSubscriber = ...; // subscriber expecting integers
// casting to a rawtype generates a warning, but will compile:
manager.subscribe((IEventSubscriber) integerSubscriber, String.class);
// the integer subscriber is now subscribed to string messages
// this will cause a ClassCastException when the integer subscriber tries to use "test" as an Integer:
manager.publish("test", String.class);
public class ClassA implements IEventSubscriber<String> { ... }
public class ClassB<T> implements IEventSubscriber<T> { ... }
IEventSubscriber<String> a = new ClassA();
IEventSubscriber<String> b = new ClassB<String>();
public <T> boolean subscribe(IEventSubscriber<T> subscriber, Class<T> eventClass) {
Class<? extends IEventSubscriber<T>> subscriberClass = subscriber.getClass();
// get generic interfaces implemented by subscriber class
for (Type type: subscriberClass.getGenericInterfaces()) {
ParameterizedType ptype = (ParameterizedType) type;
// is this interface IEventSubscriber?
if (IEventSubscriber.class.equals(ptype.getRawType())) {
// make sure T matches eventClass
if (!ptype.getActualTypeArguments()[0].equals(eventClass)) {
throw new ClassCastException("subscriber class does not match eventClass parameter");
}
}
}
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>());
return existingSubscribers.add(subscriber);
}
CopyOnWriteArraySet<IEventSubscriber> existingSubscribers = subscriptions.get(eventClass);
if (existingSubscribers == null) {
existingSubscribers = subscriptions.putIfAbsent(eventClass, new CopyOnWriteArraySet<IEventSubscriber>());
}