Lambda Expressions in Java

1. Introduction to Lambda Expressions

Lambda expressions are one of the most important features introduced in Java 8. They provide a clear and concise way to represent one method interface (functional interface) using an expression.

What are Lambda Expressions?

  • Anonymous functions (functions without name)
  • Concise way to represent method interfaces
  • Enable functional programming in Java
  • Can be passed as arguments or stored in variables

Why Use Lambda Expressions?

  • Reduce boilerplate code
  • Improve code readability
  • Enable functional programming
  • Facilitate parallel processing

2. Syntax of Lambda Expressions

Basic Syntax

(parameters) -> expression

or

(parameters) -> { statements; }

Syntax Variations

// No parameters
() -> System.out.println("Hello World")
// Single parameter (parentheses optional)
x -> x * x
// Multiple parameters
(x, y) -> x + y
// With multiple statements
(x, y) -> {
int result = x + y;
return result;
}

3. Functional Interfaces

Lambda expressions work with functional interfaces - interfaces with only one abstract method.

Common Built-in Functional Interfaces

@FunctionalInterface
interface MyFunction {
int operate(int a, int b);
}
// Using the custom functional interface
public class LambdaDemo {
public static void main(String[] args) {
// Lambda expression implementation
MyFunction addition = (a, b) -> a + b;
MyFunction multiplication = (a, b) -> a * b;
System.out.println("Addition: " + addition.operate(5, 3));        // 8
System.out.println("Multiplication: " + multiplication.operate(5, 3)); // 15
}
}

4. Complete Code Examples

Example 1: Basic Lambda Expressions

import java.util.*;
public class BasicLambdaExamples {
public static void main(String[] args) {
// 1. Runnable interface
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
// 2. Comparator interface
List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana");
// Traditional way
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
// Lambda way
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
// Even shorter
names.sort((s1, s2) -> s1.compareTo(s2));
System.out.println("Sorted names: " + names);
// 3. Custom functional interface
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;
System.out.println("10 + 5 = " + operate(10, 5, add));
System.out.println("10 * 5 = " + operate(10, 5, multiply));
}
interface MathOperation {
int operation(int a, int b);
}
private static int operate(int a, int b, MathOperation mathOperation) {
return mathOperation.operation(a, b);
}
}

Example 2: Lambda with Collections

import java.util.*;
import java.util.function.*;
public class CollectionLambdaExamples {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 1. Using forEach with lambda
System.out.println("All numbers:");
numbers.forEach(n -> System.out.print(n + " "));
System.out.println();
// 2. Filter even numbers
System.out.println("Even numbers:");
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// 3. Map - square each number
System.out.println("Squared numbers:");
numbers.stream()
.map(n -> n * n)
.forEach(n -> System.out.print(n + " "));
System.out.println();
// 4. Reduce - sum of all numbers
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// 5. Method reference
System.out.println("Using method reference:");
numbers.forEach(System.out::print);
System.out.println();
}
}

Example 3: Real-World Example - Employee Management

import java.util.*;
import java.util.stream.*;
class Employee {
private String name;
private int age;
private double salary;
private String department;
public Employee(String name, int age, double salary, String department) {
this.name = name;
this.age = age;
this.salary = salary;
this.department = department;
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
public String getDepartment() { return department; }
@Override
public String toString() {
return String.format("%s (Age: %d, Salary: $%.2f, Dept: %s)", 
name, age, salary, department);
}
}
public class EmployeeManagement {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("John", 25, 50000, "IT"),
new Employee("Alice", 30, 60000, "HR"),
new Employee("Bob", 35, 75000, "IT"),
new Employee("Diana", 28, 55000, "Finance"),
new Employee("Mike", 40, 80000, "IT")
);
// 1. Filter IT department employees
System.out.println("IT Department Employees:");
employees.stream()
.filter(e -> e.getDepartment().equals("IT"))
.forEach(System.out::println);
// 2. Increase salary by 10% for IT department
System.out.println("\nAfter 10% salary hike for IT:");
employees.stream()
.filter(e -> e.getDepartment().equals("IT"))
.map(e -> {
double newSalary = e.getSalary() * 1.10;
return new Employee(e.getName(), e.getAge(), newSalary, e.getDepartment());
})
.forEach(System.out::println);
// 3. Sort employees by salary
System.out.println("\nEmployees sorted by salary:");
employees.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.forEach(System.out::println);
// 4. Find average salary
double averageSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.average()
.orElse(0.0);
System.out.printf("\nAverage Salary: $%.2f\n", averageSalary);
// 5. Group employees by department
System.out.println("\nEmployees by Department:");
Map<String, List<Employee>> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
byDepartment.forEach((dept, empList) -> {
System.out.println(dept + ": " + empList.size() + " employees");
empList.forEach(emp -> System.out.println("  - " + emp.getName()));
});
}
}

Example 4: Event Handling with Lambda

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class LambdaGUIExample {
public static void main(String[] args) {
// Create frame
JFrame frame = new JFrame("Lambda GUI Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 200);
frame.setLayout(new FlowLayout());
// Create components
JButton button1 = new JButton("Click Me!");
JButton button2 = new JButton("Say Hello");
JLabel label = new JLabel("Waiting for click...");
// Traditional way (before Java 8)
button1.addActionListener(new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("Button 1 clicked!");
}
});
// Lambda way (Java 8+)
button2.addActionListener(e -> label.setText("Hello from Lambda!"));
// Add components to frame
frame.add(button1);
frame.add(button2);
frame.add(label);
// Show frame
frame.setVisible(true);
}
}

5. Method References

Method references are shorthand notation of lambda expressions to call methods.

Types of Method References

import java.util.*;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "Bob", "Diana");
// 1. Static method reference
names.forEach(MethodReferenceExample::printStatic);
// 2. Instance method reference of particular object
MethodReferenceExample example = new MethodReferenceExample();
names.forEach(example::printInstance);
// 3. Instance method reference of arbitrary object
names.sort(String::compareToIgnoreCase);
// 4. Constructor reference
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
System.out.println("Upper case names: " + upperCaseNames);
}
public static void printStatic(String str) {
System.out.println("Static: " + str);
}
public void printInstance(String str) {
System.out.println("Instance: " + str);
}
}

6. Variable Capture in Lambda

public class VariableCaptureExample {
private String instanceVariable = "Instance Variable";
private static String staticVariable = "Static Variable";
public void demonstrateCapture() {
String localVariable = "Local Variable";
final String finalLocalVariable = "Final Local Variable";
// Lambda capturing different types of variables
Runnable lambda = () -> {
System.out.println("Instance: " + instanceVariable);
System.out.println("Static: " + staticVariable);
System.out.println("Local: " + localVariable); // Effectively final
System.out.println("Final Local: " + finalLocalVariable);
// Can modify instance and static variables
instanceVariable = "Modified Instance";
staticVariable = "Modified Static";
};
lambda.run();
// localVariable = "Modified"; // This would make lambda invalid
}
public static void main(String[] args) {
new VariableCaptureExample().demonstrateCapture();
}
}

7. Best Practices and Common Patterns

Best Practices

import java.util.*;
import java.util.function.*;
public class BestPractices {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Alice", "", "Bob", "");
// 1. Use method references when possible
names.removeIf(String::isEmpty); // Good
// names.removeIf(s -> s.isEmpty()); // Okay, but longer
// 2. Keep lambdas short and readable
names.stream()
.filter(name -> !name.isEmpty())  // Simple condition
.map(String::toUpperCase)         // Simple transformation
.forEach(System.out::println);    // Simple action
// 3. Use meaningful parameter names
BiFunction<Integer, Integer, Integer> bad = (a, b) -> a + b;
BiFunction<Integer, Integer, Integer> good = (firstNumber, secondNumber) -> firstNumber + secondNumber;
// 4. Avoid complex logic in lambdas
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Bad - complex logic in lambda
numbers.stream()
.map(n -> {
int result = n * n;
if (result > 10) {
return result + 100;
} else {
return result - 10;
}
});
// Good - extract to method
numbers.stream()
.map(BestPractices::complexCalculation);
}
private static int complexCalculation(int n) {
int result = n * n;
if (result > 10) {
return result + 100;
} else {
return result - 10;
}
}
}

8. Common Functional Interfaces

import java.util.function.*;
import java.util.*;
public class CommonFunctionalInterfaces {
public static void main(String[] args) {
// 1. Predicate - boolean test(T t)
Predicate<String> isLong = s -> s.length() > 5;
System.out.println("Is 'Hello' long? " + isLong.test("Hello"));
// 2. Function - R apply(T t)
Function<String, Integer> stringLength = String::length;
System.out.println("Length of 'Hello': " + stringLength.apply("Hello"));
// 3. Consumer - void accept(T t)
Consumer<String> printer = System.out::println;
printer.accept("Hello Consumer!");
// 4. Supplier - T get()
Supplier<Double> randomSupplier = Math::random;
System.out.println("Random number: " + randomSupplier.get());
// 5. UnaryOperator - T apply(T t)
UnaryOperator<String> toUpper = String::toUpperCase;
System.out.println("Upper case: " + toUpper.apply("hello"));
// 6. BinaryOperator - T apply(T t1, T t2)
BinaryOperator<Integer> adder = Integer::sum;
System.out.println("5 + 3 = " + adder.apply(5, 3));
}
}

9. Conclusion

Key Takeaways:

  1. Conciseness: Lambda expressions significantly reduce boilerplate code
  2. Readability: Make code more readable and expressive
  3. Functional Programming: Enable functional programming paradigms in Java
  4. Parallel Processing: Facilitate parallel execution with streams
  5. API Design: Enable more flexible and powerful API designs

When to Use Lambda Expressions:

  • Working with functional interfaces
  • Collection processing with streams
  • Event handling
  • Thread creation
  • Anywhere you need to pass behavior as a parameter

When to Avoid:

  • Complex logic (extract to methods instead)
  • When method references are more readable
  • When the logic requires multiple statements and becomes hard to read

Final Thoughts:

Lambda expressions revolutionized Java programming by introducing functional programming concepts while maintaining Java's object-oriented nature. They work hand-in-hand with streams, method references, and functional interfaces to create more expressive, concise, and maintainable code.

Mastering lambda expressions is essential for modern Java development and will significantly improve your coding efficiency and code quality.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper