I was making my own Event System and tried to make it usable like this.
EventSystem.get(EventClientStart.class)
.setCallback((event) -> {
System.out.println("Client Started");
});
EventSystem.get(EventClientStart.class)
.invoke(new EventClientStart());
But got a problem with checks of type, what did I miss? T extends Event, I think it should work, and it actually is (only if I will cast it to (EventController), but then I will get warning "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<>());
}
}
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);
}
}
Even though in your method you've declared that T
is a subtype of Event
and that the return type is EventController<T>
, you're returning a Maps's value, which instead corresponds to EventController<? extends Event>>
.
At some point, T
will refer to a very specific subtype of Event
, while ? extends Event
can be any unknown subtype of Event, and not necessarily correspond to T
. In this scenario, the compiler cannot tell at compile time which exact type is returned for ? extends Event
. So, since it isn't safe to return a ? extends Event
in place of T
, you're getting a compiler error.
If you want to get rid of that error, the return type of your method should match the type of the Map's values.
public static <T extends Event> EventController<? extends Event> get(Class<T> event) {
return map.computeIfAbsent(event, k -> new EventController<>());
}
Here is also a very interesting article from Java Tutorials that introduces wildcards and explains your specific case with an example. I'll attach an extract here and highlight the part that applies to your case:
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
Now, the type rules say that
drawAll()
can only be called on lists of exactlyShape
: it cannot, for instance, be called on aList<Circle>
. That is unfortunate, since all the method does is read shapes from the list, so it could just as well be called on aList<Circle>
. What we really want is for the method to accept a list of any kind of shape:
public void drawAll(List<? extends Shape> shapes) {
...
}
There is a small but very important difference here: we have replaced the type
List<Shape>
withList<? extends Shape>
. NowdrawAll()
will accept lists of any subclass ofShape
, so we can now call it on aList<Circle>
if we want.
List<? extends Shape>
is an example of a bounded wildcard. The ? stands for an unknown type, just like the wildcards we saw earlier. However, in this case, we know that this unknown type is in fact a subtype ofShape
. (Note: It could beShape
itself, or some subclass; it need not literally extendShape
.) We say thatShape
is the upper bound of the wildcard.
There is, as usual, a price to be paid for the flexibility of using wildcards. That price is that it is now illegal to write into shapes in the body of the method. For instance, this is not allowed:
public void addRectangle(List<? extends Shape> shapes) {
// Compile-time error!
shapes.add(0, new Rectangle());
}
You should be able to figure out why the code above is disallowed. The type of the second parameter to
shapes.add()
is? extends Shape
-- an unknown subtype ofShape
. Since we don't know what type it is, we don't know if it is a supertype ofRectangle
; it might or might not be such a supertype, so it isn't safe to pass aRectangle
there.