Runnable
Sun bug ID 6433012
Submit Date 01-JUN-2006
(The argument presented here is significantly enhanced over the bug report.)
Greatly simplify creating a Runnable
, Callable
, or other closure
by using the ‘do
’ keyword in a new way that fits in with Java syntax.
Here is some example code. The commented-out lines show the proposed syntax. More about that after the example.
All examples on this page compile and run in 1.5. Download them here.
import java.util.concurrent.Callable; // See http://Yost.com/computers/java/closures class EasyRunnable { public static void main(String[] args) throws Exception { // new EasyRunnable().proposal(); new EasyRunnable().worksLikeThis(); } int bar = 0; String baz = " so far."; static void proposal() throws Exception { String foo = "Objections: "; // Runnable r = do(foo, bar) { System.out.println(foo + bar + baz); }; // r.run(); // Callable<String> c = do(foo, bar) { return foo + bar + baz; }; // System.out.println(c.call()); } void worksLikeThis() throws Exception { String foo = "Objections: "; Runnable r = new $java_lang_Runnable1(foo, bar); r.run(); Callable<String> c = new $java_util_concurrent_Callable1(foo, bar); System.out.println(c.call()); } //---------------------------------------------------------------------------- // Code from here to end of class is generated by the compiler private class $java_lang_Runnable1 implements Runnable { $java_lang_Runnable1(String foo, int bar) { this.foo = foo; this.bar = bar; } private final String foo; private final int bar; public void run() { System.out.println(foo + bar + baz); } } private class $java_util_concurrent_Callable1 implements Callable<String> { $java_util_concurrent_Callable1(String foo, int bar) { this.foo = foo; this.bar = bar; } private final String foo; private final int bar; public String call() { return foo + bar + baz; } } } |
First, what does this code actually do? It makes a Runnable
instance then runs it. The instance contains snapshots of the foo
local variable and the bar
field as of the time of the creation of the Runnable
(because they were passed to the constructor). The baz
field of the EasyRunnable
instance is not snapshotted, so the Runnable
will use baz
’s current value at runtime.
The proposed syntax vastly simplifies the syntax for creating a Runnable
, simplifying many lines of boilerplate down to just one line of meat.
For simplicity, the above discussion has mentioned only Runnable
, but the example shows that the same syntax can also be used for Callable
. The compiler decides whether to create a new Runnable
or a new Callable
simply by the presence of a return
statement with an argument. (See The trouble with Callable
below.)
Runnable
and Callable
Runnable
is an easy target for such a syntax proposal because Runnable
has only a single, no-arg method (and Runnable
is in the java.lang
package).
But can we expand this syntax proposal to encompass any arbitrary class or interface? Yes!
Consider:
Runnable r = do(foo, bar) { System.out.println(foo + bar + baz); } |
Runnable
is the default class/interface for closures, and it has only one method. If we want to be explicit, we can say this:
Runnable r = do(foo, bar) Runnable.run() { System.out.println(foo + bar + baz); } |
If a class or interface has only one no-arg method (as does Runnable
) the method need not be named. (The same goes for an abstract class that has only one abstract, no-arg method.) So we can say this:
Runnable r = do(foo, bar) Runnable { System.out.println(foo + bar + baz); } |
Now we have some wiggle room to use the closure syntax with other classes or interfaces. Here is some example code:
import javax.swing.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.*; // See http://Yost.com/computers/java/closures class EasyListener { public static void main(String[] args) throws Exception { EasyListener instance = new EasyListener(); // instance.proposal(); instance.worksLikeThis(); } EasyListener() { textField = new JTextField(); textField.setText("Type the Return key."); JFrame frame = new JFrame(); frame.add(textField); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setPreferredSize(new Dimension(300, 40)); frame.pack(); frame.setVisible(true); } static final String msg = "Event: "; JTextField textField; //static void proposal() throws Exception { // f.addActionListener(do(msg) ActionListener.actionPerformed(ActionEvent e) { System.out.println(e); }); // f.addKeyListener (do() KeyListener.keyTyped(KeyEvent e) { System.out.println(e); }); //} void worksLikeThis() throws Exception { textField.addActionListener(new $java_awt_event_ActionListener1(msg)); textField.addKeyListener (new $java_awt_event_KeyListener1() ); } //---------------------------------------------------------------------------- // Code from here to end of class is generated by the compiler private class $java_awt_event_ActionListener1 implements ActionListener { public $java_awt_event_ActionListener1(String msg) { this.msg = msg; } private final String msg; public void actionPerformed(ActionEvent e) { System.out.println(msg + "ActionEvent"); } } private class $java_awt_event_KeyListener1 implements KeyListener { public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) {} public void keyTyped(KeyEvent e) { System.out.println("KeyEvent"); } } } |
Again, we get a massive reduction in clutter, especially when implementing an interface that has multiple methods.
What about method arguments? You declare the method’s arguments explicitly, choosing your own names for argument variables (as an API cannot specify method argument names):
f.addKeyListener(do() KeyListener.keyTyped(KeyEvent evt) { System.out.println(evt); }); |
Closure: do ( ArgumentListopt ) ClosureMethodDeclaratoropt Block ClosureMethodDeclarator: ClassOrInterfaceType ClassOrInterfaceType . MethodName ( FormalParameterListopt ) |
means
Runnable
unless Block contains a return
statement, in which case default is Callable
.);
super()
, if any, followed by
Here are some points to note:
final
; they do, however, have to be explicitly passed to the constructor of the Runnable
in the argument list after the do
.
final
) local variables, in the proposed syntax you can cause a field to be snapshotted, simply by including it in the argument list after the do
.
do
is, of course, snapshotted, so that the closure reads a copy at runtime. A local variable that is to participate dynamically in the running of the closure (rather than being snapshotted) could be modified with the keyword volatile
(this is a new usage context for volatile
). The compiler would generate code so that such a variable lives in a one-element array, which is passed to the constructor of the generated class and used as an array in the class.
Runnable
’s constructor makes the code more readable than inner class syntax because you can see in an instant which local variables from the surrounding context are used in the Runnable
do
’ is a perfect fit.
do
’ clearly indicates to the compiler that this is not the beginning of a ‘do
... while
’. This will make things easier when someday Java makes end of line semicolons optional where possible.import
syntax to allow the declaration of a method one will use with do
, like this import java.awt.event.ActionListener.actionPerformed(ActionEvent e);
do(foo) ActionListener { System.out.println(foo + e); }
do(foo) ActionListener.actionPerformed(ActionEvent e) { System.out.println(foo + e); }
do
argument list, also OK would be a version of this proposal that would use the argument list do
argument list only for arguments needed for super()
. The main thing is the compact syntax.In answer to Martin Fowler’s page about the lack of closures in Java and other languages, I offer the following rendering of his closure example code using the proposal presented here.
The example also illustrates how a class lacking a no-arg constructor might be subclassed by the syntax. The idea is this: if the class has only a single constructor and that constructor requres arguments, then the first arguments in the do()
argument list
must be objects that are type-compatible with the constructor’s arguments (and will be passed to super()).
Additional do()
arguments are handled as if the class had only a single, no-arg constructor.
It gets tricky if the class to be subclassed has more than one constructor. I leave it as an exercise for language lawyers to figure out how far the specification can go toward allowing usage with classes having constructor arguments and/or having multiple constructors.
import java.util.Collection; import java.util.ArrayList; import java.util.LinkedList; // See http://Yost.com/computers/java/closures // See http://www.martinfowler.com/bliki/Closure.html public class FowlerDemo { public static void main(String[] args) throws Exception { emps = new ArrayList<Employee>(); Employee john = new Employee("john", true, 200); emps.add(john); emps.add(new Employee("joe", false, 40)); System.out.println(managers(emps)); System.out.println(highPaid(emps)); Selector<Employee> highPaid = paidMore(150); System.out.println(highPaid.select(john)); } static Collection<Employee> emps; static Collection<Employee> managers(Collection<? extends Employee> empsArg) { // return do(empsArg) Selector<Employee>.select(Employee item) { return item.isManager; }.selectAll(); return new $Selector1<Employee>(empsArg).selectAll(); // Works like this } static Collection<Employee> highPaid(Collection<? extends Employee> empsArg) { int threshold = 150; // return do(empsArg, threshold) Selector<Employee>.select(Employee item) { return item.salary < threshold; }.selectAll(); return new $Selector2<Employee>(empsArg, threshold).selectAll(); // Works like this } static Selector<Employee> paidMore(int amount) { // return do(emps, amount) Selector<Employee>.select(Employee item) { return item.salary > amount; }; return new $Selector3<Employee>(emps, amount); // Works like this } //---------------------------------------------------------------------------- // Code from here to end of class is generated by the compiler private static class $Selector1<T> extends Selector<Employee> { public $Selector1(Collection<? extends Employee> c) { super(c); } public boolean select(Employee item) { return item.isManager; } } private static class $Selector2<T> extends Selector<Employee> { public $Selector2(Collection<? extends Employee> c, int threshold) { super(c); this.threshold = threshold; } private final int threshold; public boolean select(Employee item) { return item.salary < threshold; } } private static class $Selector3<T> extends Selector<Employee> { public $Selector3(Collection<? extends Employee> c, int amount) { super(c); this.amount = amount; } private final int amount; public boolean select(Employee item) { return item.salary > amount; } } } class Employee { Employee(String name, boolean isManager, int salary) { this.name = name; this.isManager = isManager; this.salary = salary; } final String name; final boolean isManager; final int salary; public String toString() { return name; } } //============================================================================== // Probable candidates for java.util abstract class Selector<T> { public Selector(Collection<? extends T> c) { this.c = c; } private final Collection<? extends T> c; public abstract boolean select(T item); public Collection<T> selectAll() { return Selections.selectAll(c, this); } public Collection<T> selectAll(Collection<T> result) { return Selections.selectAll(c, this, result); } } class Selections { private Selections() {} public static <T> Collection<T> selectAll(Collection<? extends T> c, Selector<T> selector) { Collection<T> result = new LinkedList<T>(); return selectAll(c, selector, result); } public static <T> Collection<T> selectAll(Collection<? extends T> c, Selector<T> selector, Collection<T> result) { for (T obj : c) { if (selector.select(obj)) { result.add(obj); } } return result; } } |
Callable
Callable
is not in the java.lang
package. It should have been, of course. But that little mistake shouldn’t ruin Callable
’s chances of participating in this proposal on equal footing with Runnable
.
volatile
ideaimport
suggestion