Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/349.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
避免对事件发布服务器的Java通用接口集合进行未经检查的强制转换_Java_Generics - Fatal编程技术网

避免对事件发布服务器的Java通用接口集合进行未经检查的强制转换

避免对事件发布服务器的Java通用接口集合进行未经检查的强制转换,java,generics,Java,Generics,我正在尝试为我正在构建的Android应用程序创建一个轻量级、线程安全的应用程序内发布/订阅机制。我的基本方法是跟踪每个事件类型T的IEventSubscriber列表,然后通过传递类型T的有效负载将事件发布到订阅对象 我使用泛型方法参数(我认为)确保以类型安全的方式创建订阅。因此,我非常确定,当我从订阅地图中获取订阅列表时,当发布事件时,我可以将其强制转换为IEventSubscriber列表,但是,这会生成未经检查的强制转换警告 我的问题是: 未经检查的演员在这里真的安全吗 我如何实际检查订

我正在尝试为我正在构建的Android应用程序创建一个轻量级、线程安全的应用程序内发布/订阅机制。我的基本方法是跟踪每个事件类型T的
IEventSubscriber
列表,然后通过传递类型T的有效负载将事件发布到订阅对象

我使用泛型方法参数(我认为)确保以类型安全的方式创建订阅。因此,我非常确定,当我从订阅地图中获取订阅列表时,当发布事件时,我可以将其强制转换为
IEventSubscriber
列表,但是,这会生成未经检查的强制转换警告

我的问题是:

  • 未经检查的演员在这里真的安全吗
  • 我如何实际检查订户列表中的项目是否实现了
    IEventSubscriber
  • 假设(2)涉及一些令人讨厌的反思,你会在这里做什么
  • 代码(Java 1.6):

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