Requirements: To understand this concept you have to be comfortable with the idea of interfaces in Java:
public interface Printer {
void print(String text);
}
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.
}
}
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.
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:
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!");
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.
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
;
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.
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.
For more information on this: We were inspired by the book “Java 8 in Action” (ISBN: 9781617291999)
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!