Lambda Expressions in Java
Introduction
Lambda expressions were introduced in Java 8, released in March 2014, to support functional programming by enabling the creation of anonymous functions. They simplify the implementation of interfaces with a single abstract method, known as functional interfaces.
Advantages
Conciseness: Lambdas reduces boilerplate code, making the code shorter and more readable.
Readability: They make the code easier to understand by removing the need for anonymous class implementations.
Ease of Use: Simplifies the use of APIs and libraries that rely on callbacks and event handlers.
Parallel Processing: Enhances the ability to write parallel and concurrent code using the Stream API.
Disadvantages
Debugging Difficulty: Lambdas can make stack traces harder to read, complicating debugging.
Overhead: Initial learning curve for developers new to the concept.
Limited Expressiveness: Not suitable for all situations, especially where complex logic is involved.
Simple Examples
Example 1: Simple Lambda Expression
// Traditional way using an anonymous class Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Hello World"); } }; // Using lambda expression Runnable r2 = () -> System.out.println("Hello World");
Example 2: Lambda with Parameters
// Using lambda to define a Comparator Comparator<Integer> comparator = (a, b) -> a.compareTo(b); // Using the comparator List<Integer> list = Arrays.asList(3, 1, 4, 1, 5, 9); Collections.sort(list, comparator);
Example 3: Lambda in a Stream
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(name -> System.out.println(name));
Example 4: Filtering a List
// Traditional way using an anonymous class List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> filteredNames = new ArrayList<>(); for (String name : names) { if (name.startsWith("A")) { filteredNames.add(name); } } System.out.println(filteredNames); // Output: [Alice] // Using lambda expression List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList()); System.out.println(filteredNames); // Output: [Alice]
Complex Examples
Example 1: Filtering and Mapping with Streams
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> result = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); System.out.println(result); // Output: [ALICE]
Example 2: Summing Values with Streams
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream() .reduce(0, (a, b) -> a + b); System.out.println(sum); // Output: 15
Example 3: Grouping and Counting with Streams
List<String> items = Arrays.asList("apple", "banana", "apple", "orange", "banana", "banana"); Map<String, Long> result = items.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); System.out.println(result); // Output: {banana=3, orange=1, apple=2}
Examples Without Lambda
Example 1: Sorting Without Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } }); System.out.println(names); // Output: [Alice, Bob, Charlie]
Example 2: Filtering Without Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); List<String> result = new ArrayList<>(); for (String name : names) { if (name.startsWith("A")) { result.add(name); } } System.out.println(result); // Output: [Alice]
Example 3: Iterating Without Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); for (String name : names) { System.out.println(name); } // Output: // Alice // Bob // Charlie
Examples Rewritten with Lambda
Now, let's rewrite the examples above but using lambda...
Example 1: Sorting With Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // Explanation: // The traditional way used an anonymous inner class to define the Comparator. // With a lambda expression, we can write this in a more concise form. // The lambda `(o1, o2) -> o1.compareTo(o2)` directly provides the comparison logic. Collections.sort(names, (o1, o2) -> o1.compareTo(o2)); System.out.println(names); // Output: [Alice, Bob, Charlie]
Example 2: Filtering With Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // Explanation: // The traditional way involved iterating over the list and adding elements that match the condition to a new list. // Using streams and lambda expressions, we can achieve this in a more declarative way. // The `filter` method applies the lambda `name -> name.startsWith("A")` to each element in the stream. // The `collect(Collectors.toList())` method gathers the filtered elements into a new list. List<String> result = names.stream() .filter(name -> name.startsWith("A")) .collect(Collectors.toList()); System.out.println(result); // Output: [Alice]
Example 3: Iterating With Lambda
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // Explanation: // The traditional way used a for-each loop to print each element in the list. // With a lambda expression, we can use the `forEach` method to achieve the same result. // The lambda `name -> System.out.println(name)` is applied to each element in the list. names.forEach(name -> System.out.println(name)); // Output: // Alice // Bob // Charlie
Conclusion
Lambda expressions in Java bring the power of functional programming to the language, making code more concise, readable, and expressive. They are particularly useful when working with collections and streams, providing significant advantages in terms of code clarity and simplicity. However, they come with a learning curve and can complicate debugging. By understanding and using lambda expressions effectively, developers can greatly enhance their productivity and the quality of their code.
Subscribe to my newsletter
Read articles from André Felipe Costa Bento directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
André Felipe Costa Bento
André Felipe Costa Bento
Fullstack Software Engineer.