Reading 19, Sidebar: Anonymous Runnable
Anonymous classes
Usually when we implement an interface, we do so by declaring a class.
For example, given the interface
Comparator
in the Java API:
/** A comparison function that imposes a total ordering on some objects.
* ... */
public interface Comparator<T> {
/** Compares its two arguments for order.
* ...
* @return a negative integer, zero, or a positive integer if the first
* argument is less than, equal to, or greater than the second */
public int compare(T o1, T o2);
}
We might declare:
/** Orders Strings by length (shorter first) and then lexicographically. */
public class StringLengthComparator implements Comparator<String> {
@Override public int compare(String s1, String s2) {
if (s1.length() == s1.length()) {
return s1.compareTo(s2);
}
return s1.length() - s2.length();
}
}
One purpose of
Comparator
is for sorting.
A
SortedSet
keeps its items in a total order.
Without a
Comparator
, the
SortedSet
implementation uses the
compareTo
method provided by the objects in the set:
SortedSet<String> strings = new TreeSet<>();
strings.addAll(Arrays.asList("yolanda", "zach", "alice", "bob"));
// strings is { "alice", "bob", "yolanda", "zach" }
With a
Comparator
:
// uses StringLengthComparator declared above
Comparator<String> compareByLength = new StringLengthComparator();
SortedSet<String> strings = new TreeSet<>(compareByLength);
strings.addAll(Arrays.asList("yolanda", "zach", "alice", "bob"));
// strings is { "bob", "zach", "alice", "yolanda" }
If we only intend to use this comparator in this one place, we already know how to eliminate the variable:
// uses StringLengthComparator declared above
SortedSet<String> strings = new TreeSet<>(new StringLengthComparator());
strings.addAll(Arrays.asList("yolanda", "zach", "alice", "bob"));
// strings is { "bob", "zach", "alice", "yolanda" }
An anonymous class declares an unnamed class that implements an interface and immediately creates the one and only instance of that class. Compare to the code above:
// no StringLengthComparator class!
SortedSet<String> strings = new TreeSet<>(new Comparator<String>() {
@Override public int compare(String s1, String s2) {
if (s1.length() == s1.length()) {
return s1.compareTo(s2);
}
return s1.length() - s2.length();
}
});
strings.addAll(Arrays.asList("yolanda", "zach", "alice", "bob"));
// strings is { "bob", "zach", "alice", "yolanda" }
Pros:
-
If we’re only using the comparator in this one piece of code, we’ve reduced its scope. Previously, any other code could start using and depending on
StringLengthComparator
. -
A reader no longer has to search elsewhere for the details of the comparator, everything is right here.
Cons:
-
If we need the same comparator more than once, we might be tempted to copy-and-paste. The old way is DRY.
-
If the implementation of the comparator is long, it interrupts the surrounding code, making it harder to understand. The old way is broken into modular pieces.
So anonymous classes are good for short one-off implementations of a method.
Using an anonymous
Runnable
to start a thread
The
Runnables
we use to create new threads often meet these criteria perfectly.
Here’s the example from the reading :
public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("Hello from a thread!");
}
}).start();
}
Rather than (1) declare a class that implements
Runnable
where the
run
method calls
System.out.println
, (2) create an instance of that class, and (3) pass that instance to the
Thread
constructor, we do all three steps in one go with an anonymous
Runnable
.
If you’re feeling clever, you can go one step further with Java’s lambda expressions :
public static void main(String[] args) {
new Thread(() -> System.out.println("Hello from a thread!")).start();
}
Whether that’s more or less
easy to understand
is up for debate.
Runnable
and
run
never appear at all, so you certainly have to do more research to understand this construction the first time you come across it.