In this appendix, we will discuss interfaces, anonymous classes, and lambda expressions which are becoming a more commonly used feature in Java (and other languages) and allow for writing incredibly succinct, clear code.
In order to understand lambda expressions, we will start with interfaces and slowly work our way to proper lambda expressions.
Let’s use the Transform interface from our Object Oriented review:
public interface Transform {
String apply(final input String);
}
We can use this interface to implement types of Transformers.
public class ToCaps implements Transform {
public String apply(final String input){
return input.toUpperCase();
}
}
Once we have a concrete class defined, we would typically create and use instances as follows:
final Transform tx = new ToCaps();
final String result = tx.apply("hello");
return result;
Sometimes, though, we will only be creating instances for a class in one location in our code. Creating another file and another class is overkill, so instead we could use an anonymous class.
final Transform tx = new Transform(){
public String apply(final String input){
return input.toUpperCase();
}
};
final String result = tx.apply("hello");
return result;
Basically, this allows us to declare the same class inline.
It’s “anonymous,” because it no longer has a name.
(it doesn’t need one, because we are only referencing this class here!)
If you look at the declaration of the anonymous class, you see that we have declared the variable to be a Transform, so in theory, we shouldn’t have to tell the compiler we are creating a Transform.
This won’t actually compile, but you should be able to see how the compiler could be implemented so that it would.
final Transform tx = {
public String apply(final String input){
return input.toUpperCase();
}
};
final String result = tx.apply("hello");
return result;
Also, since there is only one method in the Transform interface, it seems like we should also be able to leave the method name, parameter type, return type, and access modifier out.
final Transform tx = {
(input){
return input.toUpperCase();
}
};
final String result = tx.apply("hello");
return result;
Now, if we just eliminate unnecessary braces and tweak the syntax a bit, we can arrive at the following, which will compile.
final Transform tx = (input)->{
return input.toUpperCase();
};
final String result = tx.apply("hello");
return result;
In this particular case, since there is only a single statement in the method body, we can be even more concise.
final Transform tx = (input)->input.toUpperCase();
final String result = tx.apply("hello");
return result;
And since there is only a single parameter, we can do a little better.
final Transform tx = input->input.toUpperCase();
final String result = tx.apply("hello");
return result;
This may not seem that interesting, but let’s look at a more complex example:
public class Transformer {
private final Transform transform;
public Transformer(final Transform tx){
this.transform = tx;
}
public List<String> transform(final Source source){
final List<String> output = new ArrayList<>();
for(final String input : source){
output.add(transform.apply(input));
}
return output;
}
}
Now we can write really succinct code like:
private final Source = getSource();
final Transformer toUpper =
new Transformer(s->s.toUpperCase());
toUpper.transform(source);
final Transformer toLower =
new Transformer(s->s.toLowerCase());
toLower.transform(source);
…or maybe you have experienced the pain of writing a bunch of ActionListeners?
button.addActionListener(
event->JOptionPane.showMessageDialog(this, "I was clicked!")
);
In addition to lambda expressions we can also use method references to avoid even more boiler plate code!
Which gives us:
final Transformer toUpper =
new Transformer(String::toUpperCase);
Instead of
final Transformer toUpper =
new Transformer(s -> s.toUpperCase());
The most generalized version of this code would not even specify that a Transform is needed. Instead, the constructor would be:
public Transformer(Function<String,String> f){
//...
}
Java provides Function and BiFunction interfaces to represent any one argument or two argument method, class, etc.
For zero argument functions, Java provides the Supplier interface:
final Supplier<String> s = ()->"Hello!";
And for a “function” with no return, the Consumer interface:
final Consumer<String> c = s->System.out.println(s);
If you want to pass a computation that has no parameters and no output, then you can use the Runnable interface:
new Thread(
()->{
while(true){
System.out.println("JavaScript Sucks");
}
}
).start();
One other interesting thing you can do with anonymous classes and lambda expressions is use them to capture variables in their closures.
public Transformer create(String message, int count){
return new Transformer(input->{
final StringBuilder sb = new StringBuilder();
for(int i=0; i<count; i++){
sb.append(text);
}
});
}
final Transformer t = create("hi",4);
t.transform(source); //message and count are out of scope!?
This works, because any variable referenced in the lambda expression is captured and will be accessible for the life of the expression!