Lesson 158 min read

Lambdas & Streams

Modern Java — elegant, concise, powerful

Lambdas — Functions as Values

Before Java 8, if you wanted to pass a piece of behavior to a method, you had to create a whole class. It was like writing a full letter when all you needed was a sticky note. Lambdas are those sticky notes — tiny, anonymous functions you can pass around.

The syntax is simple: (parameters) -> expression

Some examples:

  • (a, b) -> a + b — takes two values, returns their sum
  • (s) -> s.length() — takes a string, returns its length
  • () -> System.out.println("Hello!") — takes nothing, prints hello

Lambdas work with functional interfaces — interfaces with exactly one abstract method. You can use them with collections like Maps and Sets to write concise data-processing code. Java provides many built-in ones like Predicate (test), Function (transform), Consumer (act on), and Comparator (compare).

Lambda Basics & Functional Interfaces

import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
public class Main {
// A functional interface — has exactly ONE abstract method
interface MathOperation {
int operate(int a, int b);
}
static int calculate(int a, int b, MathOperation op) {
return op.operate(a, b);
}
public static void main(String[] args) {
// Lambda as a variable
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;
MathOperation max = (a, b) -> a > b ? a : b;
System.out.println("5 + 3 = " + calculate(5, 3, add));
System.out.println("5 * 3 = " + calculate(5, 3, multiply));
System.out.println("max(5,3) = " + calculate(5, 3, max));
System.out.println("---");
// Sorting with lambdas — so much cleaner!
List<String> names = new ArrayList<>(Arrays.asList(
"Charlie", "Alice", "Bob", "Diana"
));
// Sort alphabetically
Collections.sort(names, (a, b) -> a.compareTo(b));
System.out.println("Alphabetical: " + names);
// Sort by length
Collections.sort(names, (a, b) -> a.length() - b.length());
System.out.println("By length: " + names);
// forEach with lambda
System.out.print("Each: ");
names.forEach(n -> System.out.print(n + " "));
System.out.println();
}
}
Output
5 + 3 = 8
5 * 3 = 15
max(5,3) = 5
---
Alphabetical: [Alice, Bob, Charlie, Diana]
By length: [Bob, Alice, Diana, Charlie]
Each: Bob Alice Diana Charlie

Streams — Processing Data Like a Pipeline

Think of a stream as a conveyor belt in a factory. Data items go in one end, pass through various stations (filter, transform, sort), and come out the other end as a finished product.

Streams don't change the original data — they create a new result. Understanding how chained operations affect time complexity helps you write efficient pipelines. They're also lazy: nothing actually happens until you call a terminal operation (like collect(), forEach(), or count()).

Common stream operations:

  • filter() — keep only items that match a condition
  • map() — transform each item into something else
  • sorted() — put items in order
  • reduce() — combine all items into one result
  • collect() — gather results into a list, set, or other collection

Streams in Action

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Filter: keep only even numbers
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Evens: " + evens);
// Map: square each number
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares: " + squares);
// Chain them: filter + map + sort
List<Integer> result = numbers.stream()
.filter(n -> n > 3) // keep 4,5,6,7,8,9,10
.map(n -> n * 2) // double them
.filter(n -> n < 16) // keep under 16
.sorted() // sort
.collect(Collectors.toList());
System.out.println("Chained: " + result);
// Reduce: sum all numbers
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
System.out.println("Sum: " + sum);
// Count
long count = numbers.stream()
.filter(n -> n > 5)
.count();
System.out.println("Numbers > 5: " + count);
}
}
Output
Evens: [2, 4, 6, 8, 10]
Squares: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
Chained: [8, 10, 12, 14]
Sum: 55
Numbers > 5: 5

Streams with Strings & Method References

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> words = Arrays.asList(
"hello", "world", "java", "streams", "are", "awesome", "hi"
);
// Method reference :: — shorthand for simple lambdas
// Instead of: s -> s.toUpperCase()
List<String> upper = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Upper: " + upper);
// Filter words longer than 3 chars, sort, join into one string
String sentence = words.stream()
.filter(w -> w.length() > 3)
.sorted()
.collect(Collectors.joining(", "));
System.out.println("Long words: " + sentence);
// Find first match
String first = words.stream()
.filter(w -> w.startsWith("a"))
.findFirst()
.orElse("none found");
System.out.println("First 'a' word: " + first);
// Check conditions
boolean anyLong = words.stream().anyMatch(w -> w.length() > 5);
boolean allShort = words.stream().allMatch(w -> w.length() < 10);
System.out.println("Any word > 5 chars? " + anyLong);
System.out.println("All words < 10 chars? " + allShort);
// Get distinct lengths
List<Integer> lengths = words.stream()
.map(String::length)
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("Distinct lengths: " + lengths);
}
}
Output
Upper: [HELLO, WORLD, JAVA, STREAMS, ARE, AWESOME, HI]
Long words: awesome, hello, java, streams, world
First 'a' word: are
Any word > 5 chars? true
All words < 10 chars? true
Distinct lengths: [2, 3, 4, 5, 7]
Note: Method references (::) are just shorthand for simple lambdas. String::toUpperCase is the same as s -> s.toUpperCase(). Use them when the lambda just calls a single method — it makes your code read like English. System.out::println, Integer::parseInt, Math::max — clean and clear.

Quick check

What is a lambda expression?
File I/O