【问题标题】:Java Generics Wildcards QuestionJava泛型通配符问题
【发布时间】:2011-05-14 06:02:28
【问题描述】:

在使用 Google Guava 出色的 Multimap 时,我遇到了一些泛型问题。我有一个这样定义的类型处理程序

public interface Handler<T extends Serializable> {
    void handle(T t);
} 

在另一个类中,我定义了一个将字符串映射到处理程序集合的多映射。

private Multimap<String, Handler<? extends Serializable>> multimap = 
    ArrayListMultimap.create();

现在,当我尝试使用多图进行操作时,我遇到了编译器错误。我的第一次尝试是这样的:

public <T extends Serializable> void doStuff1(String s, T t)  {
    Collection<Handler<T>> collection = multimap.get(s);
    for (Handler<T> handler : collection) {
        handler.handle(t);
    }
}

导致以下错误。

Type mismatch: cannot convert from Collection<Handler<? extends Serializable>> to Collection<Handler<T>>

后来,我试着这样编码

public void doStuff2(String s, Serializable serializable)  {
    Collection<Handler<? extends Serializable>> collection = multimap.get(s);
    for (Handler<? extends Serializable> handler : collection) {
        handler.handle(serializable); 
    }
}

不幸的是也失败了:

The method handle(capture#1-of ? extends Serializable) in the type Handler<capture#1-of ? extends Serializable> is not applicable for the arguments (Serializable)

任何帮助将不胜感激。谢谢。

更新:

我设法解决此问题的唯一方法是禁止编译器警告。给定以下处理程序:

public interface Handler<T extends Event> {
    void handle(T t);

    Class<T> getType();
}

我可以这样写事件总线。

public class EventBus {

    private Multimap<Class<?>, Handler<?>> multimap = ArrayListMultimap.create();

    public <T extends Event> void subscribe(Handler<T> handler) {
        multimap.put(handler.getType(), handler);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void publish(Event event)  {
        Collection<Handler<?>> collection = multimap.get(event.getClass());
        for (Handler handler : collection) {
            handler.handle(event);
        }
    }
}

我想没有办法用更少甚至不用@SuppressWarnings 来处理这个问题?

【问题讨论】:

  • 我试图简化我的问题,但我刚刚意识到我完全掩盖了多图的意图。我的处理程序的类型参数实际上并不是从 Serializable 扩展而来,而是从 Event 接口扩展而来,并且 multimap 的键实际上不是 String,而是 Class,即。 e.事件类型与可以处理该特定事件的处理程序之间存在相关性。 (每种事件类型可能有多个处理程序)。包含多图的类是事件总线。事件处理程序可以订阅总线并在某个事件发生时得到通知。
  • 如果您仍然没有为您找到解决方案,那么我建议您编辑您的问题,以便人们可以帮助您。
  • 谢谢,文森特。我就是这么做的。

标签: java generics guava


【解决方案1】:

问题是类型可能不同:

private Multimap<String, Handler<? extends Serializable>> multimap = 
ArrayListMultimap.create();

不允许您向多图添加任何内容,因为您不知道 ? 实际代表什么。例如,您可以有一个Multimap&lt;String, Handler&lt;String&gt;&gt; 并尝试添加一个Integer,因为两者都实现了Serializable

编辑:实际上上面的段落略有错误。您应该能够将处理程序添加到多图,但由于处理程序的类型参数未知,您将无法使用处理程序,见下文。

在您的doStuff1 方法中,您定义了一个具体参数T,这可能是完全不同的东西。因此编译器无法确定此分配是否正确:Collection&lt;Handler&lt;T&gt;&gt; collection = multimap.get(s);T 真的是您从多映射中获得的处理程序的类型吗?- 编译器不知道)。

您的第二种方法确实可以正确分配,但是 handle() 方法不起作用,因为您传入了 Serializable 可以是任何东西(StringInteger,其他东西)和编译器仍然不知道处理程序的类型是否匹配(假设它是 Handler&lt;Number&gt;,而您将 String 传递给 doStuff2)。

你有几种方法可以解决这个问题,每种方法都有自己的缺点:

  1. 只需使用Multimap&lt;String, Handler&lt;Serializable&gt;&gt;,这将允许您将任何Serializable 对象传递给处理程序
  2. 使用具体类型,例如Multimap&lt;String, Handler&lt;String&gt;&gt;,这将限制您仅使用字符串处理程序
  3. 在运行时获取处理程序的类型参数并进行强制转换,如果没有正确可能容易出错

【讨论】:

  • 恐怕我没有正确解释我的问题(见我上面的评论)。我的错。
  • 好吧,在这种情况下,您已经知道处理程序的类型参数(它应该能够处理用作键的类的所有事件),因此您可以使用选项 3 并强制转换相应的处理程序(例如,强制转换为 Handler&lt;Serializable&gt; 或仅通过强制转换为 Handler 来禁用该情况下的泛型)。
  • 谢谢你的回答,托马斯。我已经知道一种通过抑制警告来解决这个问题的方法。我希望有一个更优雅的解决方案。
【解决方案2】:

如果你定义它会更好:

private Multimap<String, Handler<Serializable>> multimap = 
    ArrayListMultimap.create();

更新:您的问题的解释。

当你有类似..的东西时

private Multimap<String, Handler<? extends Serializable>> multimap;

这意味着多图可以接受任何Handler&lt;N&gt;,其中N Extends Serializable。假设它将包含一个Handler&lt;Foo&gt; 类型的值和一个Handler&lt;Bar&gt; 类型的值。 FooBar 不相关,不相互延伸。

当你想在你的函数中使用一个类型来表示Handler&lt;? extends Serializable&gt;的所有可能值的类型时,你试图表达一个同时是FooBar的类型,但是有不是这样的类型。

这解释了您的编译器问题。现在删除这个“-1”,如果您认为我是正确的,请为我的答案投票。

【讨论】:

  • 1) 这应该是一个评论,2) 那是一种完全不同的类型(你可以在其中放置 不同 类的对象,只要它们都是 @ 987654332@.
  • 关于 2):对我来说,这是相同的目的/效果。
  • List&lt;? extends Number&gt; 可以是List&lt;Long&gt;List&lt;Double&gt;,即您可以将Longs(仅)放入其中,也可以将Doubles(仅)放入其中... List&lt;Number&gt; 可以包含 Longs 和 Doubles 的混合。
  • 试试这个,你会看到帖子作者的问题:List 列表 = 新 ArrayList(); list.add(new Integer(5));
  • 恐怕我没有正确解释我的问题(见我上面的评论)。我的错。
【解决方案3】:

两部分答案。首先,你的方法“消费”对象,所以你不能使用“扩展”......你需要使用“超级”(PECS:生产者扩展,消费者超级!)。

在不改变你的处理程序的情况下,这对我来说编译时没有警告:

private Multimap<String, Handler<? super Serializable>> multimap = ArrayListMultimap.create();

public void doStuff1(String s, Serializable t) {
    Collection<Handler<? super Serializable>> collection = multimap.get(s);
    for (Handler<? super Serializable> handler : collection) {
        handler.handle(t);
    }
}

通过这种方式,您可以定义一个从字符串到至少消耗 Serializable 的处理程序的多映射。

其次,我经常使用类似于您的构造的东西:

Map<Class<?>, Handler<?>> 

并且是返回的处理程序是 Class 的消费者的地方。主要问题是当您了解更多时,您无法“添加到泛型类型”...如果需要,始终声明一个新变量可以将@SuppressWarning 放在声明中:

@SuppressWarnings("unchecked") 
Handler<String> myHandler = (Handler<String>) getHandler();

这仅在您转换 整个 泛型类型时才有效。如果给你 Handler 并且你知道你拥有的是真正的 Handler>,那么你可以转换它的唯一方法是通过原始类型:

Handler<List> myLessSpecifiedHandler = getHandler();
@SuppressWarnings("unchecked") 
Handler<List<String>> myHandler = (Handler<List<String>>) (Handler) myLessSpecifiedHandler;

如果你不这样做,你会得到一个错误而不是一个警告......

是的,泛型有点混乱.. :-/

【讨论】:

    【解决方案4】:

    我稍微修改了您更新的代码。 现在它可以在没有 @SuppressWarnings 的情况下工作。

    当然,我做了一些假设。

    但我希望它会有所帮助。

    public interface Handler<T extends Event> {
        //void handle(T t);
        // It seems you won't need dependency of T in this method implementation.
        // So, you can use generic method here.
        <S extends Event> void handle(S s);
    
        Class<T> getType();
    }
    

    修改了 EventBus。

    public class EventBus {
        //private Multimap<Class<?>, Handler<?>> multimap = ArrayListMultimap.create();
        // It seems you don't need anything except Event here.
        // So. Logicaly more correct to use the bounded wildcard.
        private Multimap<Class<? extends Event>, Handler<? extends Event>> 
            multimap = ArrayListMultimap.create();
    
        //public <T extends Event> void subscribe(Handler<T> handler) {
        // You don't need to use generic method here.
        // Because T is never used in method implementation.
        // Wildcard fits simpler.
        public void subscribe(Handler<? extends Event> handler) {
            multimap.put(handler.getType(), handler);
        }
    
        //@SuppressWarnings({ "rawtypes", "unchecked" })
        public void publish(Event event)  {
            //Collection<Handler<?>> collection = multimap.get(event.getClass());
            // The bounded wildcard again.
            Collection<Handler<? extends Event>> 
                collection = multimap.get(event.getClass());
            //for (Handler handler : collection) {
            for (Handler<? extends Event> handler : collection) {
                handler.handle(event);
            }
        }
    }
    

    还有一些代码,只是为了完成示例。

    public class Main {
    
        public static void main(String[] args) {
            EventBus bus = new EventBus();
    
            bus.subscribe(new Handler<MyEvent> () {
    
                public <S extends Event> void handle(S s) {
                    System.out.println(s);
                }
    
                public Class<MyEvent> getType() {
                    return MyEvent.class;
                }
            });
    
            bus.publish(new MyEvent());
        }
    }
    
    class MyEvent implements Event {
    
    // Event implementation ...
    
    }
    

    程序输出如下所示:

    MyEvent@12276af2
    

    【讨论】:

    • 您的解决方案的问题是我需要在处理程序实现中将s 转换为MyEvent
    • 我已经添加了程序的输出。我不明白为什么以及在哪里需要将s 转换为MyEvent。你能提供一些代码来解释你的需求吗?
    • MyEvent 显然包含一些您希望在句柄方法中访问的数据。如果参数的类型为 ,你会怎么做?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    • 2017-02-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多