1. Java 8 Streams Overview
Optional
Optionality is expressed via a special wrapper class.
Stream API
A particular iterator-style API used to efficiently process collections of items.
Streams
To process objects of a collection, the Stream concept was introduced in Java 8.
Stream s = c.stream();
The stream() method is a default method added to the Collection interface in Java 8.
Stream is an interface present in java.util.stream.
2. Difference Between Collection and Stream
| Collection | Stream |
|---|---|
| Represents a group of individual objects as a single entity. | Used to process a group of objects from a collection. |
| Primarily represents/stores the group of objects. | Provides operations for processing the objects. |
Can be converted to a Stream using stream(). |
Obtained from a collection through collection.stream(). |
3. Java 8 Stream Features
| Java 8 Feature | Stream / Related Capability |
|---|---|
| Lambda Expressions | Intermediate operations |
| Functional Interfaces | Filter — Predicate |
| Method References | Map — Function |
| Default Methods in Interfaces | flatMap — Function<T, Stream<R>> |
| Static Methods in Interfaces | Distinct uses Object.equals() and hashCode() |
| Stream API | sorted() — Comparator<T> |
| Date and Time API (java.time) | limit(n) |
| Optional Class | skip(n) |
| Nashorn JavaScript Engine | peek — Consumer<T> |
| Parallel Streams | Terminal Operations |
| CompletableFuture | collect(Collectors) |
| Collectors Utility | forEach(Consumer), count(), anyMatch(Predicate<T>) |
| More terminal operations | allMatch(), noneMatch(), findFirst(), findAny(), reduce() |
4. Stream Processing Phases
The source describes processing Stream objects in two phases:
Configuration
Configuration can be performed using either:
Processing
After configuration, the objects can be processed using several methods such as:
5. Filtering with filter()
The filter() method is used to filter elements from a collection based on a boolean condition.
public Stream filter(Predicate<T> t)
The Predicate can be a boolean-valued function or Lambda Expression.
Stream s = c.stream();
Stream s1 =
s.filter(i -> i % 2 == 0);
6. Mapping with map()
If we want to create a separate/new object for every object present in the collection based on our requirement, we can use the map() method.
public Stream map(Function f);
The function can be represented by a Lambda Expression.
Stream s = c.stream();
Stream s1 =
s.map(i -> i + 10);
7. flatMap()
The source describes flatMap() as a combination of transformation and flattening.
The notes distinguish the two operations by describing map as processing each value, whereas flatMap produces individual elements from the mapped result.
8. Stream Processing Operations
| Operation | Purpose |
|---|---|
| collect() | Collects stream elements into a specified result/collection. |
| count() | Counts the number of elements. |
| sorted() | Sorts stream elements. |
| min() | Finds minimum according to a comparator. |
| max() | Finds maximum according to a comparator. |
| forEach() | Processes each element. |
| toArray() | Converts stream elements into an array. |
| Stream.of() | Creates a Stream from specified values. |
| reduce() | Combines elements using an accumulator such as BinaryOperator. |
| anyMatch() | Checks whether any element matches a predicate. |
| allMatch() | Checks whether all elements match a predicate. |
| noneMatch() | Checks whether no elements match a predicate. |
| findFirst() | Retrieves the first element if present. |
| findAny() | Retrieves an element, mainly useful with parallel streams. |
9. collect()
The collect() method collects elements from the stream and adds them to the result/collection specified by the argument.
List<String> names =
Arrays.asList(
"Alice",
"Bob",
"Charlie"
);
List<String> result =
names.stream()
.collect(Collectors.toList());
10. String.join() and Collectors.joining()
String.join()
String result =
String.join(
"-",
"2015",
"10",
"31"
);
List<String> list =
Arrays.asList(
"java",
"python",
"nodejs",
"ruby"
);
String result2 =
String.join(", ", list);
Collectors.joining()
List<String> list =
Arrays.asList(
"java",
"python",
"nodejs",
"ruby"
);
String result =
list.stream()
.map(x -> x)
.collect(
Collectors.joining(" | ")
);
11. Optional Class
Optional is a container object used to contain non-null objects. It can represent a value as available or not available instead of repeatedly checking for null values.
Example
public class OptionalDemo {
public static void main(String[] args) {
String[] words =
new String[10];
Optional<String> checkNull =
Optional.ofNullable(words[5]);
if (checkNull.isPresent()) {
String word =
words[5].toLowerCase();
System.out.print(word);
} else {
System.out.println(
"word is null"
);
}
}
}
12. Optional Methods
Creation Methods
| Method | Description |
|---|---|
| empty() | Returns an empty Optional instance. |
| of(T value) | Returns an Optional containing the specified present non-null value. |
| ofNullable(T value) | Returns an Optional describing the value if non-null; otherwise returns an empty Optional. |
Core / Utility Methods
| Method | Description |
|---|---|
| equals(Object obj) | Indicates whether another object is equal to this Optional. |
| filter(Predicate<? super T> predicate) | If a value is present and matches the predicate, returns an Optional describing it; otherwise returns empty. |
| flatMap(Function<? super T, Optional<U>> mapper) | Applies an Optional-bearing mapping function if a value is present. |
| get() | Returns the contained value if present; otherwise throws NoSuchElementException. |
| hashCode() | Returns the hash code of the value, or 0 if no value is present. |
| ifPresent(Consumer<? super T>) | If a value is present, invokes the specified consumer. |
| isPresent() | Returns true if a value is present. |
| map(Function<? super T, ? extends U> mapper) | Applies a mapping function if a value is present and returns an Optional of the result. |
| orElse(T other) | Returns the value if present; otherwise returns the supplied other value. |
| orElseGet(Supplier<? extends T> other) | Returns the value if present; otherwise invokes the supplier. |
| orElseThrow(Supplier<? extends X> exceptionSupplier) | Returns the value if present; otherwise throws an exception created by the supplier. |
| toString() | Returns a non-empty string representation suitable for debugging. |
13. Advantages of Java 8 Optional
Null Checks
Null checks are not required in the same repetitive form.
Runtime Safety
The source presents Optional as helping avoid NullPointerException scenarios.
Clean APIs
Can help develop clean and neat APIs.
Less Boilerplate
Reduces repetitive null-handling boilerplate.
Optional<String> gender =
Optional.of("MALE");
String answer1 = "Yes";
String answer2 = null;
System.out.println(
"Non-Empty Optional: " + gender
);
System.out.println(
"Non-Empty Optional: Gender value = "
+ gender.get()
);
System.out.println(
"Empty Optional: "
+ Optional.empty()
);
System.out.println(
"ofNullable on Non-Empty Optional: "
+ Optional.ofNullable(answer1)
);
System.out.println(
"ofNullable on Empty Optional: "
+ Optional.ofNullable(answer2)
);
14. StringJoiner
StringJoiner is a new class added in Java 8 under the java.util package. It is useful for joining Strings using a delimiter, prefix and suffix.
The source lists two constructors:
StringJoiner(CharSequence delimiter)
StringJoiner(
CharSequence delimiter,
CharSequence prefix,
CharSequence suffix
)
Example of StringJoiner
private static String PREFIX = "{";
private static String SUFFIX = "}";
StringJoiner joiner =
new StringJoiner(
", ",
PREFIX,
SUFFIX
);
joiner
.add("Core Java")
.add("Spring Boot")
.add("Angular");
System.out.println(
joiner.toString()
);
15. join() Method in String Class
Java 8 introduced the join() method in the String class. It concatenates the given Strings using the specified delimiter and returns a new String.
public static String join(
CharSequence delimiter,
CharSequence... elements
)
Example
public class Example {
public static void main(String args[]) {
String str =
String.join(
"-",
"You",
"are",
"learning",
"join",
"method",
"in Java 8"
);
System.out.println(str);
}
}
Output:
You-are-learning-join-method-in Java 8
Example of a List using join()
import java.util.List;
import java.util.Arrays;
public class Example {
public static void main(String args[]) {
// Converting an array of String to the list
List<String> list =
Arrays.asList(
"Virat",
"Ricky",
"Peterson",
"Watson"
);
String names =
String.join(" | ", list);
System.out.println(names);
}
}
16. Java 8 HashMap / Map New Methods
| Method | Description / Example |
|---|---|
| forEach | Iterates over each key-value pair.map.forEach((key, value) -> System.out.println(key + " = " + value)); |
| getOrDefault | Returns the value for the key, or a default value if the key is not found.int count = map.getOrDefault("orange", 0); |
| putIfAbsent | Puts a value only if the key is not already associated with a value.map.putIfAbsent("apple", 5); |
| computeIfAbsent | Computes and puts a value if the key is absent.map.computeIfAbsent("orange", k -> 10); |
| computeIfPresent | Computes a new value only if the key is already present.map.computeIfPresent("banana", (k, v) -> v + 2); |
| compute | Updates the value using a remapping function.map.compute("apple", (k, v) -> (v == null) ? 1 : v + 1); |
| merge | Combines the existing value with a new value using a merge function.map.merge("apple", 1, Integer::sum); |
| replace | Replaces an entry if it exists and optionally matches the old value.map.replace("apple", 2, 4); |
| remove | Removes an entry only if the key is mapped to the specified value.map.remove("banana", 3); |
17. Intermediate Operations
The source provides examples for several intermediate Stream operations.
filter()
public class FilterDemo {
public static void main(String[] args) {
List<String> names =
List.of(
"Alice",
"Bob",
"Amanda",
"David"
);
List<String> names =
names.stream()
.filter(
name ->
name.startsWith("A")
)
.collect(
Collectors.toList()
);
System.out.println(names);
}
}
map()
public class MapDemo {
public static void main(String[] args) {
List<String> names =
List.of("Alice", "Bob");
List<Integer> nameLengths =
names.stream()
.map(String::length)
.collect(
Collectors.toList()
);
System.out.println(nameLengths);
}
}
distinct()
public class FlatMapDemo {
public static void main(String[] args) {
List<Integer> nums =
List.of(1, 2, 2, 3);
List<Integer> unique =
nums.stream()
.distinct()
.collect(
Collectors.toList()
);
System.out.println(unique);
}
}
flatMap()
public class DistinctDemo {
public static void main(String[] args) {
List<List<String>> data =
List.of(
List.of("a", "b"),
List.of("c", "d")
);
List<String> flat =
data.stream()
.flatMap(List::stream)
.collect(
Collectors.toList()
);
System.out.println(flat);
}
}
sorted()
public class SortedDemo {
public static void main(String[] args) {
List<String> names =
List.of(
"John",
"Alice",
"Bob"
);
List<String> sorted =
names.stream()
.sorted()
.collect(
Collectors.toList()
);
System.out.println(sorted);
}
}
limit()
public class LimitDemo {
public static void main(String[] args) {
List<Integer> limited =
Stream.of(1, 2, 3, 4, 5)
.limit(3)
.collect(
Collectors.toList()
);
System.out.println(limited);
}
}
skip()
public class SkipDemo {
public static void main(String[] args) {
List<Integer> skipped =
Stream.of(1, 2, 3, 4, 5)
.skip(2)
.collect(
Collectors.toList()
);
System.out.println(skipped);
}
}
18. Terminal Operations
peek()
public class PeekDemo {
public static void main(String[] args) {
List<String> result =
List.of(
"apple",
"banana",
"cherry"
)
.stream()
.peek(
System.out::println
)
.map(
String::toUpperCase
)
.collect(
Collectors.toList()
);
System.out.println(result);
}
}
mapToInt()
public class MapToIntDemo {
public static void main(String[] args) {
int sum =
List.of(
"a",
"bb",
"ccc"
)
.stream()
.mapToInt(String::length)
.sum();
System.out.println(sum);
}
}
forEach()
List<String> names =
Arrays.asList(
"Alice",
"Bob",
"Charlie"
);
names.stream()
.forEach(
System.out::println
);
toArray()
String[] nameArray =
names.stream()
.toArray(String[]::new);
reduce()
List<Integer> numbers =
Arrays.asList(
1, 2, 3, 4
);
int sum =
numbers.stream()
.reduce(
0,
Integer::sum
);
collect()
List<String> list =
names.stream()
.collect(
Collectors.toList()
);
Set<String> set =
names.stream()
.collect(
Collectors.toSet()
);
String joined =
names.stream()
.collect(
Collectors.joining(", ")
);
min() and max()
Optional<Integer> min =
numbers.stream()
.min(
Integer::compareTo
);
Optional<Integer> max =
numbers.stream()
.max(
Integer::compareTo
);
count()
long count =
names.stream().count();
anyMatch(), allMatch(), noneMatch()
boolean anyStartsWithA =
names.stream()
.anyMatch(
s -> s.startsWith("A")
);
boolean allHaveLength3 =
names.stream()
.allMatch(
s -> s.length() == 3
);
boolean noneStartsWithZ =
names.stream()
.noneMatch(
s -> s.startsWith("Z")
);
findFirst()
Optional<String> first =
names.stream()
.findFirst();
findAny()
Optional<String> any =
names.parallelStream()
.findAny();
forEachOrdered()
names.parallelStream()
.forEachOrdered(
System.out::println
);
19. Custom Collector
The final example in the PDF demonstrates a custom Collector that uses StringJoiner to combine stream elements with a delimiter.
Collector<String, StringJoiner, String>
customCollector =
Collector.of(
() ->
new StringJoiner(" | "),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString
);
String result =
names.stream()
.collect(customCollector);
Example output shown in the source:
Alice | Bob | Charlie
20. Quick Revision
| Topic | Key Point |
|---|---|
| Collection | Represents a group of objects as a single entity. |
| Stream | Used to process objects from a collection. |
| stream() | Creates a Stream from a Collection. |
| filter() | Filters elements using a Predicate condition. |
| map() | Transforms each element using a Function. |
| flatMap() | Combines transformation and flattening. |
| distinct() | Removes duplicate elements. |
| sorted() | Sorts stream elements. |
| limit() | Restricts the number of elements processed. |
| skip() | Skips the specified number of elements. |
| peek() | Performs an action on elements while processing. |
| collect() | Collects stream results into a desired form. |
| reduce() | Combines elements into a single result. |
| count() | Counts stream elements. |
| min()/max() | Finds minimum/maximum values. |
| anyMatch() | Checks whether any element matches a condition. |
| allMatch() | Checks whether all elements match a condition. |
| noneMatch() | Checks whether no elements match a condition. |
| findFirst() | Retrieves the first element as Optional. |
| findAny() | Retrieves an element as Optional. |
| Optional | Container for representing a value as present or absent. |
| StringJoiner | Joins Strings using delimiter, prefix and suffix. |
| String.join() | Joins Strings with a specified delimiter. |
| Map enhancements | forEach, getOrDefault, putIfAbsent, compute, merge, replace, remove, etc. |
Stream Pipeline Memory Trick
Source
Collection, List, Set, Stream.of(), etc.
Intermediate
filter, map, flatMap, distinct, sorted, limit, skip, peek.
Terminal
collect, forEach, reduce, count, min, max, match operations, find operations.