Я создавал свою собственную систему событий и пытался сделать ее пригодной для использования вот так.
EventSystem.get(EventClientStart.class)
.setCallback((event) -> {
System.out.println("Client Started");
});
EventSystem.get(EventClientStart.class)
.invoke(new EventClientStart());
Но возникла проблема с проверками типа, что я пропустил? T расширяет событие, я думаю, оно должно работать, и это действительно так (только если я приведу его к (EventController), но тогда я получу предупреждение «Непроверенное приведение»)
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);
}
}
Я думаю, это связано с Covariance
на Java. Вы можете читать только с List
или Map
, если используете Upper Bounded Wildcards
.
Более подробную информацию вы можете найти в этом ответе здесь
На самом деле, я не понимаю, как это можно использовать в моем случае.
Несмотря на то, что в вашем методе вы заявили, что T
является подтипом Event
и что тип возвращаемого значения — EventController<T>
, вы возвращаете значение Maps, которое вместо этого соответствует EventController<? extends Event>>
.
В какой-то момент T
будет относиться к очень конкретному подтипу Event
, тогда как ? extends Event
может быть любым неизвестным подтипом События и не обязательно соответствовать T
. В этом сценарии компилятор не может определить во время компиляции, какой именно тип возвращается для ? extends Event
. Итак, поскольку возвращать ? extends Event
вместо T
небезопасно, вы получаете ошибку компилятора.
Если вы хотите избавиться от этой ошибки, тип возвращаемого значения вашего метода должен соответствовать типу значений карты.
public static <T extends Event> EventController<? extends Event> get(Class<T> event) {
return map.computeIfAbsent(event, k -> new EventController<>());
}
Вот также очень интересная статья из Java Tutorials, в которой представлены подстановочные знаки и объясняется ваш конкретный случай на примере. Я прикреплю сюда выдержку и выделю ту часть, которая относится к вашему случаю:
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
туда небезопасно.
Это проблема ассоциации ключ-значение, которая не является частью языка. Невозможно сообщить компилятору, что универсальный тип определенного ключа совместим с универсальным типом конкретного значения, хранящегося в карте для этого ключа. Вы можете объявить только общий универсальный тип для всех ключей и общий универсальный тип для всех значений. Похоже на: вопросы и ответы