1.4-Software-Development-Principles

Lambda expressions part1

Learning goals

Requirements: To understand this concept you have to be comfortable with the idea of interfaces in Java:

public interface Printer {
	void print(String text);
}

Normal use of an interface so far

When the Printer interface above is implemented, the print method can determine what to do with the provided attribute.

For instance, simply print to the SaxionApp. Later I may choose another way to print this information without having to change the rest of my code.

public class MyPrinter implements Printer {
	@Override
    public void print(String text) {
		SaxionApp.printLine(text);
    }
}

Now I can supply an instance of this class to another method to print something.

public class Main {
	public static void main(String[] args) {
		Printer printer = new MyPrinter();
		doSomethingComplicated(printer);
	}
	
	private doSomethingComplicated(Printer printer) {
		// While doing stuff, this method will use 
        // printer.print("message") to output messages.
    }
}

Passing behaviour

Now we can choose to provide doSomethingComplicated with any instance, as long as the method print(String text) is implemented:

public class SilentPrinter implements Printer {
	@Override
	public void print(String text) {
		// Don't actually do anything.
	}	
}

public class Main {
	public static void main(String[] args) {
		Printer printer = new SilentPrinter();
		doSomethingComplicated(printer);
	}
}

By simply providing a different class that implements the Printer interface in this second example the same method doSomethingComplicated now doesn’t print anything.

This makes the doSomethingComplicated method really flexible. By providing a different Printer the doSomethingComplicated method may write it output to a file, database, System.out, or not at all.

This technique is called passing code or passing behaviour.

Anonymous class

In the previous example we actually created an entire class, probably in its own file:

public class MyPrinter implements Printer {
	@Override
	public void print(String text) {
		/* You choose */
	}	
}

But this is not always necessary. You may do the following:

public class Main {
	public static void main(String[] args) {
		Printer p = new Printer() {
		    @Override
            public void print(String text) {
				System.out.println(text);
            }
        };
		
		p.print("Look mom! No actual class!");
		doSomethingComplicated(p);
    }
}

This code doesn’t actually define a class with its very own name it just adds the implementation right after the new Printer() statement.

In the code above p is instantiated as an anonymous class.

For anonymous classes the following applies:

Lambda expressions

When an interface only defines a single method, then you have the opportunity to make the code from before even shorter:

// Anonymous class
Printer p = new Printer() {
    @Override
    public void print(String text) {
        System.out.println(text);
    }
};
p.print("This works!");

can be changed to

// Lambda expression
Printer p = (text) -> { 
    System.out.println(text); 
};
p.print("This STILL works!");

Wait! What?

Remember the interface definition:

@FunctionalInterface
public interface Printer {
	void print(String text); // One single method!!!
}

Because the Printer interface only defines a single method the compiler already knows which method you want to implement. This is called a functional interface. You may even add the annotation @FunctionalInterface to indicate your intention that this interface should be used for lambda expressions.

(Although you may still create classes that also implement this interface if you really want to.)

Now the pattern becomes:

Printer p = (text) -> { /* Do something with text */ };

The compiler also knows the type and number of the arguments of this method. So (text) is used as the single String argument of the method.

Between the braces { /* here */ } you can now type any instructions that you need.

A single line of code.

When your implementation only uses a single line of code. You can additionally leave our the braces and simply type the instruction directly:

// Lambda expression without braces.
Printer p = (t) ->  System.out.println(t); 
p.print("This STILL works!");

Single variable names are Very Bad, but this shows that you can use any variable name to pass to the lambda expression. In this case q instead of test;

Only one parameter?

Throw away those parentheses:

// Lambda expression without braces.
Printer p = t ->  System.out.println(t); 
p.print("This STILL works!");

Here the parenteses around the argument t are removed.

Why stop there?

The final form of a lambda expression looks like this:

// Lambda expression without braces.
Printer p = System.out::println; 
p.print("This STILL works!");

This code effectively says: The println method in System.out has the same definition as the print method defined in the Printer interface, please use that method.

You can also refer to static methods using ClassName :: methodName:

The :: operator is also called the method reference operator.

Note: You can reference any method whose signature matches the definition of the single method in the functional interface.

Printer p = MyClass::myStaticPrintMethod;
p.print("Works with static methods as well.");

Consider how mindbendingly powerful this is: You can simply use the reference to a method as a parameter in your own code, so that your code changes its behaviour depending on the provided method that is passed along. This is called passing behaviour.

More information

For more information on this: We were inspired by the book “Java 8 in Action” (ISBN: 9781617291999)

Anonymous classes vs. Lambda expressions

One important difference between lambda expression and anonymous classes.

Please examine the following code:

// Anonymous class
Printer p = new Printer() {
    private int nrOfPrints = 0;

    @Override
    public void print(String text) {
        nrOfPrints++;
        System.out.println("[" + nrOfPrints + "] " + text);
    }
};
p.print("Test");

This anonymous implementation of Printer stores a member variable that counts each line. Every time the print method is called the internal state of the class is updated.

There is no way to do this with lambda expressions!