Lambda expressions — a simple way to get acquainted with Java 8

Liviu Adrian Esanu
METRO SYSTEMS Romania
3 min readJun 13, 2017

The beauty of adopting java 8 early on is that it gives an extra option to developers. Namely if a project ports to using java 8 but code is still mostly written in a java 7 fashion, one can incrementally add or use features of java 8, having the option to both increase or decrease the pace of its adoption. Let’s take a look at three examples of how code is written in the old and new fashion, with the concrete benefit of it being more concise using Java 8.

This is a sample implementation of the Strategy pattern in Java 7:

public interface Validator {
boolean validate(String s);
}

public class IsLowercase implements Validator {
public boolean validate(String s){
return s.matches("[a-z]+");
}
}

public class IsNumeric implements Validator {
public boolean validate(String s){
return s.matches("\\d+");
}
}

public class FieldValidator {
private final Validator v;

public FieldValidator(Validator v){
this.v = v;
}
public boolean validate(Field field){
return v.validate(field.getInput());
}
}

FieldValidator isNum = new FieldValidator(new IsNumeric());
boolean b1 = isNum.validate(new Field("1234"));
FieldValidator isLower = new FieldValidator(new IsLowercase());
boolean b2 = isLower.validate(new Field("lambda"));

It has the following counterpart in Java8, lambda expressions remove the need for two of the classes (IsLowercase and IsNumeric):

FieldValidator numericValidator = new FieldValidator((String s) ->    s.matches("\\d+"));
boolean a = numericValidator.validate(new Field("1234"));
FieldValidator lowerCaseValidator = new FieldValidator((String s) -> s.matches("[a-z]+"));
boolean b = lowerCaseValidator.validate(new Field("lambda"));

The same holds true for the Observer pattern:

Interface Observer {
void notify(String tweet);
}

class Wine implements Observer {
public void notify(String tweet) {
if(tweet.contains("wine")) {
System.out.println("Breaking news: " + tweet);
}
}
}

class Queen implements Observer {
public void notify(String tweet) {
if(tweet.contains("queen")) {
System.out.println("Exclusive: " + tweet);
}
}
}

interface Subject {
void registerObserver(Observer o);
void notifyObservers(String tweet);
}

class Feed implements Subject {
private final List<Observer> observers = new ArrayList<>();

public void registerObserver(Observer o) {
this.observers.add(o);
}

public void notifyObservers(String tweet) {
observers.forEach(o -> o.notify(tweet));
}
}

And the usage is:

Feed f = new Feed();
f.registerObserver(new Wine());
f.registerObserver(new Queen());
f.notifyObservers("The queen loves French wine!");

The counterpart in Java 8, using lambdas will also eliminate the need for class Queen and Wine:

f.registerObserver(
(String tweet) -> {
if(tweet.contains("wine")) {
System.out.println("Breaking news: " + tweet);
}
});

f.registerObserver(
(String tweet) -> {
if(tweet.contains("queen")) {
System.out.println("Exclusive: " + tweet);
}
});

And lastly, below is the Java 7 code for the Processing pipeline pattern:

abstract class ProcessingObject<T> {
protected ProcessingObject<T> successor;

public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}

public T handle(T input) {
T r = handleWork(input);
if (successor != null) {
return successor.handle(r);
}

return r;
}

abstract protected T handleWork(T input);
}

class Header extends ProcessingObject<String> {
public String handleWork(String text) {
return "You might like: " + text;
}
}

class Spellchecker extends ProcessingObject<String> {
public String handleWork(String text) {
return text.replaceAll("aquainted", "acquainted");
}
}

The usage will be something like:

ProcessingObject<String> p1 = new Header();
ProcessingObject<String> p2 = new Spellchecker();
p1.setSuccessor(p2);
String result = p1.handle(“A simple way to get acquainted with Java 8”);
System.out.println(result);

And here is the Java 8 correspondent, again notice that it is less verbose as we no longer need classes Header and Spellchecker:

UnaryOperator<String> header = (String text) -> "You might like: " + text;UnaryOperator<String> spellchecker = (String text) -> text.replaceAll("aquainted", "acquainted");Function<String, String> pipeline = header.andThen(spellchecker);String result = pipeline.apply("A simple way to get acquainted with Java 8");System.out.println(result);

There is no question if current Java applications will have to be migrated eventually to Java 8 in order to take advantage of the servers and new technologies that will be available for it. The effort will have to be done, be it now or later. It seems thus reasonable to consider the benefits of doing it now, and how much effort is needed to see the first results.

In conclusion, adopting java 8 earlier rather than later is a better approach. It offers more options and it can be done incrementally, without the need of a quick concentrated effort from developers to master the new features.

As part of the “Java solutions” team at METRO SYSTEMS ROMANIA, I have experienced first hand the adoption of Java 8 by a large company. Every project had to decide for itself whether it switches or not, from java 7 to java 8, and when to do or not do so. This turned out to be of great idea because it made the more passionate and curious programmers, hyped by the java 8 features, interact with the more conservative ones. This made the first group explain, convince, clarify and persuade the later.

Further reading and bibliography:

  1. https://en.wikipedia.org/wiki/Strategy_pattern
  2. https://en.wikipedia.org/wiki/Observer_pattern
  3. https://en.wikipedia.org/wiki/Pipeline_(computing)
  4. https://www.amazon.de/Java-8-Lambdas-Richard-Warburton/dp/1449370772
  5. https://github.com/RichardWarburton/java-8-lambdas-exercises

--

--

Liviu Adrian Esanu
METRO SYSTEMS Romania

Senior Java developer at METRO Systems Romania. 9+ years of Java experience. Founder of “Bucharest Competitive Programming” group.