Java vs C# - AddActionListener vs event subscription
Very simple question from one somewhat new to Java: when adding an event handler (or whatever it is called in Java) to a Control, MUST it be an object? I mean, in C# I can do
control.event += System.eventHandler(methodThatHandlesEvent)
Sure, that's because we have delegate开发者_开发技巧 types in C#, but I was wondering if I can choose which method would be called when an event is raised in Java? I mean, in Java I can have something like:
control.AddActionListener(objectWhichClassImplementsActionListener)
And then I have the actionPerformed
method in this class, which is the only method that is called. I know there are more kinds of listeners, but I can't have a "ActionListenerHandler" class in which I implement several actionPerformed
methods that can be assigned to different controls?
Java has no "method pointers", or function types or something like that, so you have to use the ActionListener
interface with its actionPerformed
method, yes. In most cases I give an object of an anonymous class there, which only calls the method which is handling it:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
myRealMethod();
}
});
This is a bit verbose compared to the C# version, but Java has never been a language to save code lines.
If you want to have it a bit shorter, you can use reflection to help.
package de.fencing_game.paul.examples;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import javax.swing.*;
/**
* An adapter class for calling an (almost) arbitrary
* method as an ActionListener.
*<p>
* In fact, you can use any public method which has either no parameter
* or any parameter of type {@link ActionEvent} or some superclass
* of it (then it gets passed the ActionEvent there).
*</p>
* <p>
* Additionally, the adapter needs an object of a class containing
* this method as the receiver.
* </p>
*<p>
* Inspired by the question <a href="http://stackoverflow.com/questions/5182558/java-vs-c-addactionlistener-vs-event-subscription">Java vs C# - AddActionListener vs event subscription</a> on Stackoverflow.
*</p>
* @author Paŭlo Ebermann
*/
public class MethodAdapter implements ActionListener {
/** the receiver object. */
private Object target;
/** the method to be invoked. */
private Method method;
/** true if the method accepts the ActionEvent argument,
false if it is a no-arg method. */
private boolean takesArgument;
/**
* creates a new MethodAdapter.
* @param o the receiver object.
* @param mName the name of a method on the receiver.
* If there are multiple same-named methods in the class
* of the receiver, we take the first one of these: <ul>
* <li> a public method taking an ActionEvent as parameter</li>
* <li> a public method taking an AWTEvent as parameter</li>
* <li> a public method taking an EventObject as parameter</li>
* <li> a public method taking an Object as parameter</li>
* <li> a public method taking no parameter.</li>
* </ul>
* @throws IllegalArgumentException if there is no such method.
*/
public MethodAdapter(Object o, String mName) {
this(o, findMethod(o, mName));
}
/**
* creates a new MethodAdapter.
* @param o the receiver object.
* @param m the method to be invoked.
* This method has to be declared in the class of the receiver object
* or some supertype, has to take no or one argument, and if one, then
* of some supertype of {@link ActionEvent}.
* @throws IllegalArgumentException if the method does not fit the
* receiver, if the method takes too much arguments or arguments of
* wrong types.
*/
public MethodAdapter(Object o, Method m) {
Class<?>[] params = m.getParameterTypes();
if(!m.getDeclaringClass().isInstance(o)) {
throw new IllegalArgumentException("wrong target object");
}
if(params.length > 1) {
throw new IllegalArgumentException("too many arguments");
}
if(params.length == 1 &&
! params[0].isAssignableFrom(ActionEvent.class)) {
throw new IllegalArgumentException("method not compatible: " + m);
}
this.target = o;
this.method = m;
this.takesArgument = params.length > 0;
}
private static Method findMethod(Object o, String mName) {
Class<?> c = o.getClass();
Class<?> eventClass = ActionEvent.class;
while(eventClass != null) {
try {
return c.getMethod(mName, ActionEvent.class);
}
catch(NoSuchMethodException ex) {}
eventClass = eventClass.getSuperclass();
}
try {
// try a no-argument method
return c.getMethod(mName);
}
catch(NoSuchMethodException ex) {
throw new IllegalArgumentException("No fitting method named '" +
mName +"' on this object " + o +
" of class " + c.getName());
}
}
/**
* the implementation of the actionPerformed method.
* We delegate to our target object and method.
* Any return value of
* the method is silently ignored.
* @throws RuntimeException if any exception is thrown by the invoked
* method (or during the invoke process), it is wrapped in a
* RuntimeException and rethrown.
*/
public void actionPerformed(ActionEvent event) {
try {
if(takesArgument) {
method.invoke(target, event);
}
else {
method.invoke(target);
}
}
catch(Exception e) {
if(e instanceof InvocationTargetException) {
Throwable t = e.getCause();
if(t instanceof Error)
throw (Error)t;
e = (Exception)t;
}
if(e instanceof RuntimeException)
throw (RuntimeException) e;
throw new RuntimeException(e);
}
}
/**
* main method for testing purposes.
*/
public static void main(String[] params) {
JFrame f = new JFrame("Test");
JButton b = new JButton("close");
b.addActionListener(new MethodAdapter(f, "dispose"));
f.getContentPane().add(b);
f.setVisible(true);
}
}
Having this class, you can write
button.addActionListener(new MethodAdapter(this, "myRealMethod"));
instead of the above code, and on the button click the method will be called. Note that now the compiler can't do any type checks, so it has to be done on runtime (and you should check your System.err for exceptions stack traces - the application will continue to run even if some event listener throws exceptions). It will be a bit slower, too, but for user interaction this should not really matter.
This is now also part of my github repository.
Update: With Java 8, there are method reference expressions (like this::myRealMethod
). Java still doesn't have function types, so those expressions are only possible where a SAM-interface (single abstract method) is needed, and then are wrapped into a compiler-generated implementation of that interface, which includes the target object reference and an implementation of the interface's method which calls the target method.
MUST it be an object?
In Java all should be an Object) You can declare your listeners as you want with any number of methods with any number of parameters, but Observable object should know what method call and what arguments pass.
You can use an anonymous inner class to avoid having the ActionListener
declared separately and cluttering up your project with unnecessary helper classes:
control.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked.");
}
});
Note the new ActionListener() { ... }
, which is declaring an anonymous class which implements ActionListener
.
Make a class that will encapsulate sub-classes like
public class MyEvents
{
public class MyEvent_1 : ActionListner
{
....
}
public class MyEvent_2 : ActionListner
{
....
}
}
this way all your event will be at the same place as you want.
精彩评论