Key Take Aways:
By reading through this article you would get to know about
- What are Java lambda expressions?
- How to write Java lambda expressions?
- At what all places you can use the java lambda expressions?
- What are some of the very useful Java functional interfaces?
And if you practice by following the unit tests I have written to explain various concepts related to interfaces in this post, you would also get some basic knowledge of utilizing Mockito to mock various situations for your unit testing.
Pre-Requisites
This article assumes that you have basic knowledge of Java programming language prior to Java version 8 and have a basic understanding of Object Oriented Programming Concepts.
Technologies Referred Here
- Java programming language version 14.0
- JUnit version 5.6.2
- Mockito version 3.3.3
- Eclipse IDE for Enterprise Java Developers version: 2020-06
Lambda (λ)
A lambda simply means a function without a name. This is a function that you don’t have to declare, define and implement ahead of its use but you can do all that on the fly when you need it.
Java Lambda Expressions
Java lambda expression is basically a block of code that performs a desired function without a name being given to it. Its a one time use block of code that is only relevant or useful in a particular context. Although you can reuse this block of code by assigning it to a suitable variable in the context where it is defined or by passing it to a method accepting a suitable argument type. Suitable variable or argument type for accepting a lambda expression is a
.Syntax of Lambda Expressions
To understand the syntax of Lambda Expressions let’s take some examples
Lambda Expressions with no Arguments
To understand the lambda expression with no argument let’s create a functional interface with a single abstract method that takes no argument and doesn’t return any value.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions with no *
* arguments. *
* *
* @author D2DJ *
************************************************************************
**/
public interface RandomNumberPrinter {
/**
* Prints a random number.
*/
void printRandomNumber();
}
And Let’s create one more functional interface that has a method returning a value.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions taking no *
* argument and returning a value. *
* *
* @author D2DJ *
************************************************************************
**/
public interface UserGreeter {
/**
* Greets every user that visits Day2DayJava.com.
* @return
*/
String greetUser();
}
Let’s see how we can use a lambda expression to provide an implementation for these interface with a JUnit test. We will also see how we can use similar lambda expression while working with the Java API classes to give us a better idea.
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding Lambda Expressions with no arguments. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class LambdaExpressionsWithNoArgumentTests {
private final ByteArrayOutputStream outputContent = new ByteArrayOutputStream();
private final PrintStream originalOutputStream = System.out;
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outputContent));
}
@AfterEach
public void restoreStreams() {
System.setOut(originalOutputStream);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.RandomNumberPrinter#printRandomNumber()}.
*/
@Test
@Order(1)
final void testLambdaAcceptingNoArgument() {
RandomNumberPrinter randomNumberPrinter = () -> System.out
.print(String.format("Your random number is \"%s\".", Math.random()));
randomNumberPrinter.printRandomNumber();
assertFalse(outputContent.toString().isEmpty());
}
/**
* Test method for explaining the use of lambda expression with no argument with Java API.
*/
@Test
@Order(2)
final void testLambdaAcceptingNoArgumentJavaAPI() throws Exception {
Thread executorThread = new Thread(() -> System.out.print("Runnable Method Called by the Executor Thread."));
executorThread.start();
executorThread.join();
assertEquals(outputContent.toString(), "Runnable Method Called by the Executor Thread.");
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.UserGreeter#greetUser()}.
*/
@Test
@Order(3)
final void testLambdaAcceptingNoArgumentReturningValue() {
UserGreeter userGreeter = () -> "Welcome to Day2DayJava.";
assertEquals("Welcome to Day2DayJava.", userGreeter.greetUser());
}
/**
* Test method for explaining the use of lambda expression with no argument and returning a value with Java API.
*/
@Test
@Order(4)
final void testLambdaAcceptingNoArgumentReturningValueJavaAPI() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> resultFuture = executorService.submit(() -> "Callable Method called by the Executor Service.");
assertEquals("Callable Method called by the Executor Service.", resultFuture.get());
}
}
I have highlighted all the lines in the above test class where we are using a lambda expression with no arguments. Let’s first run these tests and see an all green result. Then we will go test case by test case to understand how those lambdas are working in our test methods.
testLambdaAcceptingNoArgument(): In this test we can see that “randomNumberPrinter” variable has been assigned a lambda expression. The lambda expression here is providing an implementation of the RandomNumberPrinter interface’s method “printRandomNumber()”. That is later invoked by the test. And we can see that it prints the following output to the output stream by putting a debug point in the test and inspecting the outputContent variable’s value for the first test.
testLambdaAcceptingNoArgumentJavaAPI(): The lambda expression present on line number 66 is providing an implementation for Java’s Runnable interface’s run() method. Java compiler understands and treats a lambda expression as an implementation of a particular functional interface’s method smartly based on the lambda expression’s context and assignment. It is similar to writing an anonymous class implementation in prior to Java version 8 but in a more compact way.
testLambdaAcceptingNoArgumentReturningValue(): Tests a lambda expression that returns a value but still doesn’t take an argument. The lambda expression present in this test case at line 79 returns a String value. Notice how we don’t even have to put a return keyword. Java compiler determines that based on the context in which that lambda expression is used and considers the string message present there as the return value from our lambda. In this case compiler determines that this lambda expression is providing an implementation for the greetUser() method from the UserGreeter interface. The assert present in the test case validates that on invoking the greetUser() method it returns the greeting message provided by the lambda.
testLambdaAcceptingNoArgumentReturningValueJavaAPI(): The lambda expression present on line number 92 in this case is providing an implementation for Java’s Callable interface’s call() method. Notice how in this case Java compiler understood and treated our lambda expression as an implementation of the Callable and not Runnable interface. Java compiler does that smartly based on the lambda expression’s context and assignment. As in this case our lambda expression is returning a value.
Lambda Expressions with one Argument
To understand the lambda expression with one argument let’s create a functional interface with a single abstract method that takes only one argument and doesn’t return any value.
package com.day2dayjava.tutorials.java.lambda.expressions;
import java.util.Collection;
/**
************************************************************************
* Example interface to explain the lambda expressions with one *
* argument. *
* *
* @author D2DJ *
************************************************************************
**/
public interface EmptyAndNullElementFilter {
/**
* This method is going to remove all the null and empty elements if present in the passed collection.
*
* @param collection that may have null or empty string values.
*/
void removeEmptyAndNullElements(Collection<String> collection);
}
And Let’s create one more functional interface that has a method returning a value.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions accepting one *
* argument and returning a value. *
* *
* @author D2DJ *
************************************************************************
**/
public interface OddValueIdentifier {
/**
* Tells whether it is a odd number or not.
*
* @param number to check for whether it is an odd number or even.
* @return true/false
*/
boolean isOddInteger(Integer number);
}
Let’s see how we can use a lambda expression to provide an implementation for these interface with a JUnit test. We will also see how we can use similar lambda expression while working with the Java API classes to give us a better idea.
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding Lambda Expressions with one argument. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class LambdaExpressionsWithOneArgumentTests {
private final ByteArrayOutputStream outputContent = new ByteArrayOutputStream();
private final PrintStream originalOutputStream = System.out;
/**
* By default System.out writes to the console, set the output stream to our own output stream.
* This will enables us to read it if needed for validating the output written by the System.out.
*/
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outputContent));
}
/**
* After the test is complete set output stream for System.out back to the default stream.
*/
@AfterEach
public void restoreStreams() {
System.setOut(originalOutputStream);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.EmptyAndNullElementFilter#removeEmptyAndNullElements(java.util.Collection)}.
*/
@Test
@Order(1)
final void testLambdaAcceptingOneArgument() {
List<String> unFilteredData = new ArrayList<>(Arrays.asList("One", null, "Two", "Three", "Four", null, "Five"));
assertEquals(7, unFilteredData.size());
EmptyAndNullElementFilter emptyAndNullElementFilter = (collection) -> {
if (collection != null && !collection.isEmpty()) {
Iterator<String> iterator = collection.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
if (element == null || element.trim().isEmpty()) {
iterator.remove();
}
}
}
};
emptyAndNullElementFilter.removeEmptyAndNullElements(unFilteredData);
assertEquals(5, unFilteredData.size());
}
/**
* Shows the use of a lambda expression accepting one argument with the Java API.
*/
@Test
@Order(2)
final void testLambdaAcceptingOneArgumentJavaAPI() {
List<Integer> numbersUptoFive = List.of(1, 2, 3, 4, 5);
numbersUptoFive.forEach(number -> System.out.print(number));
assertEquals("12345", outputContent.toString());
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.OddValueIdentifier#isOddInteger(Integer)}.
*/
@Test
@Order(3)
final void testLambdaAcceptingOneArgumentReturningValue() {
OddValueIdentifier oddValueIdentifier = valueToValidate -> valueToValidate % 2 == 1;
assertTrue(oddValueIdentifier.isOddInteger(7));
assertFalse(oddValueIdentifier.isOddInteger(6));
}
/**
* Shows the use of a lambda expression accepting one argument and returning a value with the Java API.
*/
@Test
@Order(4)
final void testLambdaAcceptingOneArgumentReturningValueJavaAPI() {
List<String> unFilteredData = new ArrayList<>(Arrays.asList("One", null, "Two", "Three", "Four", null, "Five"));
assertEquals(7, unFilteredData.size());
unFilteredData = unFilteredData.stream().filter(element -> element != null && !element.trim().isEmpty())
.collect(Collectors.toList());
assertEquals(5, unFilteredData.size());
}
}
I have highlighted all the relevant lines in the above test class where we are using a lambda expression with one argument. Let’s run these tests and see the results. Then we will go test case by test case to understand how those lambdas are working in our test methods.
testLambdaAcceptingOneArgument(): In this test we can see that “emptyAndNullElementFilter” variable has been assigned a lambda expression. The lambda expression here is providing an implementation of the EmptyAndNullElementFilter interface’s method “removeEmptyAndNullElements()”. We can see here that lambda expression doesn’t always have to be a single statement. In this use case lines from 65-75 represent the lambda implementation. This lambda accepts a single argument in this case represented by the name “collection”.
testLambdaAcceptingOneArgumentJavaAPI(): The lambda expression in this use case is taking an argument “number” which is actually an element coming from the collection “numbersUptoFive” and is being used by the lambda implementation. The lambda implementation is to just print that number into the output stream.
testLambdaAcceptingOneArgumentReturningValue(): The lambda expression implementation in this case takes an integer argument and returns a boolean value. Note that as long as it is a single statement we do not need to enclose the statement in braces and as long as the operation happening in that lambda expression is not resulting into a void result we do not need to explicitly put a return statement.
testLambdaAcceptingOneArgumentReturningValueJavaAPI(): In this test case we can see that the lambda expression works as a predicate. It takes an argument which is an element of the “unFilteredData” list and returns a boolean value after applying the specified condition on that element.
Lambda Expressions with two Arguments
To understand the lambda expression with two arguments let’s create a functional interface with a single abstract method that takes only two arguments and doesn’t return any value.
package com.day2dayjava.tutorials.java.lambda.expressions;
import java.time.ZonedDateTime;
/**
************************************************************************
* Example interface to explain the lambda expressions with two *
* arguments. *
* *
* @author D2DJ *
************************************************************************
**/
public interface TimeBasedUserGreetor {
/**
* Greets a user based on the time of the day.
*
* @param userName user name to greet with.
* @param dateAndTime date and time to identify the time of the day.
*/
void greetUser(String userName, ZonedDateTime dateAndTime);
}
And Let’s create one more functional interface that has a method returning a value.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions taking two *
* arguments and returning a value. *
* *
* @author D2DJ *
************************************************************************
**/
public interface FullUserNameProducer {
/**
* This method should concatenate the first and last name.
* @param firstName first name of the user.
* @param lastName last name of the user.
* @return combined full name of the user.
*/
String getFullUserName(String firstName, String lastName);
}
Let’s see how we can use a lambda expression to provide an implementation for these interface with a JUnit test. And we will also see how we can use similar lambda expression while working with the Java API classes to give us a better idea.
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding Lambda Expressions with two arguments. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class LambdaExpressionsWithTwoArgumentsTests {
private final ByteArrayOutputStream outputContent = new ByteArrayOutputStream();
private final PrintStream originalOutputStream = System.out;
/**
* By default System.out writes to the console, set the output stream to our own output stream.
* This will enables us to read it if needed for validating the output written by the System.out.
*/
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outputContent));
}
/**
* After the test is complete set output stream for System.out back to the default stream.
*/
@AfterEach
public void restoreStreams() {
System.setOut(originalOutputStream);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.TimeBasedUserGreetor#greetUser(String, java.time.ZonedDateTime)}.
*/
@Test
@Order(1)
final void testLambdaAcceptingTwoArguments() {
TimeBasedUserGreetor timeBasedUserGreetor = (userName, dateAndTime) -> {
Integer hourOfTheDay = -1;
if (dateAndTime != null) {
hourOfTheDay = dateAndTime.getHour();
}
if (userName == null || userName.trim().isEmpty()) {
userName = "Guest";
}
if (hourOfTheDay < 12) {
System.out.print(String.format("Good Morning %s", userName));
} else if (hourOfTheDay < 16) {
System.out.print(String.format("Good Afternoon %s", userName));
} else if (hourOfTheDay < 23) {
System.out.print(String.format("Good Evening %s", userName));
} else {
System.out.print(String.format("Welcome %s", userName));
}
};
String userName = "John";
LocalDateTime localDateTime = LocalDateTime.of(2020, Month.JULY, 25, 16, 30);
ZonedDateTime californiaTimeOfTheDay = ZonedDateTime.of(localDateTime, ZoneId.of("America/Los_Angeles"));
timeBasedUserGreetor.greetUser(userName, californiaTimeOfTheDay);
assertEquals(String.format("Good Evening %s", userName), outputContent.toString());
}
/**
* Shows the use of a lambda expression accepting two arguments with the Java API.
*/
@Test
@Order(2)
final void testLambdaAcceptingTwoArgumentsJavaAPI() {
Map<String, String> famousSportsPersons = new LinkedHashMap<>();
famousSportsPersons.put("Muhammad", "Ali");
famousSportsPersons.put("Roger", "Federer");
famousSportsPersons.put("Michael", "Schumacher");
famousSportsPersons.put("Lance", "Armstrong");
famousSportsPersons.put("Usain", "Bolt");
famousSportsPersons.forEach((k, v) -> System.out.print(k + " " + v + ", "));
assertEquals("Muhammad Ali, Roger Federer, Michael Schumacher, Lance Armstrong, Usain Bolt, ",
outputContent.toString());
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.FullUserNameProducer#getFullUserName(String, String)}.
*/
@Test
@Order(3)
final void testLambdaAcceptingTwoArgumentsReturningValue() {
FullUserNameProducer fullUserNameProducer = (firstName, lastName) -> String.format("%s %s", firstName,
lastName);
assertEquals("Michael Phelps", fullUserNameProducer.getFullUserName("Michael", "Phelps"));
}
/**
* Shows the use of a lambda expression accepting two arguments and returning a value with the Java API.
*/
@Test
@Order(4)
final void testLambdaAcceptingTwoArgumentsReturningValueJavaAPI() {
List<Integer> unsortedData = new ArrayList<>(Arrays.asList(9, 7, 1, 5, 4, 3, 0));
Collections.sort(unsortedData, (valueOne, valueTwo) -> valueOne.compareTo(valueTwo));
//The unsortedData should be sorted now.
assertEquals(0, unsortedData.get(0));
assertEquals(1, unsortedData.get(1));
assertEquals(3, unsortedData.get(2));
assertEquals(4, unsortedData.get(3));
assertEquals(5, unsortedData.get(4));
assertEquals(7, unsortedData.get(5));
assertEquals(9, unsortedData.get(6));
}
}
The highlighted lines in the above test class are the lines where we are using a lambda expression with two argument. Let’s run these tests and see the results. Then we will go test case by test case to understand how those lambdas are working in our test methods.
testLambdaAcceptingTwoArguments(): This test case explains the lambda expression that takes two arguments. We can see the lambda implementation from highlighted lines, again it is a multiline lambda expression. This lambda implementation is to greet a user based on the time of the day. The test case validates the implementation by comparing the expected result with the content of output stream.
testLambdaAcceptingTwoArgumentsJavaAPI(): Here we can see that the “famousSportsPersons.foreach(…)” accepts a lambda implementation that takes two arguments and consume them. In this case the lambda expression is taking the key and value of each Map entry and utilizing them by printing them to the output stream. basically concatenating the first name and last name of famous sports persons.
testLambdaAcceptingTwoArgumentsReturningValue(): The highlighted lambda expression in this case is taking two arguments and returning a value(and not just printing). In this case it is providing an implementation of our “FullUserNameProducer” interface’s “getFullUserName” method.
testLambdaAcceptingTwoArgumentsReturningValueJavaAPI(): The highlighted lambda expression in this use case is providing an implementation of the Java’s Comparator interface, that takes two arguments to compare them and returns their comparison score (+ve value, 0, or -ve value).
Lambda Expressions with explicit Arguments
We may not need to ever provide an explicit type for the arguments of a lambda expression. It’s up to you whether you want to put explicit argument types or not. Sometimes for better code readability or clarity you may want to put the explicit argument type. For example let’s take a simple example of number subtraction provided by a “MathOperationsExecutor” class. Let’s first create an interface that has a subtraction method for the integer numbers.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions accepting two *
* argument and also specifying explicit argument type. *
* *
* @author D2DJ *
************************************************************************
**/
public interface IntegerSubtractor {
/**
* Does the integer subtraction.
* @param subtractFrom subtract from this number.
* @param subtractThis subtract this number.
* @return the remaining value post subtraction.
*/
int subtract(int subtractFrom, int subtractThis);
}
And create another interface that provides a subtraction feature for long numbers.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions accepting two *
* argument and also specifying explicit argument type. *
* *
* @author D2DJ *
************************************************************************
**/
public interface LongSubtractor {
/**
* Does the long subtraction.
* @param subtractFrom subtract from this number.
* @param subtractThis subtract this number.
* @return the remaining value post subtraction.
*/
long subtract(long subtractFrom, long subtractThis);
}
Now let’s create a class that uses both of these interfaces to provide the subtraction feature for both integer and long numbers.
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions accepting two *
* argument and also specifying explicit argument type. *
* *
* @author D2DJ *
************************************************************************
**/
public class MathOperationsExecutor {
/**
* Interface that performs integer subtraction.
*/
IntegerSubtractor integerSubtractor = null;
/**
* Interface that performs long subtraction.
*/
LongSubtractor longSubtractor = null;
/**
* Default constructor that takes a <code>IntegerSubtractor</code> and <code>LongSubtractor</code>
* as arguments.
*
* @param integerSubtractor the instance of a class(or a lambda implementation)
* that performs the long subtraction.
* @param longSubtractor the instance of a class(or a lambda implementation)
* that performs the long subtraction.
*/
public MathOperationsExecutor(IntegerSubtractor integerSubtractor, LongSubtractor longSubtractor) {
this.integerSubtractor = integerSubtractor;
this.longSubtractor = longSubtractor;
}
/**
* Performs integer subtraction.
* @param subtractFrom subtract from this number.
* @param subtractThis subtract this number.
* @return the remaining value post subtraction.
*/
public int executeSubtraction(int subtractFrom, int subtractThis) {
return integerSubtractor.subtract(subtractFrom, subtractThis);
}
/**
* Performs long subtraction.
* @param subtractFrom subtract from this number.
* @param subtractThis subtract this number.
* @return the remaining value post subtraction.
*/
public long executeSubtraction(long subtractFrom, long subtractThis) {
return longSubtractor.subtract(subtractFrom, subtractThis);
}
}
Now let’s write a JUnit test case to test the features provided by this class.
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding a use case where we might want to explicitly *
* specify the argument type *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class LambdaExpressionsWithTwoArgumentsExplicitTypeSpecificationTest {
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.MathOperationsExecutor#MathOperationsExecutor(com.day2dayjava.tutorials.java.lambda.expressions.IntegerSubtractor, com.day2dayjava.tutorials.java.lambda.expressions.LongSubtractor)}.
*/
@Test
final void testMathOperationsExecutor() {
//@formatter:off
MathOperationsExecutor mathOperationsExecutor = new MathOperationsExecutor(
(int subtractFrom, int subtractThis) -> {return subtractFrom - subtractThis;},
(long subtractFrom, long subtractThis) -> {return subtractFrom - subtractThis;}
);
//@formatter:on
assertEquals(5, mathOperationsExecutor.executeSubtraction(10, 5));
assertEquals(5l, mathOperationsExecutor.executeSubtraction(10l, 5l));
}
}
Let’s run this test and see the results first.
As you may see here we could have very well defined the lambda expressions without explicit argument type as well but that might not have been as clear to read or tell which interface implementation is being provided by each lambda expression as shown below.
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding a use case where we might want to explicitly *
* specify the argument type *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class LambdaExpressionsWithTwoArgumentsExplicitTypeSpecificationTest {
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.MathOperationsExecutor#MathOperationsExecutor(com.day2dayjava.tutorials.java.lambda.expressions.IntegerSubtractor, com.day2dayjava.tutorials.java.lambda.expressions.LongSubtractor)}.
*/
@Test
final void testMathOperationsExecutor() {
//@formatter:off
MathOperationsExecutor mathOperationsExecutor = new MathOperationsExecutor(
(subtractFrom, subtractThis) -> {return subtractFrom - subtractThis;},
(subtractFrom, subtractThis) -> {return subtractFrom - subtractThis;}
);
//@formatter:on
assertEquals(5, mathOperationsExecutor.executeSubtraction(10, 5));
assertEquals(5l, mathOperationsExecutor.executeSubtraction(10l, 5l));
}
}
But note that you can either specify the argument type for all the parameters or for none of them but you can not specify for some and not specify for some.
Lambda Expressions with multiple statements
We have already seen few examples of lambdas with multiple statements above so nothing much to explain here. For example we had the lambda implementations having multiple statements in the body at the following places above.
Lambda Expression Usage
A lambda expression can be used anywhere in the Java code where the compiler can ascertain the target type to which the lambda expression is being assigned. For example it can be used at the following places:
- Variable declarations
- Assignments.
- Return statements
- Array initializers
- Method or constructor arguments.
- Lambda expression bodies
- Conditional Expressions, ?:
- Cast Expressions
We saw one example of constructor arguments in our example for lambda expressions with explicit arguments.
Lambda Expression’s use of enclosing scope variables
Lambda expressions can use the variables(or constants) defined in it’s enclosing scope directly, as if the code inside the lambda expression was present in the enclosing scope itself and was not in the lambda body. That is because the lambdas do not introduce a new level of scope. So they can access the method parameters, class fields etc. that are available to it’s enclosing scope the same way that the enclosing scope can. Only difference is that you can not use a variable that is not final or at least “effectively final” before it’s use in the lambda expression i.e. any variable used in a lambda expression from it’s enclosing scope should not be reassigned a value post it’s declaration in the enclosing scope before it’s use in the lambda. Otherwise the Java compiler would throw an exception. Let’s create an interface that we will use for explaining the lambda expression’s access of enclosing scope variables:
package com.day2dayjava.tutorials.java.lambda.expressions;
/**
************************************************************************
* Example interface to explain the lambda expressions using a variable *
* from it's enclosing scope. *
* *
* @author D2DJ *
************************************************************************
**/
public interface OneToTenTablePrinter {
/**
* Prints the mathematical table for a given number(picks it up from the enclosing scope).
*/
void printTable();
}
Now let’s write a JUnit test case to understand the lambda expression’s access of enclosing scope variables:
package com.day2dayjava.tutorials.java.lambda.expressions;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
/**
************************************************************************
* Tests for understanding Lambda expression's access to the enclosing *
* scope. *
* *
* @author D2DJ *
************************************************************************
**/
@ExtendWith(MockitoExtension.class)
class LambdaExpressionScopeTest {
private final ByteArrayOutputStream outputContent = new ByteArrayOutputStream();
private final PrintStream originalOutputStream = System.out;
/**
* By default System.out writes to the console, set the output stream to our own output stream.
* This will enables us to read it if needed for validating the output written by the System.out.
*/
@BeforeEach
public void setUpStreams() {
System.setOut(new PrintStream(outputContent));
}
/**
* After the test is complete set output stream for System.out back to the default stream.
*/
@AfterEach
public void restoreStreams() {
System.setOut(originalOutputStream);
}
/**
* Test method for {@link com.day2dayjava.tutorials.java.lambda.expressions.OneToTenTablePrinter#printTable()}.
*/
@Test
final void testPrintTable() {
int inputNumber = 2;
//inputNumber = 6;
OneToTenTablePrinter tablePrinter = () -> {
//int inputNumber = 8;
for (int i = 1; i <= 10; i++) {
System.out.print((inputNumber * i) + " ");
}
};
tablePrinter.printTable();
assertEquals("2 4 6 8 10 12 14 16 18 20 ", outputContent.toString());
}
}
Let’s run this test case and see the successful results.
You can see that the moment you uncomment the line 51, compiler starts throwing an error telling you that the variable used by the lambda expression is not “effectively final” as shown below.
Same way you can also see that the compiler would again start crying if you try to use the same variable name as was used by its enclosing scope by uncommenting the line with statement “int inputNumber =8;”.
Functional Interfaces in Java API
Many of the functional interfaces we created in our examples above we don’t actually have to create. As all we are doing with them is using them as a receptor or target type for the lambda expressions. Java JDK already comes with a lot of useful functional interfaces that we can use as target types for these lambda expressions.
We will take a look at some of these in our next post.
Source Download
You can download full eclipse project from my Github repository using the following link: https://github.com/Day2DayJava/java
What’s Next
Next we will learn about the Java Functional Interfaces and see few examples of utilizing them.