【问题标题】:How to use collections and generics with wildcards?如何使用带有通配符的集合和泛型?
【发布时间】:2012-10-31 07:14:56
【问题描述】:

首先我将尝试解释这段代码背后的想法。 我有一堆类(Processors)可以处理某种类型的其他类(Processables)。我有一个处理器列表,可以按特定顺序执行它们。我有一个地图,它将检索我要为某个处理器处理(Processables)的数据。 它看起来像这样。

public abstract class AbstractProcessable {
    ...
}

public class DummyProcessable extends AbstractProcessable {
   ...
}

public abstract class AbstractProcessor<T extends AbstractProcessable> {
    public abstract void process(List<T> listOfProcessables);
}

public class DummyProcessor extends AbstractProcessor<DummyProcessable> {

    @Override
    public void process(List<DummyProcessable> listToProcess) {
        ...
    }
}

到目前为止,这似乎工作正常。没有编译错误。但现在我有一个类似如下的类:

public class RandomClass {

    private List<AbstractProcessor<? extends AbstractProcessable>> processors;

    private Map<Class<? extends AbstractProcessor>, List<? extends AbstractProcessable>> data;

    public RandomClass() {
        processors = new ArrayList<>();
        processors.add(new DummyProcessor());

        data = new HashMap<>();
        data.put(DummyProcessor.class, new ArrayList<DummyProcessable>());

        processAll();
    }

    private void processAll() {
        for (AbstractProcessor<? extends AbstractProcessable> processor : processors) {
            List<? extends AbstractProcessable> dataToProcess;
            dataToProcess = data.get(processor);
            processor.process(dataToProcess); // compile error
        }
    }
}

编译错误:

    The method process(List<capture#4-of ? extends AbstractProcessable>) in the type AbstractProcessor<capture#4-of ? extends AbstractProcessable> is not applicable for the arguments (List<capture#5-of ? extends AbstractProcessable>)

我知道它可能有点难以阅读,但我尽量简化它。我对泛型也不是很好,所以也许我用错了一些通配符? 谁能帮我解决这个问题?

提前非常感谢!

【问题讨论】:

  • 请发布你得到的完整错误,以便详细检查
  • 能否在派生类的process()方法中添加“@Override”注解,看看编译器是否报错?
  • 添加了覆盖。覆盖一直在那里,我只是在这里忘记了。此外,错误消息现在是代码,因为它已被更改...

标签: java generics collections wildcard


【解决方案1】:

我怀疑这里的问题是:

  1. ? extends AbstractProcessable 来自 private List&lt;AbstractProcessor&lt;? extends AbstractProcessable&gt;&gt; processors;
  2. ? extends AbstractProcessable 来自List&lt;? extends AbstractProcessable&gt; dataToProcess;

不是同一类型。 ? extends AbstractProcessable 表示 AbstractProcessable 的未知子类,没有什么强制这两个未知数应该相同(这就是为什么会出现错误,因为未知数:capture#4-of ? 与未知数:capture#5-of ? 不同)。 不确定RandomClass 的用途,但我会将其更改为&lt;T extends AbstractProcessable&gt; 并使用T 定义列表和地图。这样列表和地图将具有相同的类型。

【讨论】:

    【解决方案2】:

    如果我正确理解你的问题,我前段时间问过a similar one。简短的回答是您不能以您想要的方式使用这样的地图。好消息是你不需要它;)

    问题在于 Java 泛型是编译时的东西(您可能已经知道),它们只存在于编译时,而当您实际填写 List 时,不是。你找不到用 Java 表达你的想法的方法,即使它看起来完全合法。

    您可能会发现Super Type Tokens 在某些情况下可用于提取参数,并避免用户在合适的情况下提供显式参数(不过请注意the limitations 并考虑它们是否真的可以增加一些价值)。

    简而言之,您最终会得到一个类似的字段

    private Map<Class<?>, List<?>> data;
    

    (或者您可以为此使用Guava's Multimap 实现),使用一些强制转换并喊出一些警告,并依靠您的程序逻辑来确保客户端代码的类型安全。我在我的代码中使用了这种方法,到目前为止它从未失败过。

    这是我的代码。我认为这正是您的情况,只需将 Subscriber&lt;T&gt;Message 替换为您的 Processor&lt;T&gt;Processable

    public class MessageBus {
    
        public enum Action {
            CREATE, REMOVE, UPDATE, DELETE;
        }
    
        private static Map<Class<?>, Set<Subscriber<?>>> subscriptions;
    
        static {
            subscriptions = new HashMap<Class<?>, Set<Subscriber<?>>>();
        }
    
        @SuppressWarnings("unchecked")
        public static <T> void publish(T message, Action action) {
            Set<Subscriber<?>> set = getSubscribersFor(message.getClass());
    
            if (set == null)
                return;
    
            for (Subscriber<?> subscriber: set) {
                ((Subscriber<T>) subscriber).onMessage(message, action);
            }
        }
    
        public static <T> void subscribe(Class<T> type, Subscriber<T> subscriber) {
            Set<Subscriber<?>> set = getSubscribersFor(type);
    
            if (set == null) {
                set = new HashSet<Subscriber<?>>();
                subscriptions.put(type, set);
            }
    
            set.add(subscriber);
        }
    
        public static <T> void unsuscribe(Class<T> type, Subscriber<T> subscriber) {
            Set<Subscriber<?>> set = getSubscribersFor(type);
            set.remove(subscriber);
        }
    
        private static Set<Subscriber<?>> getSubscribersFor(Class<?> topic) {
            return subscriptions.get(topic);
        }
    }
    

    【讨论】:

    • 好的,你似乎知道你在说什么,我会接受你的回答是正确的,但我错过了关于为什么这不起作用的解释.问题出在哪里,看不懂报错信息。
    • @noone this answer of mine 可能会帮助您理解错误消息,因为长答案并不适合评论。我想您知道 Java 类型系统是如何工作的:Integer 是接受 Object 的方法的有效参数,因为 ObjectInteger 之间存在子类型关系。泛型类型在类型参数上被称为不变,即List&lt;Integer&gt;不是List&lt;Object&gt; 的子类型。这是一个规则。 (继续)
    • @noone 查看我的答案以获得可能的、易于测试的解释。
    • (continue) 在您的代码中,您有两种 unknown 类型:unknown 意味着由于编译器无法在任何地方找到它们的定义,它甚至无法在基本类型。例如LongInteger 扩展Number。您可以声明 List&lt;? extends Number&gt; 并将引用分配给 List&lt;Integer&gt;,但您无法在此类列表中调用 add(T),因为没有告诉编译器任何 real 类型定义,它使用称为capture#N的虚构类型。
    【解决方案3】:

    对我来说,听起来您在其中导入了 2 个不同的 List 类。检查导入部分并确保

    import java.util.List
    

    在两者中(或您需要的任何列表类型)

    就像第一个猜测一样,没有详细检查您的代码:)

    【讨论】:

    • 这至少不是问题。虽然我希望它会这么容易!
    猜你喜欢
    • 2017-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-11
    • 1970-01-01
    • 2012-02-14
    相关资源
    最近更新 更多