CodeMIRAI
0 XP

Lambdas and streams

3 min read 4 runnable examples Quiz included

Lambdas

A lambda is a short, unnamed function that you can pass around like a value:

Java
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Asha", "Leo", "Mei", "Carlos", "Bea"));

        names.forEach(name -> System.out.println("Hi, " + name));
        names.removeIf(name -> name.length() <= 3);
        System.out.println(names);              // [Asha, Carlos]
        names.sort((a, b) -> b.compareTo(a));
        System.out.println(names);              // [Carlos, Asha]
    }
}
  • name -> ... takes one parameter, (a, b) -> ... takes two, and () -> ... takes none.
  • For more than one statement, use braces and return: (a, b) -> { int d = a - b; return d; }.

Functional interfaces

A lambda can go wherever Java expects an interface with exactly one abstract method. The java.util.function package has the common shapes:

Java
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Function<String, Integer> length = s -> s.length();
        BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
        Supplier<String> greeting = () -> "Hello!";

        System.out.println(isEven.test(10));            // true
        System.out.println(length.apply("CodeMIRAI"));  // 9
        System.out.println(add.apply(3, 4));            // 7
        System.out.println(greeting.get());             // Hello!
    }
}
InterfaceTakesReturnsMethod
Predicate<T>a Ta booleantest
Function<T, R>a Tan Rapply
Consumer<T>a Tnothingaccept
Supplier<T>nothinga Tget

Method references

When a lambda only calls an existing method, a method reference says the same thing more briefly:

Java
names.forEach(name -> System.out.println(name));
names.forEach(System.out::println);              // the same

names.sort((a, b) -> a.compareToIgnoreCase(b));
names.sort(String::compareToIgnoreCase);         // the same

Streams

A stream processes a collection as a pipeline of steps:

Java
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(5, 12, 8, 3, 20, 15, 7);

        List<Integer> result = nums.stream()
            .filter(n -> n > 6)            // keep 12, 8, 20, 15, 7
            .map(n -> n * 10)              // 120, 80, 200, 150, 70
            .sorted()                      // 70, 80, 120, 150, 200
            .collect(Collectors.toList());
        System.out.println(result);

        int sum = nums.stream().mapToInt(Integer::intValue).sum();
        double avg = nums.stream().mapToInt(Integer::intValue).average().orElse(0);
        long big = nums.stream().filter(n -> n >= 10).count();
        System.out.println("sum " + sum + ", average " + avg + ", 10 or more: " + big);
    }
}
  • filter keeps the items that pass a test, and map transforms each item.
  • sorted sorts, and collect gathers the results into a new collection. (Java 16 added a shorter .toList(), but the code runner uses Java 15.)
  • Nothing happens until a final step such as collect, count or sum asks for a result, and the original list is never changed.

Streams of objects

Java
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

class Student {
    private final String name;
    private final String course;
    private final int score;

    Student(String name, String course, int score) {
        this.name = name;
        this.course = course;
        this.score = score;
    }

    String getName() { return name; }
    String getCourse() { return course; }
    int getScore() { return score; }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = List.of(
            new Student("Asha", "Java", 92),
            new Student("Leo", "Python", 78),
            new Student("Mei", "Java", 85),
            new Student("Raj", "Python", 88),
            new Student("Zoe", "SQL", 95)
        );

        String topThree = students.stream()
            .sorted(Comparator.comparingInt(Student::getScore).reversed())
            .limit(3)
            .map(Student::getName)
            .collect(Collectors.joining(", "));
        System.out.println("Top three: " + topThree);

        Map<String, Double> averageByCourse = students.stream()
            .collect(Collectors.groupingBy(Student::getCourse, TreeMap::new,
                                           Collectors.averagingInt(Student::getScore)));
        System.out.println(averageByCourse);

        boolean anyBelow50 = students.stream().anyMatch(s -> s.getScore() < 50);
        System.out.println("Anyone below 50? " + anyBelow50);
    }
}

groupingBy sorts items into groups, here one per course, and averagingInt reduces each group to an average. A TreeMap keeps the courses in alphabetical order.

Loop or stream?

Streams shine for filter-transform-collect pipelines over data. A plain loop is often clearer when you need indexes, an early exit, or several variables changing together. Both are good Java: choose whichever reads better.

Finished this lesson?Mark it complete to earn +20 XP and move your progress on.