我正在制作我自己的事件系统,并试图使它像这样可用.

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

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

但我对类型的判断有问题,我错过了什么? T扩展了Event,我认为它应该工作,它实际上是(只有当我将它转换为(EventController),但然后我会得到警告"未判断的转换")

Intellij screenshot

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

♪Event.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);
    }
}

推荐答案

即使在你的方法中,你已经声明了TEvent的子类型,并且返回类型是EventController<T>,你返回的是一个Maps的值,而它对应于EventController<? extends Event>>.

在某些时候,T将指Eventspecific亚型,而? extends Event可以是事件的any未知亚型,并且不一定对应于T.在这种情况下,编译器无法在编译时判断? extends 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 Tutorials的非常有趣的文章,它介绍了通配符,并用一个例子解释了您的具体情况.我将在这里附上一个摘录,并强调适用于您的 case 的部分:

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<? extends Shape>替换了List<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 Shapean unknown subtype of 102.Since we don't know what type it is, we don't know if it is a supertype of 103; it might or might not be such a supertype, so it isn't safe to pass a 103 there.

Java相关问答推荐

给定Java枚举类,通过值查找枚举

如何确定springboot在将json字段转换为Dto时如何处理它?

Java编译器抛出可能未正确初始化的错误?

迁移到Java 17后,日期显示不准确

为什么同步数据块无效?

扩展视图高度,并将其拖动到较低的视图上,而不是将其向下推?

二进制数据的未知编码/序列化

根本不显示JavaFX阿拉伯字母

如何在透视表中添加对计数列的筛选?

在Java泛型中使用通配符时,如何推断类型

为了安全起见,有必要复制一份 list 吗?

在打开搜索结果时,如何让Eclipse打开整个文件?

在整数列表中查找和可被第三个整数整除的对时出现无法解释的RunTimeError

字符串的Gzip压缩在java11和java17中给出了不同的结果

如何使用带有可选参数的类生成器?

Java中的一个错误';s stdlib SocksSocketImpl?

由于版本不匹配,从Java 8迁移到Java 17和Spring 6 JUnit4失败

获取月份';s在java中非UTC时区的开始时间和结束时间

java构造函数中的冻结操作何时发生?

java.exe如何执行java源代码?