Java’s Collections API is one of the most-used parts of the language. Since Java 8, lambda expressions and Streams API have made working with collections more expressive, concise, and powerful.
This tutorial covers how to use lambdas with List
, Map
, and Set
, using real-world examples and idiomatic best practices.
🧠 Why Use Lambdas with Collections?
Benefit | Description |
---|---|
✅ Readability | Reduces boilerplate logic for filtering, transforming, aggregating |
⚡ Performance | Enables lazy evaluation and parallel processing with streams |
🧩 Functional Design | Encourages immutability and declarative logic |
🔧 Flexibility | Easily compose logic using predicates, functions, consumers |
🔍 Functional Interfaces at the Core
Lambdas require functional interfaces — interfaces with a single abstract method.
Common interfaces with Collections:
Consumer<T>
— forEach actionsPredicate<T>
— filteringFunction<T, R>
— mappingBiConsumer<K, V>
— map iterationUnaryOperator<T>
— element replacement
📝 Working with List
1. forEach with Consumer
List<String> names = List.of("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println("Hello " + name));
Or using a method reference:
names.forEach(System.out::println);
2. Filtering with Predicate
List<String> filtered = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
3. Mapping with Function
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
4. Sorting with Comparator
List<String> list = new ArrayList<>(names);
list.sort((a, b) -> a.compareToIgnoreCase(b));
Or use method reference:
list.sort(String::compareToIgnoreCase);
5. Replacing with UnaryOperator
list.replaceAll(name -> name.toUpperCase());
🗺️ Working with Map
1. forEach with BiConsumer
Map<String, Integer> map = Map.of("Alice", 30, "Bob", 25);
map.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));
2. Replacing Values
map.replaceAll((name, age) -> age + 1);
3. Filtering Entries
Map<String, Integer> adults = map.entrySet().stream()
.filter(entry -> entry.getValue() >= 18)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
4. Transforming Keys or Values
List<String> allNames = new ArrayList<>(map.keySet());
List<Integer> allAges = new ArrayList<>(map.values());
🧺 Working with Set
1. forEach with Consumer
Set<String> countries = Set.of("India", "USA", "UK");
countries.forEach(c -> System.out.println("Country: " + c));
2. Filtering with Stream
Set<String> filtered = countries.stream()
.filter(c -> c.length() > 3)
.collect(Collectors.toSet());
3. Transforming Elements
Set<String> upperSet = countries.stream()
.map(String::toUpperCase)
.collect(Collectors.toSet());
🔄 Composing Functional Interfaces
You can combine multiple operations using andThen
, compose
, or
, etc.
Function<String, String> trim = String::trim;
Function<String, String> toUpper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(toUpper);
⚠️ Exception Handling Inside Lambdas
Consumer<String> safeWrite = s -> {
try {
Files.writeString(Path.of("file.txt"), s);
} catch (IOException e) {
e.printStackTrace();
}
};
🧠 Variable Capture & Scoping
String prefix = "Name: ";
names.forEach(n -> System.out.println(prefix + n));
prefix
must be effectively final (i.e., not reassigned later).
📌 What's New in Java?
Java 8
- Lambda expressions
- Functional interfaces
- Streams API
Optional
,Collectors
,Function
, etc.
Java 9
Optional.ifPresentOrElse()
Map.of()
factory method
Java 11+
var
in lambda parameters- Enhanced
Collectors
API
Java 21
- Structured concurrency + virtual threads
- Scoped values + better async lambda support
✅ Conclusion and Key Takeaways
- Lambdas simplify common operations on
List
,Map
, andSet
. - Stream operations allow chaining transformations and filters.
- Functional interfaces make collections more powerful and flexible.
- Leverage method references and composition for cleaner code.
❓ FAQ
Q1: Can I use lambdas on any collection?
Yes, if the collection supports streams or forEach()
.
Q2: What's the best place to start using lambdas with collections?
Start with forEach
, then move to filter
, map
, and collect
.
Q3: Are lambdas faster than loops?
Not always. Streams are often more readable, but not necessarily faster unless using parallel streams.
Q4: Can I use lambdas with mutable collections?
Yes, but be careful with concurrent modifications.
Q5: How do I handle checked exceptions in lambdas?
Wrap them in try-catch or write a utility wrapper.
Q6: Are sets and maps streamable like lists?
Yes, via stream()
on Set
or entrySet()
for Map
.
Q7: Can lambdas be used in sorting?
Absolutely. Use Comparator
lambdas with sort()
.
Q8: Can I create a custom functional interface for collection ops?
Yes, especially when built-in interfaces don’t match your use case.
Q9: Are method references better than lambdas?
They are shorter and often more readable for simple method calls.
Q10: Should I always use lambdas with collections?
Use them when they make your code more expressive and concise—don’t force them into every scenario.