The Optional
class is a container object which may or may not contain a value. It is introduced to prevent NullPointerExceptions and to represent the potential absence of a value in a more explicit manner.
Key Methods:
Optional.of(value)
: Creates an Optional
with a non-null value.Optional.ofNullable(value)
: Creates an Optional
that can hold a null value.isPresent()
: Checks if the value is present (not null).ifPresent(consumer)
: Executes a consumer if the value is present.orElse(value)
: Returns the value if present, otherwise returns the provided fallback.orElseThrow(exceptionSupplier)
: Throws a specified exception if the value is not present.map()
: Transforms the value if present, returns an Optional
of the result.Example:
1 | Optional<String> name = Optional.ofNullable(getName()); |
A Functional Interface is an interface with just one abstract method (SAM), and it can have multiple default or static methods.
Lambda expressions allow you to provide clear and concise syntax for writing anonymous methods (implementations of functional interfaces).
This enables functional programming in Java, promoting cleaner and more concise code. Lambda expressions allow passing behavior as arguments to methods.
Syntax of Lambda Expression:
1 | (parameters) -> expression |
Example:
1 |
|
Common Functional Interfaces:
Runnable
, Callable
, Comparator
, Consumer
, Supplier
, Function
, Predicate
, UnaryOperator
, BinaryOperator
.
In Java 8, interfaces can have default methods and static methods:
Default methods: These methods can have a body and allow adding methods to interfaces without breaking existing implementations.
Static methods: These are methods that belong to the interface itself, not to the objects implementing the interface.
Default methods allow you to add new functionality to interfaces without requiring all implementing classes to provide their own implementation.
Example:
1 | interface MyInterface { |
The Stream API provides a high-level abstraction for processing sequences of elements, such as collections, in a functional style. It allows you to process large data sets in a declarative way, using operations like map, filter, and reduce.
It allows you to express complex operations like filtering, sorting, and transforming data in a concise and readable manner, while also enabling parallel processing for performance optimization.
Key Operations:
filter()
, map()
, sorted()
).forEach()
, collect()
, reduce()
).Intermediate operations are lazy, meaning they don’t modify the source data until a terminal operation is invoked. They transform the data into another stream.
filter(Predicate<? super T> predicate)
Purpose: Filters elements based on a condition (predicate).
Usage: It returns a stream that contains only the elements that match the given condition.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); |
map(Function<? super T, ? extends R> mapper)
Purpose: Transforms each element of the stream by applying a function.
Usage: It returns a new stream with the results of applying the given function to the elements.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry"); |
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
Purpose: Similar to map()
, but it flattens nested streams into a single stream.
Usage: It is used when each element of the stream is a collection itself, and you want to flatten them into a single stream.
1 | EditList<List<String>> listOfLists = Arrays.asList( |
distinct()
Purpose: Removes duplicate elements from the stream.
Usage: It returns a new stream with distinct elements (no duplicates).
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5); |
sorted()
Purpose: Sorts the elements of the stream.
Usage: It returns a new stream with the elements sorted in ascending order by default.
1 | EditList<Integer> numbers = Arrays.asList(5, 3, 1, 4, 2); |
peek(Consumer<? super T> action)
Purpose: Allows you to inspect the elements as they pass through the stream, without modifying them.
Usage: It’s often used for debugging purposes.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
Terminal operations produce a result or side effect and terminate the stream pipeline.
forEach(Consumer<? super T> action)
Purpose: Performs an action on each element of the stream.
Usage: It processes each element of the stream and performs the specified action.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry"); |
collect(Collector<? super T, A, R> collector)
Purpose: Collects the elements of the stream into a collection (like List
, Set
, etc.) or any other type of result.
Usage: It is a very powerful and flexible operation to gather results from the stream.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
reduce(T identity, BinaryOperator<T> accumulator)
Purpose: Performs a reduction on the elements of the stream using an associative accumulation function.
Usage: It combines elements of the stream into a single result.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
count()
Purpose: Returns the number of elements in the stream.
Usage: It is a straightforward way to count elements.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry"); |
anyMatch(Predicate<? super T> predicate)
Purpose: Checks if any element in the stream matches the given condition.
Usage: It returns true
if any element matches the predicate, otherwise false
.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
allMatch(Predicate<? super T> predicate)
Purpose: Checks if all elements in the stream match the given condition.
Usage: It returns true
if all elements match the predicate, otherwise false
.
1 | EditList<Integer> numbers = Arrays.asList(2, 4, 6, 8); |
noneMatch(Predicate<? super T> predicate)
Purpose: Checks if no elements in the stream match the given condition.
Usage: It returns true
if no element matches the predicate, otherwise false
.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
The Collectors
class in Java provides a variety of predefined implementations for collecting results from a stream.
toList()
Purpose: Collects the stream elements into a List
.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry"); |
toSet()
Purpose: Collects the stream elements into a Set
(removes duplicates).
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5); |
joining()
Purpose: Joins the stream elements into a single String
with an optional delimiter.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry"); |
groupingBy()
Purpose: Groups the elements of the stream by a classifier function.
1 | EditList<String> words = Arrays.asList("apple", "banana", "cherry", "apricot"); |
partitioningBy()
Purpose: Partitions the elements of the stream into two groups based on a predicate.
1 | EditList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); |
The forEach()
method is a default method introduced in the Iterable
interface. It provides a simple way to iterate over a collection.
It allows for a more readable and functional-style iteration over collections, and you can use it with lambda expressions to define actions.
1 | codeList<String> names = Arrays.asList("Alice", "Bob", "Charlie"); |
Note: forEach()
can be used with streams as well to apply an action to each element of a stream:
1 | numbers.stream().forEach(n -> System.out.println(n)); |
Method References: A shorthand notation for passing a method as an argument to a function. It improves the readability of the code.
1 | // Method Reference: Equivalent to (x) -> System.out.println(x) |
Date and Time API: Java 8 introduced a new Date-Time API (java.time.*
) that is immutable and thread-safe, addressing the flaws in the old Date
and Calendar
classes.
LocalDate, LocalTime, LocalDateTime: These are used for representing date and time without timezone.
Instant, ZonedDateTime: These are used for representing date and time with timezone and precise moments in time.
1 | LocalDate date = LocalDate.now(); |
Nashorn JavaScript Engine: Java 8 introduced a new JavaScript engine called Nashorn, which is faster and more efficient than the previous Rhino engine.
CompletableFuture: For asynchronous programming, CompletableFuture
allows you to write non-blocking asynchronous code that can be easily combined and composed.
Collectors: The Collectors
utility class offers various factory methods to create common collectors, such as toList()
, joining()
, groupingBy()
, and partitioningBy()
.