Wrapping my head around Generics

I am trying to create a sort of event bus that can handle multiple different events and their listeners.
The goal is that every event would inherit from a super Event class, the event bus would register that event, and listeners could subscribe to it.
This is probably easier to explain in code:

Event class and possible subclasses


public class Event
{
    public final String eventName;
    public final long timestamp;

    public Event(String name, long time)
    {
        this.eventName = name;
        this.timestamp = time;
    }
}

public class UpdateEvent extends Event
{
    public final double delta;

    public UpdateEvent(double delta)
    {
        super("Event.Update", currentTime);
        this.delta = delta;
    }
}

public class FileUpdatedEvent extends Event
{
    public final File file;

    public FileUpdatedEvent(File file)
    {
        super("Event.FileUpdated", CurrentTime);
        this.file = file;
    }
}

EventBus class


public class EventBus
{
    private Map<Class<? extends Event>, List<MonoCallback<? extends Event>> eventListeners = new HashMap<>();

    public <E extends Event> void registerEventListener(Class<E> clazz, MonoCallback<E> listener)
    {
        List<MonoCallback<E> list = eventListeners.get(clazz);
        list.add(listener);
    }

    public <E extends Event> void pushEvent(E event)
    {
        list<MonoCallback<E> list = eventListeners.get(event.getClass());
        for(MonoCallback<E> listener : list)
        {
            listener.invoke(event);
        }
    }
}

Listener


public class SomeClass
{
    public SomeClass()
    {
        instance_of_eventbus.registerEventListener(FileUpdatedEvent.class, (event) -> {
            File file = event.file;
            //do stuff
        }
    }
}

Obviously, the above lacks any sort of polish with null checks and what not, and… of course all fails. But hopefully you understand the point.
So my question is,
First; is there a way to enforce that the Class key and generic parameter in MonoCallback in the eventListeners map are the same?
Second; when the callback is invoked, that the passed event parameter is ensured to be an instance of the proper Event subclass, not requiring instanceof checks and casting on the listener end? (SomeClass example)

I have been messing around with this for quite some time, and have not come to a satisfactory solution, and any online research is so jumbled and confusing that it makes my head hurt.
For the record… I hate generics :-\

Yes, in the way that you’re doing it! As long as you don’t expose the Map so that only your registration methods can alter it, then you’re achieving this. You can’t generify a value based on the key.

Cast it on the dispatching side after you get it out of the map using Class.cast() with the key. You may want to consider explicitly passing in the Class as a parameter for the push event method, or storing it in the event, rather than relying on getClass() - eg. gives you more flexibility with the class hierarchy later.

EDIT - by the way, what you’re looking at is kind of a Typesafe Heterogeneous Container - https://gerardnico.com/wiki/code/design_pattern/typesafe_heterogeneous_container

Thank you for the reply. It lifts my spirits to know I was not only on the right track, but also so close to a solution.
Here is what I have come up with for the EventBus class:


public class EventBus
{
	private Map<Class<? extends Event>, List<MonoCallback<? extends Event>>> eventListeners;

	public <E extends Event> void registerEventListener(Class<E> clazz, MonoCallback<E> listener)
	{
		List<MonoCallback<? extends Event>> list = eventListeners.get(clazz);
		if (list == null)
		{
			eventListeners.put(clazz, list = new ArrayList<>());
		}
		list.add(listener);
	}

	public <E extends Event> void pushEvent(Class<E> clazz, E event)
	{
		if (eventListeners.isEmpty()) return;
		if (!eventListeners.containsKey(clazz)) return;

		List<MonoCallback<? extends Event>> list = eventListeners.get(clazz);
		if (list == null) return;

		for (MonoCallback<? extends Event> listener : list)
		{
			MonoCallback<E> castedCallback = (MonoCallback<E>) listener; // Type Safety: unchecked cast...
			castedCallback.invoke(event);
		}
	}
}

Line 25 produces a type safety warning, is there any good way of getting around, or a better design to avoid this warning. Or must I live with it?