Java 事件系统通用转换为有界通配符

问题描述 投票:0回答:2

我正在制作自己的事件系统并尝试使其像这样可用。

EventSystem.get(EventClientStart.class)
    .setCallback((event) -> {
        System.out.println("Client Started");
    });

EventSystem.get(EventClientStart.class)
    .invoke(new EventClientStart());

但是类型检查有问题,我错过了什么? T 扩展了 Event,我认为它应该可以工作,而且实际上是这样(仅当我将其强制转换为 (EventController) 时,但随后我会收到警告“Unchecked Cast”)

EventSystem.java

package im.silkproject.event;

import im.silkproject.event.internal.EventController;

import java.util.HashMap;
import java.util.Map;

public final class EventSystem
{
    private static final Map<Class<? extends Event>, EventController<? extends Event>> map = new HashMap<>();

    private EventSystem() { }

    public static <T extends Event> EventController<T> get(Class<T> event)
    {
        return map.computeIfAbsent(event, k -> new EventController<>());
    }
}

事件.java

package im.silkproject.event;

public class Event
{
    private boolean cancelled;

    public void cancel()
    {
        cancelled = true;
    }

    public boolean isCancelled()
    {
        return cancelled;
    }
}

EventCallback.java

package im.silkproject.event.internal;

@FunctionalInterface
public interface EventCallback<T>
{
    void __call(T event);
}

EventController.java

package im.silkproject.event.internal;

import java.util.concurrent.CopyOnWriteArrayList;

public class EventController<T>
{
    private final CopyOnWriteArrayList<EventCallback<T>> callbacks = new CopyOnWriteArrayList<>();

    public void invoke(T event)
    {
        for (EventCallback<T> callback : callbacks)
        {
            callback.__call(event);
        }
    }

    public int length()
    {
        return callbacks.size();
    }

    public boolean setCallback(EventCallback<T> event)
    {
        return callbacks.addIfAbsent(event);
    }

    public boolean unsetCallback(EventCallback<T> event)
    {
        return callbacks.remove(event);
    }
}
java dictionary generics events hashmap
2个回答
0
投票

我认为它与Java中的

Covariance
有关。如果您使用
List
,则只能从
Map
Upper Bounded Wildcards
读取。 您可以在此答案中找到更多详细信息这里


0
投票

即使在您的方法中您已声明

T
Event
的子类型并且返回类型为
EventController<T>
,您还是返回了 Maps 的值,该值对应于
EventController<? extends Event>>
。书写
EventController<? extends Event>>
意味着这些值可以是事件的任何 unknown 子类型。

在某些时候,

T
将指代Event的非常
特定
子类型,而
? extends Event
可以是事件的任何子类型,并且不一定对应于
T
。这就是您收到编译器错误的原因。

如果您想消除该错误,方法的返回类型应该与 Map 值的类型匹配。

public static <T extends Event> EventController<? extends Event> get(Class<T> event) {
     return map.computeIfAbsent(event, k -> new EventController<>());
}

这里还有一篇来自 Java 教程 的非常有趣的文章,介绍了通配符并通过示例解释了您的具体情况。我将在此处附上摘录并突出显示适用于您的案例的部分:

public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}

现在,类型规则规定

drawAll()
只能在恰好为
Shape
的列表上调用:例如,它不能在
List<Circle>
上调用。这是不幸的,因为该方法所做的只是从列表中读取形状,所以它也可以在
List<Circle>
上调用。我们真正想要的是该方法接受任何形状的列表:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

这里有一个很小但非常重要的区别:我们用

List<Shape>
替换了
List<? extends Shape>
类型。现在
drawAll()
将接受
Shape
的任何子类的列表,因此如果我们愿意,我们现在可以在
List<Circle>
上调用它。

List<? extends Shape>
是有界通配符的示例。这 ?代表未知类型,就像我们之前看到的通配符一样。然而,在这种情况下,我们知道这种未知类型实际上是
Shape
的子类型。 (注意:它可以是
Shape
本身,也可以是某个子类;它不需要字面上扩展
Shape
。)我们说
Shape
是通配符的上限。

与往常一样,使用通配符的灵活性是要付出代价的。这个代价是现在在方法主体中写入形状是非法的。例如,这是不允许的:

public void addRectangle(List<? extends Shape> shapes) {
    // Compile-time error!
    shapes.add(0, new Rectangle());
}

您应该能够弄清楚为什么上面的代码被禁止。

shapes.add()
的第二个参数的类型是
? extends Shape
——
Shape
的未知子类型。 因为我们不知道它是什么类型,所以我们也不知道它是否是
Rectangle
的超类型;它可能是也可能不是这样的超类型,因此在那里传递
Rectangle
是不安全的。

© www.soinside.com 2019 - 2024. All rights reserved.