Thursday, May 19, 2011

Java local methods

Local functions are a neat paradigm in most dynamic languages like Python and JavaScript:
def recursiveList(path):
def collect(path, result):
# this is a function inside another function
result.append(path)
if os.path.isdir(path):
for child in os.listdir(path):
collect(os.path.join(path, child), result)
result = []
collect(path, result)
return result
One big design win of local functions is namespace cleanliness. The function collect is not visible anywhere but inside of recursiveList.

Unfortunately, C/C++ has no way of doing this that I know of. Java gets really close to local functions with the ability to use anonymous classes. An anonymous class is especially useful for implementing an interface with a single method. Here's an example:
public static void pipeStreamsAsynchronously(final InputStream in, final OutputStream out) {
Thread thread = new Thread(new Runnable() {
// Runnable is an interface with just a void run() method.
public void run() {
try {
while (true) {
// this call to read() can block
int b = in.read();
if (b == -1)
return;
out.write(b);
}
} catch (IOException e) {
}
}
});
thread.start();
}
It occurred to me only today that anonymous classes can be used just like local functions by using initializers. Initializers are segments of code that run at the beginning of (almost) every constructor. You declare them like this:
public class LinePrinter {

private final String newline;

{
// this is an initializer
if (System.getProperty("os.name").startsWith("Windows"))
newline = "\r\n";
else
newline = "\n";
// NOTE: this is the wrong way to get the platform's line terminator.
}

private final PrintStream out;
public LinePrinter() {
// initializer has run by now
out = System.out;
}
public LinePrinter(PrintStream out) {
// initializer has run by now
this.out = out;
}
public void print(String line) {
out.print(line);
out.print(newline);
}
}
You can also put a static before the { to make it a static initializer which runs once when the class is first accessed at runtime. Static and non-static initializers are the mechanisms java compilers use behind the scenes to evaluate initial assignments for fields with an assignment built into their declarations:
public class MyClass {
// the getProperty() call runs in an implicit static initializer.
public static final String NEWLINE = System.getProperty("line.separator");

// the length() call runs in an implicit non-static initializer.
private final int newlineLength = NEWLINE.length();
}
In general, explicit non-static initializers are not particularly useful since they offer code reuse only in the case non-converging constructor overloads. The proper way to implement the above LinePrinter class would be have the parameterless constructor call this(System.out); and have the other constructor do the newline initialization logic. The example is only to demonstrate when initializers run.

Here's how you can use anonymous classes and initializers together to make local functions in Java:
public static ArrayList<File> recursiveList(final File path) {
final ArrayList<File> result = new ArrayList<File>();
new Object() {
private void collect(File path, ArrayList<File> result) {
result.add(path);
File[] children = path.listFiles();
if (children != null)
for (File child : children)
collect(child, result);
}
{
// this is the initializer
collect(path, result);
}
};
return result;
}

No comments: