Reputation:
The system of EventType is highly confusing in JavaFX. I think the reason for this is that the designers decided to make the EventType class itself generic.
Is there any practical use for this or is this just some obscure form of ensuring type safety ?
I would imagine classes would be made generic only if the generic type is returned somewhere to a calling class. Otherwise what's the point ?
Upvotes: 0
Views: 2462
Reputation: 209418
There are Event
classes, i.e. subclasses of Event
, that define particular methods for that class. E.g. MouseEvent
defines various methods that can be used to query the coordinates of the mouse when the event occurred (getX()
, getY()
, getSceneX()
, getScreenX()
etc); ScrollEvent
defines methods to query the amount and direction of the scroll: getDeltaX()
, getDeltaY()
, etc).
EventType
is a more fine-grained object that specifies what happened. So constants of the EventType
class include MOUSE_CLICKED
, MOUSE_PRESSED
, KEY_TYPED
, SCROLL_STARTED
, etc. Each of these values is associated with a particular Event
subclass.
The event registration method is addEventHandler(...)
. This method takes two parameters: an EventType
(which type of event to listen for), and an EventHandler
. The EventHandler
is generic for obvious reasons: by defining an EventHandler<T extends Event>
you get to define a handle(T event)
method that takes the appropriate event object, which you can then query in the type-specific way. (E.g. an EventHandler<MouseEvent>
has a handle(MouseEvent event)
method, which can query the coordinates of the mouse, etc).
Now, it clearly only makes sense to register an EventHandler<T>
for an EventType
that is associated with the Event
subclass T
. Furthermore, it's desirable to allow for the compiler to enforce this. By making the EventType
class generic: EventType<T extends Event>
, we get to let the compiler enforce this rule. MOUSE_CLICKED
is not just an instance of EventType
, it's an instance of EventType<MouseEvent>
. Since the signature of the addEventHandler(...)
method is addEventHandler(EventType<T>, EventHandler<T>)
, the compiler enforces that the handler is "for the same class of event" as the event type.
When you combine this with using lambdas (or I suppose with type inference, in particular), it becomes quite powerful:
node.addEventHandler(MOUSE_CLICKED, e -> {...});
Because MOUSE_CLICKED
is specifically an EventType<MouseEvent>
, the compiler is able to infer that the event handler defined by the lambda is an EventHandler<MouseEvent>
, and thus that e
is a MouseEvent
. So the compiler can now infer that
node.addEventHandler(MOUSE_CLICKED, e -> System.out.println(e.getX()));
node.addEventHandler(KEY_TYPED, e -> System.out.println(e.getText()));
are legal, but
node.addEventHandler(MOUSE_CLICKED, e -> System.out.println(e.getText()));
is not. If EventType
were not generic, this would not be possible.
(The same is true without lambdas, but the less concise syntax doesn't drive the point home as well. But without EventType
being generic, there would be no way for the compiler to reject
node.addEventHandler(MOUSE_CLICKED, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) { }
});
Again, the fact the MOUSE_CLICKED
has type EventType<MouseEvent>
means this will not compile.)
Technically, EventType
is used as a "Runtime-type token". It's being used not just to say which events we are interested in, but also which type of event handler should be used to handle the event. Other examples of this use of generics include Class<T>
: see this part of the Java tutorial for a brief discussion.
Upvotes: 7