Collections & Streams in Java 8
Overview
Java 8 introduced a new API called Streams, which allows developers to process collections of objects in a functional manner. Streams provide a clean and efficient way to handle data transformations and perform operations such as filtering, mapping, and reducing collections.
Creating Streams
Streams can be created from various data sources, such as collections, arrays, and even custom generators.
Example: Creating Streams
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; public class StreamCreationExample { public static void main(String[] args) { // From a collection List list = Arrays.asList("a", "b", "c"); Stream streamFromList = list.stream(); // From an array String[] array = {"d", "e", "f"}; Stream streamFromArray = Arrays.stream(array); // Displaying the streams streamFromList.forEach(System.out::println); streamFromArray.forEach(System.out::println); } }
Stream Operations
Streams support two types of operations: intermediate and terminal operations.
Intermediate Operations
Intermediate operations return a new stream and are lazy, meaning they are not executed until a terminal operation is invoked. Some common intermediate operations are:
filter
: Filters elements based on a predicate.map
: Transforms elements using a function.flatMap
: Transforms each element into a stream and flattens the result.distinct
: Removes duplicate elements.sorted
: Sorts elements based on a comparator.limit
: Limits the number of elements.skip
: Skips the first N elements.
Terminal Operations
Terminal operations produce a result or a side effect and terminate the stream pipeline. Some common terminal operations are:
forEach
: Performs an action for each element.collect
: Collects elements into a collection.reduce
: Reduces elements to a single value using a binary operator.count
: Counts the number of elements.anyMatch
,allMatch
,noneMatch
: Match elements based on a predicate.findFirst
,findAny
: Finds an element.
Example: Intermediate and Terminal Operations
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamOperationsExample { public static void main(String[] args) { List list = Arrays.asList("a", "b", "c", "d", "e", "f", "a"); // Intermediate operations List result = list.stream() .filter(s -> s.compareTo("c") > 0) .map(String::toUpperCase) .distinct() .sorted() .collect(Collectors.toList()); // Terminal operation result.forEach(System.out::println); } }
Optional Class
The Optional
class in Java 8 is a container object which may or may not contain a non-null value. It provides methods to deal with the presence or absence of a value in a more explicit and less error-prone way compared to using null
.
Creating Optional Objects
Optional objects can be created using the Optional.of()
, Optional.ofNullable()
, and Optional.empty()
methods.
Example: Creating Optional Objects
import java.util.Optional; public class OptionalCreationExample { public static void main(String[] args) { // Creating an Optional with a non-null value Optional nonEmptyOptional = Optional.of("Hello, World!"); System.out.println(nonEmptyOptional); // Creating an Optional with a potentially null value Optional nullableOptional = Optional.ofNullable(null); System.out.println(nullableOptional); // Creating an empty Optional Optional emptyOptional = Optional.empty(); System.out.println(emptyOptional); } }
Working with Optional Objects
The Optional
class provides several methods to work with the contained value, such as isPresent()
, ifPresent()
, orElse()
, and orElseGet()
.
Example: Working with Optional Objects
import java.util.Optional; public class OptionalUsageExample { public static void main(String[] args) { Optional optional = Optional.of("Hello, World!"); // Checking if a value is present if (optional.isPresent()) { System.out.println("Value is present: " + optional.get()); } else { System.out.println("Value is absent"); } // Performing an action if a value is present optional.ifPresent(value -> System.out.println("Value: " + value)); // Providing a default value if the value is absent String defaultValue = optional.orElse("Default Value"); System.out.println("Default Value: " + defaultValue); // Providing a default value using a supplier String suppliedValue = optional.orElseGet(() -> "Supplied Value"); System.out.println("Supplied Value: " + suppliedValue); // Throwing an exception if the value is absent try { String value = optional.orElseThrow(() -> new RuntimeException("Value is absent")); System.out.println("Value: " + value); } catch (RuntimeException e) { System.out.println(e.getMessage()); } } }
Conclusion
The Stream API and Optional class in Java 8 provide powerful tools for processing collections and handling optional values. By leveraging these features, you can write more concise, expressive, and robust code.