Zademy

Java Lambda Expressions: Essential Guide for Collections and Dates

Java; Lambdas; Stream; Dates; Tutorial
902 words

Lambda expressions are one of the most important features introduced in Java 8. They allow us to write more concise and readable code, especially when working with collections and dates. This guide will help you understand and use the most common lambda expressions in daily development.

What are Lambda Expressions

A lambda expression is an anonymous function that we can pass as an argument to methods. It allows us to treat functionality as data, making our code more flexible and expressive.

Basic Syntax

The syntax of a lambda consists of three parts:

(parameters) -> { function body }

Simple example:

// Traditional way with anonymous class
Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Hello World");
    }
};

// Lambda way (more concise)
Runnable r2 = () -> System.out.println("Hello World");

Traversing Collections with Lambdas

Traversing Arrays

Arrays can be elegantly traversed using lambdas and Streams:

String[] names = {"Ana", "Carlos", "Beatriz", "David"};

// Traversal with forEach and lambda
Arrays.stream(names)
      .forEach(name -> System.out.println("Hello " + name));

// Or with method reference
Arrays.stream(names)
      .forEach(System.out::println);

Traversing Lists

Lists are perhaps where we use lambda expressions most frequently:

List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Strawberry");

// Basic traversal
fruits.forEach(fruit -> System.out.println("I like " + fruit));

// Traversal with position numbers
IntStream.range(0, fruits.size())
         .forEach(i -> System.out.println(i + ": " + fruits.get(i)));

// Filter and traverse
fruits.stream()
      .filter(fruit -> fruit.length() > 6)
      .forEach(fruit -> System.out.println("Long fruit: " + fruit));

Traversing Maps

Maps also benefit enormously from lambdas:

Map<String, Integer> ages = new HashMap<>();
ages.put("Ana", 25);
ages.put("Carlos", 32);
ages.put("Beatriz", 28);

// Traverse entry set (key-value)
ages.forEach((name, age) ->
    System.out.println(name + " is " + age + " years old")
);

// Traverse only keys
ages.keySet().forEach(key ->
    System.out.println("Person: " + key)
);

// Traverse only values
ages.values().forEach(value ->
    System.out.println("Age: " + value)
);

Working with Dates using Lambdas

The date and time API introduced in Java 8 works perfectly with lambdas.

Working with LocalDate

LocalDate represents a date without time:

// Create dates
LocalDate today = LocalDate.now();
LocalDate christmas = LocalDate.of(2025, 12, 25);

// Operations with dates using streams
List<LocalDate> importantDates = Arrays.asList(
    LocalDate.of(2025, 1, 1),
    LocalDate.of(2025, 5, 10),
    LocalDate.of(2025, 9, 16),
    LocalDate.of(2025, 12, 25)
);

// Filter future dates
importantDates.stream()
    .filter(date -> date.isAfter(today))
    .forEach(date -> System.out.println("Future date: " + date));

// Format dates
importantDates.forEach(date ->
    System.out.println(date.format(DateTimeFormatter.ofPattern("MMMM dd, yyyy")))
);

Working with LocalTime

LocalTime represents a time without date:

// Create times
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(14, 30);

// List of times of day
List<LocalTime> times = Arrays.asList(
    LocalTime.of(8, 0),   // Start of work
    LocalTime.of(12, 0),  // Lunch
    LocalTime.of(14, 30), // Return from lunch
    LocalTime.of(18, 0)   // End of work
);

// Filter morning times
times.stream()
    .filter(time -> time.isBefore(lunch))
    .forEach(time -> System.out.println("Morning: " + time));

// Convert to minutes of the day
times.forEach(time ->
    System.out.println("Minutes of day: " + time.toSecondOfDay() / 60)
);

Working with LocalDateTime

LocalDateTime combines date and time:

// Create dates with time
LocalDateTime nowFull = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2025, 11, 15, 10, 30);

// List of events
List<LocalDateTime> events = Arrays.asList(
    LocalDateTime.of(2025, 11, 8, 9, 0),   // Today, morning meeting
    LocalDateTime.of(2025, 11, 15, 10, 30), // Next meeting
    LocalDateTime.of(2025, 12, 25, 0, 0)    // Christmas
);

// Filter events in current month
events.stream()
    .filter(event -> event.getMonth() == nowFull.getMonth())
    .forEach(event -> System.out.println("Event this month: " + event));

// Sort events by date
events.stream()
    .sorted()
    .forEach(event -> System.out.println(
        "Event: " + event.format(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm"))
    ));

Practical Combined Examples

Processing List of People with Dates

// Person class
class Person {
    private String name;
    private LocalDate birthDate;

    // constructor, getters...
}

List<Person> people = Arrays.asList(
    new Person("Ana", LocalDate.of(1995, 6, 15)),
    new Person("Carlos", LocalDate.of(1990, 12, 3)),
    new Person("Beatriz", LocalDate.of(1998, 8, 20))
);

// Calculate ages and filter adults
LocalDate today = LocalDate.now();
people.stream()
    .filter(p -> Period.between(p.getBirthDate(), today).getYears() >= 18)
    .forEach(p -> {
        int age = Period.between(p.getBirthDate(), today).getYears();
        System.out.println(p.getName() + " is " + age + " years old");
    });

Grouping Dates by Month

// Group dates by month using lambdas
List<LocalDate> dates = Arrays.asList(
    LocalDate.of(2025, 1, 15),
    LocalDate.of(2025, 1, 20),
    LocalDate.of(2025, 2, 10),
    LocalDate.of(2025, 2, 25),
    LocalDate.of(2025, 3, 5)
);

Map<Month, List<LocalDate>> datesByMonth = dates.stream()
    .collect(Collectors.groupingBy(LocalDate::getMonth));

datesByMonth.forEach((month, dateList) -> {
    System.out.println("Month: " + month);
    dateList.forEach(date ->
        System.out.println("  - " + date.format(DateTimeFormatter.ofPattern("MM/dd")))
    );
});

Best Practices

Method References

When a lambda only calls an existing method, we can use method references:

// Instead of: name -> System.out.println(name)
names.forEach(System.out::println);

// Instead of: date -> date.getMonth()
dates.stream().map(LocalDate::getMonth).collect(Collectors.toList());

// Instead of: (String s) -> s.length()
words.stream().mapToInt(String::length).sum();

Avoid Side Effects

Lambdas should be pure when possible:

// ✅ Good: Pure lambda
List<Integer> doubled = numbers.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());

// ❌ Avoid: Lambda with side effects
List<Integer> result = new ArrayList<>();
numbers.forEach(n -> {
    n = n * 2;
    result.add(n);  // Modifying external state
});

Exception Handling

Lambdas cannot directly throw checked exceptions:

// ✅ Handling with try-catch inside
dates.forEach(date -> {
    try {
        // Code that can throw exception
        LocalDate.parse(date.toString());
    } catch (DateTimeParseException e) {
        System.err.println("Invalid date: " + date);
    }
});

// ✅ Create helper method
private static void processDate(LocalDate date) throws DateTimeParseException {
    // Code that can throw exception
}

dates.forEach(LambdaGuide::processDate);  // Wraps the exception

Conclusion

Lambda expressions have transformed the way we write Java code, making it more concise, readable, and functional. Mastering their use with collections and dates is essential for any modern Java developer.

Remember to practice these concepts gradually, starting with simple examples and advancing to more complex cases. Lambdas not only make your code more elegant but also open the door to functional programming paradigms within Java.


This guide is based on Oracle's official documentation and best practices established in the Java community. To deepen your knowledge, consult Oracle's official tutorials on Lambda Expressions and Stream API.