3.9 Local Inner Classes
Long before there were lambda expressions, Java had a mechanism for concisely defining classes that implement an interface (functional or not). For functional interfaces, you should definitely use lambda expressions, but once in a while, you may want a concise form for an interface that isn’t functional. You will also encounter the classic constructs in legacy code.
3.9.1 Local Classes
You can define a class inside a method. Such a class is called a local class. You would do this for classes that are just tactical. This occurs often when a class implements an interface and the caller of the method only cares about the interface, not the class.
For example, consider a method
public static IntSequence randomInts(int low, int high)
that generates an infinite sequence of random integers with the given bounds.
Since IntSequence is an interface, the method must return an object of some class implementing that interface. The caller doesn’t care about the class, so it can be declared inside the method:
private static Random generator = new Random(); public static IntSequence randomInts(int low, int high) { class RandomSequence implements IntSequence { public int next() { return low + generator.nextInt(high - low + 1); } public boolean hasNext() { return true; } } return new RandomSequence(); }
There are two advantages of making a class local. First, its name is hidden in the scope of the method. Second, the methods of the class can access variables from the enclosing scope, just like the variables of a lambda expression.
In our example, the next method captures three variables: low, high, and generator. If you turned RandomInt into a nested class, you would have to provide an explicit constructor that receives these values and stores them in instance variables (see Exercise 15).
3.9.2 Anonymous Classes
In the example of the preceding section, the name RandomSequence was used exactly once: to construct the return value. In this case, you can make the class anonymous:
public static IntSequence randomInts(int low, int high) { return new IntSequence() { public int next() { return low + generator.nextInt(high - low + 1); } public boolean hasNext() { return true; } } }
The expression
new Interface() { methods }
means: Define a class implementing the interface that has the given methods, and construct one object of that class.
Before Java had lambda expressions, anonymous inner classes were the most concise syntax available for providing runnables, comparators, and other functional objects. You will often see them in legacy code.
Nowadays, they are only necessary when you need to provide two or more methods, as in the preceding example. If the IntSequence interface has a default hasNext method, as in Exercise 15, you can simply use a lambda expression:
public static IntSequence randomInts(int low, int high) { return () -> low + generator.nextInt(high - low + 1); }