CodeGym /Courses /JAVA 25 SELF /Using lambdas in collections and streams

Using lambdas in collections and streams

JAVA 25 SELF
Level 48 , Lesson 1
Available

1. Lambda expressions and collections

Let’s recall what collection processing looked like before lambda expressions. Suppose we have a list of strings and we want to print them to the screen:

List<String> list = Arrays.asList("cat", "dog", "ox");

for (String s : list) {
    System.out.println(s);
}

That’s simple, but if we want, for example, to remove all empty strings from a list, we have to write a loop with a condition and sometimes even use an iterator (otherwise you’ll get a ConcurrentModificationException). Or, say, sorting by string length:

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Even for such a simple task — already 5 lines of code and lots of “noisy” braces. How to optimise it? You already know the answer: lambda expressions.

Using lambda expressions in collection methods

Since Java 8, collection interfaces have gained new methods that accept functional interfaces — which means we can pass lambda expressions to them. Here are the most popular ones:

  • forEach(Consumer<T> action)
  • removeIf(Predicate<T> filter)
  • sort(Comparator<T> c)
  • replaceAll(UnaryOperator<T> operator)

Example: forEach

Print all list elements (the old way):

for (String s : list) {
    System.out.println(s);
}

Now — with a lambda:

list.forEach(s -> System.out.println(s));

Or even shorter, if you want to play the “Java guru”:

list.forEach(System.out::println); // method reference, we'll cover it later

Example: removeIf

Remove all empty strings from a list:

List<String> animals = new ArrayList<>(Arrays.asList("cat", "", "dog", "ox", ""));
animals.removeIf(s -> s.isEmpty());
System.out.println(animals); // [cat, dog, ox]

Example: sort

Sort a list by string length:

List<String> animals = Arrays.asList("cat", "dog", "ox", "elephant");

animals.sort((a, b) -> a.length() - b.length());
System.out.println(animals); // [ox, cat, dog, elephant]

Example: replaceAll

Convert all strings to upper case:

List<String> animals = new ArrayList<>(Arrays.asList("cat", "dog", "ox"));
animals.replaceAll(s -> s.toUpperCase());
System.out.println(animals); // [CAT, DOG, OX]

2. Stream API and lambda expressions

With Java 8, the Stream API was introduced — a powerful tool for processing collections in a functional style. Streams let you filter, transform, sort, and collect using method chains. And all those methods accept lambda expressions!

Important: A full deep dive into the Stream API will come later; for now — just basic examples to understand the role of lambdas.

Example: filtering

Keep only strings longer than 3 characters:

List<String> animals = Arrays.asList("cat", "elephant", "ox", "crocodile");
animals.stream()
       .filter(s -> s.length() > 3)
       .forEach(System.out::println); // elephant, crocodile

Example: transformation (map)

Make all strings uppercase:

List<String> animals = Arrays.asList("cat", "elephant", "ox");
List<String> upper = animals.stream()
                            .map(s -> s.toUpperCase())
                            .collect(Collectors.toList());
System.out.println(upper); // [CAT, ELEPHANT, OX]

Example: sorting

Get a length-sorted list (without modifying the original):

List<String> animals = Arrays.asList("cat", "elephant", "ox", "crocodile");
List<String> sorted = animals.stream()
                             .sorted((a, b) -> a.length() - b.length())
                             .collect(Collectors.toList());
System.out.println(sorted); // [ox, cat, elephant, crocodile]

3. Comparison with anonymous classes

Let’s compare how the same code would look with an anonymous class versus a lambda.

Sorting by string length

Anonymous class:

animals.sort(new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
});

Lambda expression:

animals.sort((a, b) -> a.length() - b.length());

Takeaway:
A lambda expression saves lots of lines and makes code more readable. Fewer braces, less noise — more essence!

Removing empty strings

Anonymous class:

animals.removeIf(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.isEmpty();
    }
});

Lambda expression:

animals.removeIf(s -> s.isEmpty());

4. Practice: small tasks with lambda expressions

Let’s try using lambda expressions in a mini-program that works with a list of users.

Example 1: Filtering users by age

class User {
    String name;
    int age;
    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

List<User> users = Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
);

users.stream()
     .filter(u -> u.age >= 18)
     .forEach(System.out::println); // Bob (25)

Example 2: Sorting users by name

List<User> users = Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
);

users.sort((u1, u2) -> u1.name.compareTo(u2.name));
System.out.println(users);
// [Alice (17), Bob (25), Charlie (15)]

Example 3: Transform a list of users into a list of names

List<String> names = users.stream()
                          .map(u -> u.name)
                          .collect(Collectors.toList());
System.out.println(names); // [Alice, Bob, Charlie]

Example 4: Remove all minors

List<User> users = new ArrayList<>(Arrays.asList(
    new User("Alice", 17),
    new User("Bob", 25),
    new User("Charlie", 15)
));

users.removeIf(u -> u.age < 18);
System.out.println(users); // [Bob (25)]

5. Useful nuances

Nuances: scope and “effectively final” variables

A lambda expression can use variables from the enclosing method, but only if they are final or “effectively final” (that is, not modified after initialisation).

int minAge = 18;
users.stream()
     .filter(u -> u.age >= minAge)
     .forEach(System.out::println);

If you try to modify minAge after using it in a lambda, the compiler will produce an error.

Table: key collection and stream methods with lambda expressions

Collection/stream method What it does Lambda type Example
forEach(Consumer<T>)
For each element
x -> ...
list.forEach(s -> ...)
removeIf(Predicate<T>)
Removes elements by a condition
x -> ...
list.removeIf(s -> ...)
sort(Comparator<T>)
Sorts elements
(a, b) -> ...
list.sort((a, b) -> ...)
replaceAll(UnaryOperator<T>)
Replaces each element
x -> ...
list.replaceAll(s -> ...)
filter(Predicate<T>)
Filters the stream
x -> ...
stream.filter(s -> ...)
map(Function<T, R>)
Transforms elements
x -> ...
stream.map(s -> ...)
forEach(Consumer<T>)
Iterates over the stream
x -> ...
stream.forEach(s -> ...)
sorted(Comparator<T>)
Sorting in the stream
(a, b) -> ...
stream.sorted((a, b) -> ...)

7. Common mistakes

Error #1: Lambda is too long. If your lambda already has 5 lines of code, conditionals, loops, and try-catch, you should probably extract that code into a separate method. Lambdas are best for short logic.

Error #2: Using mutable variables. If you try to modify a variable from the enclosing method inside a lambda (for example, a counter), the compiler won’t allow it. The variable must be final or effectively final.

Error #3: Forgetting that collection/stream methods do not always modify the original collection. For example, stream().filter(...) does not modify the original list — it returns a new stream. If you want a collection, use collect(Collectors.toList()).

Error #4: Lambda does not match the expected type. If a method expects, for example, a Comparator<T>, and you try to pass a lambda with one parameter (instead of two), you’ll get a compilation error.

Error #5: Losing readability with nested lambdas. If you have a chain of map, filter, forEach, and inside each lambda there’s another lambda, the code becomes unreadable. In such cases, it’s better to split expressions into separate steps or extract parts into methods.

1
Task
JAVA 25 SELF, level 48, lesson 1
Locked
The "SHOUTY" Products System 📢
The "SHOUTY" Products System 📢
1
Task
JAVA 25 SELF, level 48, lesson 1
Locked
Expedition "Great Waters" 🗺️
Expedition "Great Waters" 🗺️
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION