Appendix: Lambda Expressions

CSC-430

Phillip Wright

Lambda Expressions

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.

Interfaces, Again

In order to understand lambda expressions, we will start with interfaces and slowly work our way to proper lambda expressions.

An Interface

Let’s use the Transform interface from our Object Oriented review:

public interface Transform {
  String apply(final input String);
}

Implementations

We can use this interface to implement types of Transformers.

public class ToCaps implements Transform {
  public String apply(final String input){
    return input.toUpperCase();
  }
}

Instances

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;

Anonymous Classes

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.

Anonymous Classes II

final Transform tx = new Transform(){
  public String apply(final String input){
    return input.toUpperCase();
  }
};
final String result = tx.apply("hello");
return result;

Anonymous Classes III

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!)

Going Further

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.

Going Further II

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;

Going Further III

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;

Lambda Expression

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;

Lambda Expression II

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;

Lambda Expressions III

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;

Lambda Expressions IV

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;
  }
}

Lambda Expressions V

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);

GUI

…or maybe you have experienced the pain of writing a bunch of ActionListeners?

button.addActionListener(
  event->JOptionPane.showMessageDialog(this, "I was clicked!")
);

Even Better

In addition to lambda expressions we can also use method references to avoid even more boiler plate code!

Even Better II

Which gives us:

final Transformer toUpper = 
      new Transformer(String::toUpperCase);

Instead of

final Transformer toUpper = 
      new Transformer(s -> s.toUpperCase());

Functions

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.

Also

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);

And Finally

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();

Closures

One other interesting thing you can do with anonymous classes and lambda expressions is use them to capture variables in their closures.

Closures II

public Transformer create(String message, int count){
  return new Transformer(input->{
    final StringBuilder sb = new StringBuilder();
    for(int i=0; i&ltcount; i++){
      sb.append(text);
    }
  });
}

final Transformer t = create("hi",4);
t.transform(source); //message and count are out of scope!?

Closures III

This works, because any variable referenced in the lambda expression is captured and will be accessible for the life of the expression!