Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
A lambda expression is a piece of code that is giving an alternative way to the anonymous class to pass the function as a parameter to other subsequent flows of code such as methods, constructors, etc.. In this approach, a function can be referenced with a variable and passed as a reference to be executed in a subsequent flow of code execution.
Lambda Expressions are the most remarkable feature added to the Java platform with Java 8. It’s specified in JSR 335 and JEP 126. The very need for this feature is to gain some of the capabilities supplied by functional programming. The main idea behind this concept is to be able to parametrize the functions for subsequent executions of them.
Till Java 8, this could already be simulated via the use of anonymous inner classes with some design patterns like command pattern and functors. However, the adoption of lambda expressions gave way to the direct use of this concept.
A lambda expression is a piece of code that is giving an alternative way to the anonymous class to pass the function as a parameter to other subsequent flows of code such as methods, constructors, etc.. In this approach, a function can be referenced with a variable and passed as a reference to be executed in a subsequent flow of code execution.
The structure of a lambda expression is as follows; it is formed of some arguments, followed by an arrow and that’s followed by a code block.
A lambda expression is identified by a special single-method interface called Functional Interface. A functional interface is the target type that’s determined by the compiler and used as the type of the reference to the lambda expression.
This binding of a lambda expression to a functional interface is determined from the context of the lambda expression. That means, the binding of a lambda expression to a target type can take place in different contexts such as variable declarations, method arguments, constructors, etc.; and from this binding, the compiler finds the target type, which is a functional interface, and infers the types of the parameters used in the lambda expression according to that functional interface.
A functional interface can be marked with an informative annotation @FunctionalInterface that can be used to inform other developers.
Let’s do a simple example to understand it well.
Think that we want to lowercase or uppercase a text based on a condition.
It will be a dynamic evaluation so we can abstract the operation.
By leveraging the lambda expressions we can do it as following:
Here is the lambda expressions for case operations:
t -> t.toUpperCase(); t -> t.toLowerCase();
By looking at the code above, we see that there is a parameter t; we do not know its type, and in the code block of the expression, the methods of t, which are toUpperCase and toLowerCase, are called.
To be able to pass these expressions to somewhere, we have to declare a functional interface; with that single-method interface, the compiler will be able to infer the type of t:
public interface CaseOperation { String operate(String text); }
Then we can write our main code as such:
public void printWithCaseOperation(String text, CaseOperation operation){ System.out.println(operation.operate(text)); } public void mainCode(){ if(upperCaseEnabled){ printWithCaseOperation("Hello Lambda!", t -> t.toUpperCase()); } else { printWithCaseOperation("Hello Lambda!", t -> t.toLowerCase()); } }
Here, when we call the method printWithCaseOperation with a lambda expression as its second parameter, the compiler infers that the method’s second parameter is of type CaseOperation and, so is also of the lambda expression’s type, too.
At this point, let’s watch this video to listen to the internals of lambda expressions. For whom needs some speed, I will summarize it, you’re welcome:
Java Lambda Expressions have a basic structure as drawn in the diagram above. Besides this, some inferences can be made automatically by the compiler for us. When writing lambda expressions these compiler inferences directs us to write less code.
The full syntax of a lambda expression is as follows.
(int a, int b) -> { return a + b; }
The left side of the arrow is just like the input type declaration part of a method signature.
Then put the arrow.
And lastly, write the body in curly braces just as in a method body.
Curly brackets and the return keyword can be omitted in case of single statements in the body.
(int a, int b) -> a + b
Types of the input variables can be omitted and these can be inferred by the compiler.
(a, b) -> a + b
Parentheses are optional in case of a single implicit target type.
a -> a.size()
When using explicit target types, then parentheses are required.
(String str) -> str.length()
() -> "javalopment"
The body can have multiple statements and in this case, the use of curly braces is mandatory.
(a, b) -> { int result = a + b; return result; }
Now that we know; ultimately, a functional interface is a method reference and also defines the target type of a lambda expression for the sake of compiler.
So we can structure our API around functional interfaces and use lambdas for more effective and clean code. However, as you see, a functional interface just defines the target types of lambda expressions. Hence, the same functional interfaces could be used in most cases. For that aim, in Java 8; several common built-in functional interfaces have already been created for us.
So instead of declaring our custom functional interfaces, we can use the built-in ones that will mostly meet our needs. Let’s look over that built-in functional interfaces.
The Function interface can be used in case of the need for;
one input, one output
/** * @param <T> the type of the input to the function * @param <R> the type of the result of the function */ @FunctionalInterface public interface Function<T, R> { R apply(T t); }
So if you need a functional interface that gets one input and returns an output then you should use the Function interface instead of creating a custom one of yours.
Let’s examine the following code:
Function<Person, String> f = t -> t.getGivenName(); String name = f.apply(Person.createShortList().get(0)); System.out.println(name);
In the code above, our lambda gets an instance t of type Person as an input and returns the name as String. When we execute the lambda via the Function interface then we get the result.
The Supplier interface can be used in case of the need for;
no input, one output
/** * @param <T> the type of results supplied by this supplier */ @FunctionalInterface public interface Supplier<T> { T get(); }
So if you need a functional interface that gets no input and returns an output then you should use the Supplier interface instead of creating a custom one of yours.
Let’s examine the following code:
public int sum(int a, int b) { int result = a + b; debug("supplierTest", () -> "returns " + result + " - for a: " + a + ", b: " + b); return result; } public void debug(String method, Supplier log){ if(logger.isDebugEnabled()){ logger.debug(method + " " + log.get()); } }
The Consumer interface can be used in case of the need for;
one input, no output
/** * @param <T> the type of the input to the operation */ @FunctionalInterface public interface Consumer<T> { void accept(T t); }
So if you need a functional interface that gets one input and returns no output then you should use the Consumer interface instead of creating a custom one of yours.
Let’s examine the following code:
Consumer<String> upperCaseWriter = (s) -> System.out.println(s.toUpperCase()); Consumer<String> lowerCaseWriter = (s) -> System.out.println(s.toLowerCase()); public void consumerTest(){ write("upper-cased", upperCaseWriter); write("LOWER-CASED", lowerCaseWriter); write("Just as how it's written!", (s) -> System.out.println(s)); } public void write(String log, Consumer<String> writer){ writer.accept(log); }
The Predicate interface can be used in case of the need for;
one input, one boolean output
/** * @param <T> the type of the input to the predicate */ @FunctionalInterface public interface Predicate<T> { boolean test(T t); }
So if you need a functional interface that gets one input and returns a boolean output then you should use the Predicate interface instead of creating a custom one of yours.
Let’s examine the following code:
public static final Predicate<Person> YOUNG = p -> p.getAge() >= 18 && p.getAge() <= 25; public void predicateTest() { Person person = Person.createShortList().get(0); System.out.println(YOUNG.test(person)); }
The BiPredicate interface can be used in case of the need for;
two inputs, one boolean output
public boolean filter(String a, String b, BiPredicate<String, String> filterPredicate){ return filterPredicate.test(a, b); } public void testBiPredicate(){ boolean equals = filter("javalopment", "Javalopment", (a, b) -> a.equals(b)); System.out.println(equals); }
IntPredicate, LongPredicate, and DoublePredicate are primitive versions of Predicate interface.
In these versions; you do not need to declare the input type. For example; for IntPredicate, the input type is an integer value.
public static final IntPredicate IS_YOUNG = age -> age >= 18 && age <= 25; public void isYoung() { System.out.println(IS_YOUNG.test(14)); }
The UnaryOperator interface can be used in case of the need for;
one input, one output and both are the same type
/** * @param <T> the type of the operand and result of the operator */ @FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { static <T> UnaryOperator<T> identity() { return t -> t; } }
Let’s look over the following code:
public void testUnaryOperator(){ UnaryOperator<String> upperCase = t -> t.toUpperCase(); System.out.println(upperCase.apply("test")); }
The BinaryOperator interface can be used in case of the need for;
two inputs, one output and all are the same type
Let’s look over the following code:
public static final BinaryOperator<Integer> SUM = (a, b) -> a + b; public void sum() { System.out.println(SUM.apply(10, 20)); }
Some of the built-in functional interfaces provide some utility methods enabling the composition of multiple functions.
The Predicate interface provides two utility methods for combining the predicates: and, or.
With these methods, we can easily combine existing predicates to generate new ones.
The Predicate interface provides the default method and. With the use of and, the new combined predicate returns true if all of the predicates return true.
Let’s look over the following code:
String text = "abc.def_ghi"; Predicate<String> containsDot = str -> str.contains("."); Predicate<String> containsUnderscore = str -> str.contains("_"); Predicate<String> combinedPredicate = containsDot.and(containsUnderscore); if(combinedPredicate.test(text)) { System.out.println("passed"); }
The Predicate interface also provides the default method or. With the use of or, the new combined predicate returns true if any one of the predicates returns true.
Let’s look over the following code:
String text = "abc.def"; Predicate<String> containsDot = str -> str.contains("."); Predicate<String> containsUnderscore = str -> str.contains("_"); Predicate<String> seperated = containsDot.or(containsUnderscore); if(seperated.test(text)) { System.out.println("passed"); }
The Function interface provides two utility methods for combining the functions: compose, andThen.
With these methods, we can easily combine existing functions to generate new ones.
The Function interface provides the default method compose. With the use of compose, a new function is generated from a chain of functions. The new combined function will operate in the reverse order of the functions chained.
Let’s look over the following code:
Function<String, String> greet = str -> "Hi " + str; Function<String, String> upperCase = str -> str.toUpperCase(); Function<String, String> upperCasedGreet = upperCase.compose(greet); String greetingText = upperCasedGreet.apply("Erol"); System.out.println(greetingText); // HI EROL
The Function interface provides the default method andThen. With the use of andThen, a new function is generated from a chain of functions. The new combined function will operate in the same order of the functions chained.
Let’s look over the following code:
Function<String, String> greet = str -> "Hi " + str; Function<String, String> upperCase = str -> str.toUpperCase(); Function<String, String> upperCasedGreet = greet.andThen(upperCase); String greetingText = upperCasedGreet.apply("Erol"); System.out.println(greetingText); // HI EROL
In this article, we have looked over Java lambda expressions and its special reference type, Functional Interfaces. We have also gone over the built-in functional interfaces provided and examined the basic use cases.
In the next step, it will be worthwhile to going further to the details of the Java 8 Stream API.
You can see the sample code for this article on my Github page:
https://github.com/erolhira/java