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.
reading exercises
Here’s the code with a different anonymous Comparator
:
SortedSet<String> strings = new TreeSet<>(new Comparator<String>() {
@Override public int compare(String s1, String s2) {
return s1.substring(1).compareTo(s2.substring(1));
}
});
strings.addAll(Arrays.asList("yolanda", "zach", "alice", "bob"));
How are the names sorted in strings
?
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.