Java泛型通配符问题

15

在使用Google Guava的优秀Multimap时,我遇到了一些与泛型有关的问题。我定义了一个类型处理程序Handler如下:

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

在另一个类中,我定义了一个multimap,将一个字符串映射到一组处理程序。

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

现在当我尝试使用multimap来做一些事情时,我遇到了编译器错误。我的第一次尝试看起来像这样:

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

这导致了以下错误:

类型不匹配:无法将 Collection<Handler<? extends Serializable>> 转换为 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); 
    }
}

很不幸,这也失败了:

类型Handler<capture#1-of ? extends Serializable>中的方法handle(capture#1-of ? extends Serializable)对于参数(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来处理这个问题了?

我试图简化我的问题,但我意识到我完全混淆了multimap的意图。我的处理程序的类型参数实际上不是扩展自Serializable,而是扩展自Event接口,而multimap的键实际上不是String,而是Class<T>,即事件类型和可以处理该特定事件的处理程序之间存在关联(可能会有多个处理程序每个事件类型)。包含multimap的类是事件总线。事件处理程序可以订阅总线并在发生某个事件时得到通知。 - Tom
如果您仍未获得解决方案,我建议您编辑您的问题,以便其他人可以帮助您。 - Vincent Cantin
谢谢,文森特。我刚刚做了那件事。 - Tom
4个回答

2
问题在于类型可能不同:
private Multimap<String, Handler<? extends Serializable>> multimap = 
ArrayListMultimap.create();

由于您不知道?实际代表什么,因此无法向multimap中添加任何内容。例如,您可能有一个Multimap<String, Handler<String>>,并尝试添加一个Integer,因为两者都实现了Serializable接口。
编辑:实际上,以上段落略有错误。您应该能够向multimap中添加处理程序,但由于处理程序的类型参数未知,您将无法使用这些处理程序,请参见下文。
doStuff1方法中,您定义了一个具体的参数T,它可能完全不同。因此,编译器无法确定此赋值是否正确:Collection<Handler<T>> collection = multimap.get(s);T确实是从multimap获取的处理程序的类型吗?-编译器不知道)。
您的第二种方法确实获得了正确的赋值,但是handle()方法将无法工作,因为您传递了一个Serializable,它可以是任何东西(StringInteger或其他)。编译器仍然不知道处理程序的类型是否匹配(想象一下它是一个Handler<Number>,而您将一个String传递给doStuff2)。
您有几个选择来解决这个问题,每种选择都有自己的缺点:
  1. 只使用Multimap<String, Handler<Serializable>>,这将允许您向处理程序传递任何Serializable对象
  2. 使用具体类型,例如Multimap<String, Handler<String>>,这将限制您仅能使用字符串处理程序
  3. 在运行时获取处理程序的类型参数并进行转换,如果不正确可能会出错

很抱歉,我没有正确地解释我的问题(请参见我上面的评论)。是我的错。 - Tom
那么,在这种情况下,您已经知道处理程序的类型参数(它应该能够处理用作键的类的所有事件),因此您可以使用选项3并相应地转换处理程序(例如,转换为Handler <Serializable>或通过仅转换为Handler来禁用泛型)。 - Thomas
谢谢你的回答,Thomas。我已经知道通过抑制警告来解决这个问题的方法了。我希望有一种更优雅的解决方案。 - Tom

2

如果您定义以下内容,它将更好地工作:

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

更新:解释你的问题。

当你遇到像这样的问题时,...

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

这意味着multimap可以接受任何Handler<N>,其中N Extends Serializable。假设它将包含类型为Handler<Foo>Handler<Bar>的值。FooBar是不相关的,彼此也没有扩展关系。

当您想要使用一种类型来表示Handler<? extends Serializable>的所有可能值的类型时,您正在尝试表达一种既是Foo又是Bar的类型,但这样的类型不存在。

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


  1. 这应该是一条注释。
  2. 那是完全不同的类型(其中您可以放置不同类的对象,只要它们都是Serializable)。
- aioobe
关于第二点): 在我看来,那具有相同的目的/效果。 - Vincent Cantin
1
List<? extends Number> 可以是 List<Long>List<Double>,即你可以将仅限于 Long 的元素放入其中,或者将仅限于 Double 的元素放入其中... 一个 List<Number> 可以包含混合的 LongDouble 元素。 - aioobe
试一下这个,你就会看到帖子作者的问题:List<? extends Number> list = new ArrayList<Number>(); list.add(new Integer(5)); - Vincent Cantin
很抱歉,我没有正确地解释我的问题(请参见我上面的评论)。是我的错。 - Tom

1

有两个部分的答案。首先,您的方法“消耗”对象,因此无法使用“extends”... 您需要使用“super”(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);
    }
}

这样,您可以定义一个从字符串到处理程序的多重映射,该处理程序至少消耗可序列化对象。

其次,我经常使用类似于您的结构:

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;

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

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


0

我稍微修改了你更新的代码。 现在它可以不用 @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 - Tom
我已经添加了程序的输出。我不明白为什么和在哪里需要将s转换为MyEvent。你能提供一些代码来解释你的需求吗? - MockerTim
“MyEvent” 显然包含了一些数据,你想在 handle 方法中访问这些数据。如果参数的类型为 <S extends Event>,你会如何做到这一点? - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接