Lambda #
An anonymous function that can be stored in a variable or passed as an argument. Syntax: parameters -> body.
x -> x - 1 // single param, expression body
(x, y) -> x - y // multiple parameters
(x, y) -> { return x - y; } // block body// Passed as an argument to forEach
List<Integer> nums = Arrays.asList(42, 300, 90000);
nums.forEach(n -> System.out.println(n));Functional Interfaces #
Consumer<T>
#
Accepts a single input and returns nothing. The lambda body becomes the implementation of accept.
| Modifier | Return Type | Method | Description |
|---|---|---|---|
void |
accept(T t) |
executes the consumer on the argument | |
default |
Consumer<T> |
andThen(Consumer<? super T> after) |
Chains two consumers in sequence, first this then after |
// accept - executing a single consumer
Consumer<String> print = s -> System.out.println(s);
print.accept("Hello"); // Hello// passing a consumer to forEach
Consumer<String> printConsumer = t -> System.out.println(t);
Stream<String> cities = Stream.of(
"London", "New York", "Mexico City");
cities.forEach(printConsumer);
// London, New York, Mexico City// andThen - chaining consumers
List<String> cities = new ArrayList<>(
Arrays.asList("London", "New York", "Mexico City")
);
Consumer<List<String>> upperCaseConsumer = list -> list
.replaceAll(String::toUpperCase);
Consumer<List<String>> printConsumer = list -> list
.forEach(System.out::println);
upperCaseConsumer.andThen(printConsumer).accept(cities);
// LONDON, NEW YORK, MEXICO CITYSupplier<T>
#
Takes no input and produces a value. The lambda body becomes the implementation of get, executed lazily, only when called.
| Modifier | Return Type | Method | Description |
|---|---|---|---|
T |
get() |
returns the supplied value |
// computation only runs when get() is called
Supplier<String> costly = () -> {
System.out.println("Computing...");
return "result";
};
System.out.println("Before get()");
System.out.println(costly.get());
// runs here
System.out.println("After get()");
// Before get()
// Computing...
// result
// After get()// orElseGet takes a Supplier
// fallback only computed if the Optional is empty
Optional<Double> opt = Optional.empty();
opt.orElseGet(() -> Math.random());
// get() called here, returns random value
Optional<Double> present = Optional.of(3.14);
present.orElseGet(() -> Math.random());
// suppliers get() never calledPredicate<T>
#
Takes one argument and returns a boolean. The lambda body becomes the implementation of test.
| Modifier | Return Type | Method | Description |
|---|---|---|---|
boolean |
test(T t) |
returns true/false for the given argument | |
default |
Predicate<T> |
and(Predicate<? super T> other) |
Both must match |
default |
Predicate<T> |
or(Predicate<? super T> other) |
Either must match |
default |
Predicate<T> |
negate() |
Inverts the result |
static |
<T> Predicate<T> |
isEqual(Object targetRef) |
Matches by equality Objects.equals(Object, Object) |
List<String> names = Arrays.asList("Roman", "Scott", "Alex");
Predicate<String> startsWithS = str -> str.startsWith("S");
Predicate<String> longerThan4 = str -> str.length() > 4;
names.stream()
.filter(startsWithS.and(longerThan4))
.forEach(System.out::println);
// ScottFunction<T, R>
#
Takes one argument of type T and returns a result of type R. The lambda body becomes the implementation of apply.
| Modifier | Return Type | Method | Description |
|---|---|---|---|
R |
apply(T t) |
Transforms the input into the output | |
default |
<V> Function<V,R> |
compose(Function<? super V,? extends T> before) |
Runs before first, then this |
default |
<V> Function<T,V> |
andThen(Function<? super R,? extends V> after) |
Runs this first, then after |
static |
<T> Function<T,T> |
identity() |
Returns a function that always returns its input, equivalent to t -> t |
// apply - transform each element via stream.map
List<String> names = Arrays.asList("Roman", "Scott", "Alex");
List<Integer> lengths = names.stream()
.map(String::length) // Function<String, Integer>
.collect(Collectors.toList());
// [5, 5, 4]// identity()
// when an API requires a Function but no transformation
List<String> names = Arrays.asList("Roman", "Scott", "Alex");
Map<String, Integer> nameLengths = names.stream()
.collect(Collectors
.toMap(Function.identity(), String::length));
// {Roman=5, Scott=5, Alex=4}// passing a Function as a method parameter
static String format(
String str, Function<String, String> fn) {
// Function<Input, Output>
return fn.apply(str);
}
format("Hello", s -> s + "!"); // "Hello!"
format("Hello", s -> s + "?"); // "Hello?"// defining your own functional interface
// SAM can be named anything meaningful
@FunctionalInterface
interface Validator<T> {
boolean validate(T value);
// SAM - lambda becomes the implementation of this
}
static void printIfValid(
String s, Validator<String> validator) {
if (validator.validate(s)) System.out.println(s);
// calls the lambda here
}
// lambda s -> s.contains("@") becomes the implementation of validate()
printIfValid("[email protected]", s -> s.contains("@"));
// [email protected]
printIfValid("notanemail", s -> s.contains("@"));
// (nothing printed)Streams #
A new API for processing collections of data in a declarative way. Consists of a source, followed by zero or more intermediate operations;and a terminal operation. Streams support lazy evaluation, parallel execution, and functional operations such as map, filter, reduce, and collect.
- Stream is not a data structure and it never modifies the underlying data source.
Stream Creation #
// Array
private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
Stream.of(arrayOfEmps);// or obtain stream from already existing list
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();Java 8 added a new stream() method to the Collection interface
// Create a stream out of individual objects
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);// Or using a Stream.builder()
Stream.Builder<Employee> empStreamBuilder =
Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);
Stream<Employee> empStream = empStreamBuilder.build();Intermediate Operators #
| Return Type | Method | Description | Docs | Example |
|---|---|---|---|---|
Stream<R> |
map(Function<T,R>) |
Transforms each element | docs | ↓ |
Stream<T> |
filter(Predicate<T>) |
Keeps elements matching the predicate | docs | ↓ |
Stream<R> |
flatMap(Function<T,Stream<R>>) |
Flattens nested streams into one | docs | ↓ |
Stream<T> |
peek(Consumer<T>) |
Inspects elements without consuming, debug only | docs | ↓ |
Stream<T> |
distinct() |
Removes duplicates using equals() |
docs | ↓ |
Stream<T> |
limit(long n) |
Truncates to at most n elements | docs | ↓ |
Stream<T> |
skip(long n) |
Skips the first n elements | docs | ↓ |
Stream<T> |
sorted() / sorted(Comparator<T>) |
Sorts elements | docs | ↓ |
map #
List<Integer> lengths = Stream.of("Alice", "Bob", "Charlie")
.map(String::length)
.collect(Collectors.toList());
// [5, 3, 7]filter #
List<String> result = Stream.of("Alice", "Bob", "Charlie")
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
// [Alice, Charlie]flatMap #
List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));
List<String> flat = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// [Jeff, Bezos, Bill, Gates, Mark, Zuckerberg]peek #
Similar to forEach(), but unlike it it’s not terminal. Returns a new stream which can be used further.
Stream.of("Alice", "Bob")
.peek(s -> System.out.println("before: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("after: " + s))
.collect(Collectors.toList());
// before: Alice, after: ALICE, before: Bob, after: BOB
peekshould only be used for debugging. Do not use for logic or side effects - it may not be called in all cases.
distinct #
List<Integer> result = Stream.of(2, 5, 3, 2, 4, 3)
.distinct()
.collect(Collectors.toList());
// [2, 5, 3, 4]limit #
// infinite: 1, 2, 3, ...
List<Integer> result = Stream.iterate(1, i -> i + 1)
.skip(3)
.limit(5)
.collect(Collectors.toList());
// [4, 5, 6, 7, 8]skip #
IntStream.range(1, 10)
.skip(5)
.forEach(System.out::println);
// 6, 7, 8, 9sorted #
// Natural order
Stream.of("banana", "apple", "cherry")
.sorted()
.collect(Collectors.toList());
// [apple, banana, cherry]
// Reverse order
Stream.of("banana", "apple", "cherry")
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// [cherry, banana, apple]
// Custom comparator
// sort by string length instead of alphabetically
Stream.of("banana", "apple", "kiwi", "cherry")
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
// [kiwi, apple, banana, cherry]Terminal Operations #
| Return Type | Method | Description | Docs | Example |
|---|---|---|---|---|
void |
forEach(Consumer<T>) |
Performs action on each element | docs | ↓ |
R |
collect(Collector<T,A,R>) |
Gathers elements into a collection | docs | ↓ |
Object[] |
toArray() |
Returns array of elements | docs | ↓ |
Optional<T> |
findFirst() |
Returns first element as Optional | docs | ↓ |
boolean |
anyMatch(Predicate<T>) |
True if any element matches | docs | ↓ |
boolean |
allMatch(Predicate<T>) |
True if all elements match | docs | ↓ |
boolean |
noneMatch(Predicate<T>) |
True if no element matches | docs | ↓ |
long |
count() |
Number of elements | docs | ↓ |
T |
reduce(T, BinaryOperator<T>) |
Aggregates elements into one value | docs | ↓ |
int |
sum() |
Sum of all elements (IntStream) | docs | ↓ |
OptionalInt |
max() |
Maximum element (IntStream) | docs | ↓ |
OptionalInt |
min() |
Minimum element (IntStream) | docs | ↓ |
OptionalDouble |
average() |
Average of elements (IntStream) | docs | ↓ |
IntSummaryStatistics |
summaryStatistics() |
All stats: count, sum, min, max, avg (IntStream) | docs | ↓ |
forEach #
Stream.of("Alice", "Bob", "Charlie")
.forEach(System.out::println);
// Alice, Bob, CharlieIt’s a terminal operation**: after the operation is performed, the stream pipeline is considered consumed and can no longer be used
collect #
List<String> list = Stream.of("Alice", "Bob")
.collect(Collectors.toList());
Set<String> set = Stream.of("Alice", "Bob")
.collect(Collectors.toSet());toArray #
String[] arr = Stream.of("Alice", "Bob")
.toArray(String[]::new);findFirst #
Optional<String> first = Stream.of("Alice", "Bob", "Charlie")
.filter(s -> s.startsWith("B"))
.findFirst();
// Optional[Bob]anyMatch allMatch noneMatch #
List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);
intList
.stream()
.anyMatch(i -> i % 2 == 0);
// true - at least one even
intList
.stream()
.allMatch(i -> i % 2 == 0);
// false - not all even
intList
.stream()
.noneMatch(i -> i % 3 == 0);
// false - 6 is divisible by 3count #
long count = IntStream.range(1, 10)
.filter(x -> x <= 2)
.count(); // 2sum #
int total = IntStream.of(1, 2, 3, 4, 5).sum(); // 15max min #
IntStream.of(3, 1, 4, 1, 5).max().getAsInt(); // 5
IntStream.of(3, 1, 4, 1, 5).min().getAsInt(); // 1average #
IntStream.of(1, 2, 3, 4, 5).average().getAsDouble(); // 3.0reduce #
double total = Stream.of(7.3, 1.5, 4.8)
.reduce(0.0, (a, b) -> a + b); // identity, accumulator
// 13.6summaryStatistics #
IntSummaryStatistics summary =
IntStream.of(7, 2, 19, 88, 73, 4, 10)
.summaryStatistics();
// {count=7, sum=203, min=2, average=29.000, max=88}Collectors #
| Return Type | Method | Description | Docs | Example |
|---|---|---|---|---|
Collector |
toList() |
Collects to a List | docs | ↑ |
Collector |
toSet() |
Collects to a Set | docs | ↓ |
Collector |
toCollection(Supplier) |
Collects to a specific collection type | docs | ↓ |
Collector |
joining(CharSequence) |
Joins string elements | docs | ↓ |
Collector |
partitioningBy(Predicate<T>) |
Splits into {true, false} map |
docs | ↓ |
Collector |
groupingBy(Function<T,K>) |
Groups by a classifier | docs | ↓ |
Collector |
mapping(Function<T,U>, Collector) |
Transforms before collecting | docs | ↓ |
Collector |
reducing(BinaryOperator<T>) |
Aggregates via a binary operator | docs | ↓ |
toSet #
Set<String> set = Stream.of("Alice", "Bob")
.collect(Collectors.toSet());toCollection #
Vector<String> vector = Stream.of("Alice", "Bob")
.collect(Collectors.toCollection(Vector::new));joining #
String result = Stream.of("Alice", "Bob", "Charlie")
.collect(Collectors.joining(", "));
// "Alice, Bob, Charlie"partitioningBy #
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> evenOdd = nums.stream()
.collect(Collectors.partitioningBy(
n -> n % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}groupingBy #
List<String> words = Arrays.asList(
"hi", "hello", "hey", "world", "wow");
Map<Character, List<String>> byLetter = words.stream()
.collect(Collectors.groupingBy(
w -> w.charAt(0)));
// {h=[hi, hello, hey], w=[world, wow]}mapping #
Map<Character, List<Integer>> byLetterLength = words.stream()
.collect(Collectors.groupingBy(
w -> w.charAt(0),
Collectors.mapping(
String::length, Collectors.toList())
));
// {h=[2, 5, 3], w=[5, 3]}reducing #
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
Optional<Integer> product = nums.stream()
.collect(Collectors.reducing((a, b) -> a * b));
// Optional[720] (1*2*3*4*5*6)Stream Pipeline #
A pipeline has three parts: source → intermediate ops → terminal op.
- Intermediate operations are lazy, they return a new stream and don’t execute until a terminal op is called
- Terminal operations consume the stream, it cannot be reused after
// filter is lazy, count triggers execution
long count = Stream.of("Alice", "Bob", "Charlie")
.filter(s -> s.startsWith("A"))
.count(); // 1Short-circuit operations allow infinite streams to complete in finite time:
Stream.iterate(2, i -> i * 2) // infinite: 2, 4, 8, 16, ...
.skip(3)
.limit(5)
.collect(Collectors.toList());
// [16, 32, 64, 128, 256]Lazy Evaluation #
Defining intermediate operations doesn’t execute them, they describe what to do, not when. Execution is deferred until a terminal operation triggers it.
// Pipeline defined - nothing runs yet
Stream<String> pipeline = Stream.of("Alice", "Bob", "Charlie")
.filter(s -> s.length() > 3)
.map(String::toUpperCase);
// Execution happens here when the terminal op is called
pipeline.forEach(System.out::println);
// ALICE, CHARLIEMethod References #
Shorthand for a lambda that just calls an existing method. Syntax: Target::method.
Static Method #
// Math::abs is shorthand for x -> Math.abs(x)
Function<Double, Double> abs = Math::abs;
abs.apply(-3.14); // 3.14Instance Method #
// Method on a specific instance
String prefix = "Hello, ";
Function<String, String> greeter = prefix::concat;
greeter.apply("Alice"); // "Hello, Alice"
// Method on an arbitrary instance
List<String> names = Arrays.asList("charlie", "alice", "bob");
names.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// CHARLIE, ALICE, BOBConstructor #
Supplier<ArrayList<String>> listFactory = ArrayList::new;
listFactory.get(); // new ArrayList<>()
Function<String, StringBuilder> sb = StringBuilder::new;
sb.apply("Hello"); // new StringBuilder("Hello")Default and Static Interface Methods #
Default Methods #
Allow interfaces to ship method implementations. Implementing classes inherit them automatically but can override. The main motivation is backwards compatibility. You can add new methods to an existing interface without breaking all classes that already implement it.
interface Greeter {
String greet(String name);
default String greetLoud(String name) {
return greet(name).toUpperCase();
}
}
class FriendlyGreeter implements Greeter {
public String greet(String name) {
return "Hello, " + name + "!";
}
}
Greeter g = new FriendlyGreeter();
g.greet("Alice");
// "Hello, Alice!"
g.greetLoud("Alice");
// "HELLO, ALICE!" (inherited)Static Methods #
Interfaces can define static utility methods scoped to the interface itself. Unlike default methods, they cannot be overridden and are called directly on the interface, not on an instance. Keeps related helpers co-located with the interface rather than in a separate utility class.
interface MathOp {
int operate(int a, int b);
static MathOp add() {
return (a, b) -> a + b;
}
}
MathOp add = MathOp.add();
add.operate(3, 4); // 7Optional #
A container that may or may not hold a value. Avoids null checks and NullPointerException.
// Creating
Optional<String> empty = Optional.empty();
Optional<String> present = Optional.of("Alice");
Optional<String> maybe = Optional.ofNullable(null);
// same as empty()
// Checking and getting
present.isPresent(); // true
present.get(); // "Alice"
empty.orElse("default"); // "default"
empty.orElseGet(() -> "computed"); // "computed"
empty.orElseThrow(
() -> new RuntimeException("No value")); // throws
// Transforming
Optional<Integer> len = present.map(String::length);
// Optional[5]
present.ifPresent(System.out::println); // Alice
// Filtering
present.filter(n -> n.startsWith("A")); // Optional[Alice]
present.filter(n -> n.startsWith("B")); // Optional.emptyDate and Time API #
A new java.time package replacing the old Date/Calendar. Immutable, thread-safe, and much clearer.
// LocalDate - date only, no time, no timezone
LocalDate today = LocalDate.now(); // 2026-02-22
LocalDate birthday = LocalDate.of(1990, 3, 15);
Period age = Period.between(birthday, today);
age.getYears(); // 35
// LocalTime - time only
LocalTime now = LocalTime.now(); // e.g. 14:30:15
LocalTime meeting = LocalTime.of(9, 30);
Duration gap = Duration.between(meeting, now);
gap.toMinutes(); // minutes since 09:30
// LocalDateTime - date + time, no timezone
LocalDateTime dt = LocalDateTime.now();
LocalDateTime future = dt.plusDays(7).plusHours(3);
// ZonedDateTime - with timezone
ZonedDateTime london = ZonedDateTime
.now(ZoneId.of("Europe/London"));
ZonedDateTime tokyo = london
.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// Formatting and parsing
DateTimeFormatter fmt =
DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formatted = today.format(fmt); // "22/02/2026"
LocalDate parsed = LocalDate.parse("15/03/1990", fmt);Source #
- W3schools.com: Java Lambda Expressions
- medium.com: Understanding Java 8’s Consumer, Supplier, Predicate and Function
- stackify.com: A Guide to Java Streams in Java 8: In-Depth Tutorial With Examples
- Youtube: Java 8 STREAMS Tutorial
- Java 8 – How to sort list with stream.sorted()
- Java 8 Method Reference