Java Streams - Revisions
What is a Stream?
- A stream in Java is a sequence of data that takes input from Collections or IO Channels.
- Streams don’t change the original data structure.
- A Stream Pipeline is the operation (STREAM OPERATIONS) that run on a stream to produce a result.
- Each intermediate operation is lazily executed and returns a stream as a result.
- Terminal operations mark the end of the stream and return the result.
- Finite Streams have a limit.
- Infinite Streams are like sunrise/sunset cycles.
Important Notes
- Re-stream a List each time: Don’t re-use a stream because “creating” a Stream just points at the existing data structure behind the scenes; it does not copy the data.
-
Do not support indexed access:
findFirst()can give the top element, but not the second, third, or last element. - Simple syntax to build a List or array from a Stream.
Three Common Ways to Create a Stream
-
From ArrayList
List<Student> students = new ArrayList<>(); Stream<Student> studentStream = students.stream(); -
From Array of Objects (not array of primitives)
Student[] students = {....}; Stream<Student> studentStream = Stream.of(students).map(…).filter(…).other(…); // No Terminal Operator -
From Individual Elements
Student s1 = …; Student s2 = …; Stream<Student> studentStream = Stream.of(s1, s2, ...).map(…).filter(…).other(…); // No Terminal Operator
Outputting Streams
Getting a List out of a Stream
List<SomeClass> list = someStream.map(…).collect(Collectors.toList());
Getting an Array out of a Stream
// Fill elements into an Array from a Stream
Student[] studentArray = someStream.map(someLambda).toArray(Student[]::new);
String[] strArray = stringStream.filter(...).map(...).toArray(String[]::new);
What Cannot be Done with Streams forEach
-
Multiple Loops are not possible as
forEachis a Terminal operation consuming a Stream. -
Local variable modification:
list.stream().forEach(e -> total += e);- Do this with
mapandreduce. - Or use the built-in
summethod ofDoubleStreamorIntStream.
- Do this with
-
Cannot use
breakorreturnwithinforEachloop.
Stream Operations
Source
- SOURCE: Where the stream comes from.
Sure! Here’s a completed and organized explanation of Intermediate Operations in Java Streams, including the ones you listed:
Intermediate Operations
INTERMEDIATE OPERATIONS: These transform a stream into another stream. Streams use lazy evaluation, meaning intermediate operations are not executed until a terminal operation is invoked.
Common Intermediate Operations:
-
filter(Predicate<T> predicate)
Selects elements that match the given predicate.
✅ Example:stream.filter(x -> x > 10) -
map(Function<T, R> mapper)
Transforms each element using the provided function.
✅ Example:stream.map(String::toUpperCase) -
flatMap(Function<T, Stream<R>> mapper)
Flattens nested structures by mapping each element to a stream and then flattening the result.
✅ Example:stream.flatMap(list -> list.stream()) -
distinct()
Removes duplicate elements from the stream (based onequals()method).
✅ Example:stream.distinct() -
limit(long maxSize)
Truncates the stream to contain no more thanmaxSizeelements.
✅ Example:stream.limit(5) -
peek(Consumer<T> action)
Performs the given action on each element as they are consumed from the stream. Useful for debugging.
✅ Example:stream.peek(System.out::println) -
sorted()/sorted(Comparator<T> comparator)
Returns a stream with elements sorted in natural order or using a custom comparator.
✅ Example:stream.sorted()orstream.sorted(Comparator.reverseOrder())
Terminal Operations
These operations produce a result or a side-effect and consume the stream, making it unusable afterward. They trigger the processing of all intermediate operations.
Common Terminal Operations:
-
collect(Collector<T, A, R> collector)
Performs a mutable reduction operation on the elements of the stream and returns a collection or another result container.
✅ Example:stream.collect(Collectors.toList()) -
forEach(Consumer<T> action)
Performs the given action for each element of the stream.
✅ Example:stream.forEach(System.out::println) -
reduce(BinaryOperator<T> accumulator)
Reduces the stream to a single value using an associative accumulation function.
✅ Example:stream.reduce((a, b) -> a + b) -
count()
Returns the number of elements in the stream.
✅ Example:stream.count() -
min(Comparator<T> comparator)
Returns the minimum element of the stream according to the provided comparator, wrapped in anOptional.
✅ Example:stream.min(Comparator.naturalOrder()) -
max(Comparator<T> comparator)
Returns the maximum element of the stream according to the provided comparator, wrapped in anOptional.
✅ Example:stream.max(Comparator.naturalOrder()) -
toArray()
Returns an array containing the elements of the stream.
✅ Example:stream.toArray() -
anyMatch(Predicate<T> predicate)
Returnstrueif any elements of the stream match the provided predicate.
✅ Example:stream.anyMatch(x -> x > 10) -
allMatch(Predicate<T> predicate)
Returnstrueif all elements of the stream match the provided predicate.
✅ Example:stream.allMatch(x -> x > 0) -
noneMatch(Predicate<T> predicate)
Returnstrueif no elements of the stream match the provided predicate.
✅ Example:stream.noneMatch(x -> x < 0) -
findFirst()
Returns the first element of the stream, if present, wrapped in anOptional.
✅ Example:stream.findFirst() -
findAny()
Returns any element of the stream, useful in parallel streams, wrapped in anOptional.
✅ Example:stream.findAny()
Map vs FlatMap
Employee with Address Object
@Data
class Address {
private String city;
}
Using map
Scenario: Transforming a list of Employee objects to a list of their names.
class Employee {
private Address address;//One Address Object
}
public void testMain(List<Employee> employees) {
//Extract the address of every employhee
List<Address> addreses = employees.stream()
.map(Employee::address)
.collect(Collectors.toList());
}
Using flatMap
merge multiple streams into one
Stream<String> stream1 = Stream.of("a", "b");
Stream<String> stream2 = Stream.of("c", "d");
Stream<Stream<String>> streams = Stream.of(stream1, stream2);
List<String> combined = streams
.flatMap(s -> s)
.collect(Collectors.toList());
Scenario: Given a list of Employee objects, each with a list of Address objects within it,
return a single list of all addresses.
class Employee {
private List<Address> addresses;//Each employee has multiple addresses, including empty
}
public void testFlatMap(List<Employee> employees) {
List<Address> allAddresses = employees.stream()
.flatMap(employee -> employee.getAddresses().stream())//Flatmap needs a Stream that it can stitch together.
.collect(Collectors.toList());
}
Streams with Map
The entry set returns a collection (Set) which can then be used to create the stream
Map<String, Integer> filteredMap = map.entrySet()
.stream()
.filter(entry -> entry.getValue() > 1)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));