filter()
)The filter()
method in Java Streams is used to select elements that match a given predicateโa condition expressed as a boolean
test. It is an intermediate, lazy operation, meaning it doesn't execute until a terminal operation is called.
Filtering is one of the most common stream operations and is useful for removing unwanted values, ignoring nulls, or selecting data that meets specific criteria.
Stream<T> filter(Predicate<? super T> predicate)
The method returns a new stream consisting of the elements that match the provided predicate.
Filter names starting with "A":
import java.util.List;
public class FilterExample1 {
public static void main(String[] args) {
List.of("Alice", "Bob", "Andrew", "Charlie")
.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
// Output: Alice, Andrew
}
}
import java.util.List;
public class FilterExample2 {
public static void main(String[] args) {
List.of("Java", "", null, "Streams", " ")
.stream()
.filter(s -> s != null && !s.trim().isEmpty())
.forEach(System.out::println);
// Output: Java, Streams
}
}
Select even numbers from a list:
import java.util.List;
public class FilterExample3 {
public static void main(String[] args) {
List.of(1, 2, 3, 4, 5, 6)
.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
// Output: 2, 4, 6
}
}
filter()
Because filter()
is lazy, no elements are actually tested until a terminal operation like forEach()
or collect()
is invoked. This allows efficient and optimized processing, especially when combined with short-circuiting methods like limit()
.
Filtering is foundational to stream processing and helps write clear, expressive, and functional-style code.
map()
)The map()
operation in Java Streams is used to transform each element in a stream into another form. It performs a one-to-one mapping, meaning that for every input element, exactly one output element is produced.
This method is essential for data transformation in a pipeline, whether you're converting types, extracting object fields, or applying computations.
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
It takes a Function
that transforms elements from type T
to type R
.
import java.util.List;
public class MapExample1 {
public static void main(String[] args) {
List.of("Java", "Stream", "API")
.stream()
.map(String::length)
.forEach(System.out::println);
// Output: 4, 6, 3
}
}
Each string is mapped to its integer length.
Suppose you have a list of Person
objects and want to get their names:
import java.util.List;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name; this.age = age;
}
}
public class MapExample2 {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alice", 30),
new Person("Bob", 25)
);
people.stream()
.map(p -> p.name)
.forEach(System.out::println);
// Output: Alice, Bob
}
}
Mapping string numbers to integers:
import java.util.List;
public class MapExample3 {
public static void main(String[] args) {
List.of("1", "2", "three", "4")
.stream()
.map(s -> {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return null;
}
})
.filter(n -> n != null)
.forEach(System.out::println);
// Output: 1, 2, 4
}
}
This example highlights a common pitfall: returning null
in a map()
function. While possible, it must be handled carefullyโtypically with a filter()
step to remove nulls.
map()
is transformational, not filtering.null
values explicitly.Mapping is central to making stream pipelines powerful and expressive.
sorted()
)The sorted()
method in Java Streams is used to order elements in a stream. It can sort using natural ordering (like alphabetical or numerical) or a custom Comparator
. Sorting is a stateful intermediate operation, which means it needs to examine the entire stream before it can produce resultsโthis makes it inherently less lazy than operations like map()
or filter()
.
Stream<T> sorted(); // Natural order (Comparable)
Stream<T> sorted(Comparator<? super T> c); // Custom Comparator
Javaโs sorted()
operation is stable, meaning if two elements are considered equal under the sorting criteria, their original order is preserved. This is important when chaining multiple sorts or preserving input consistency.
import java.util.List;
public class SortedExample1 {
public static void main(String[] args) {
List.of("Banana", "Apple", "Cherry")
.stream()
.sorted()
.forEach(System.out::println);
// Output: Apple, Banana, Cherry
}
}
Here, strings are sorted alphabetically using their natural order (defined by Comparable
).
import java.util.List;
import java.util.Comparator;
public class SortedExample2 {
public static void main(String[] args) {
List.of(5, 2, 9, 1)
.stream()
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
// Output: 9, 5, 2, 1
}
}
This example demonstrates sorting numbers in descending order using a custom comparator.
import java.util.List;
import java.util.Comparator;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name; this.age = age;
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class SortedExample3 {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Alice", 22)
);
people.stream()
.sorted(Comparator.comparing((Person p) -> p.name)
.thenComparing(p -> p.age))
.forEach(System.out::println);
// Output:
// Alice (22)
// Alice (30)
// Bob (25)
}
}
This demonstrates a stable, multi-level sort: first by name, then by age.
sorted()
without arguments uses natural order (must implement Comparable
).sorted(Comparator)
provides full control over ordering.distinct()
)The distinct()
method in Java Streams is used to remove duplicate elements from a stream. It retains only the first occurrence of each element, as determined by the equals()
method. This makes distinct()
an effective way to enforce uniqueness in a stream pipeline.
equals()
and hashCode()
for comparison.IntStream
โthough similar results can be achieved using boxed()
and then distinct()
.import java.util.List;
public class DistinctExample1 {
public static void main(String[] args) {
List.of("apple", "banana", "apple", "cherry", "banana")
.stream()
.distinct()
.forEach(System.out::println);
// Output: apple, banana, cherry
}
}
equals()
and hashCode()
)import java.util.List;
import java.util.Objects;
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name; this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person p)) return false;
return age == p.age && Objects.equals(name, p.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return name + " (" + age + ")";
}
}
public class DistinctExample2 {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Alice", 30)
);
people.stream()
.distinct()
.forEach(System.out::println);
// Output: Alice (30), Bob (25)
}
}
Without properly overriding equals()
and hashCode()
, the above example would treat all objects as distinctโeven if their contents are identical.
Use boxed()
to convert primitive streams to object streams:
import java.util.stream.IntStream;
public class DistinctExample3 {
public static void main(String[] args) {
IntStream.of(1, 2, 2, 3, 3, 3)
.boxed()
.distinct()
.forEach(System.out::println);
// Output: 1, 2, 3
}
}
distinct()
uses a Set
to track duplicates.equals()
and hashCode()
are efficient and correct to avoid subtle bugs or poor performance.Using distinct()
effectively allows for clean, deduplicated data streams, especially when handling input from user lists, files, or APIs.
limit()
, skip()
)The limit()
and skip()
methods in Java Streams provide control over how many elements are processed or where processing starts in a stream. These operations are particularly useful in scenarios like pagination, data sampling, or working with infinite streams.
limit(n)
n
elements.n
elements, it returns all of them.skip(n)
n
elements from the stream.Both are intermediate, lazy operations that are evaluated only when a terminal operation is invoked.
import java.util.List;
public class LimitExample {
public static void main(String[] args) {
List.of("apple", "apricot", "banana", "avocado", "almond", "blueberry")
.stream()
.filter(s -> s.startsWith("a"))
.limit(3)
.forEach(System.out::println);
// Output: apple, apricot, avocado
}
}
Here, we filter for strings starting with "a"
and take only the first 3 matching results.
skip()
and limit()
import java.util.List;
public class PaginationExample {
public static void main(String[] args) {
List<String> items = List.of("Item1", "Item2", "Item3", "Item4", "Item5", "Item6");
int page = 2;
int pageSize = 2;
items.stream()
.skip((page - 1) * pageSize)
.limit(pageSize)
.forEach(System.out::println);
// Output (Page 2): Item3, Item4
}
}
This simulates pagination, retrieving page 2 of a 2-item-per-page listing.
limit()
is essential when working with infinite streams to avoid non-terminating execution.
import java.util.stream.Stream;
public class InfiniteStreamExample {
public static void main(String[] args) {
Stream.iterate(1, n -> n + 1)
.filter(n -> n % 2 == 0)
.skip(3) // Skip first 3 even numbers: 2, 4, 6
.limit(5) // Take next 5: 8, 10, 12, 14, 16
.forEach(System.out::println);
}
}
limit(n)
to constrain output size.skip(n)
to ignore early elements, enabling offsets.