java 8 examples
1. Lambda expressions
Lambda expressions allow you to define a method's implementation directly inline, reducing boilerplate code and improving readability, especially when working with functional interfaces. The lambda expressions provide a compact and concise way to represent anonymous functions.
- Example 1: Sorting a List
- Before Java 8:
Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return a.compareTo(b); } });
- With Java 8 Lambda Expression:
List<String> names = Arrays.asList("John", "Alice", "Bob", "Eve"); Collections.sort(names, (a, b) -> a.compareTo(b)); System.out.println(names); // Output: [Alice, Bob, Eve, John]
- Before Java 8:
- Example2: Implementing a Runnable Interface
- Before Java 8:
Runnable r1 = new Runnable() { @Override public void run() { System.out.println("Running with anonymous inner class"); } }; r1.run(); // Output: Running with anonymous inner class
- With Java 8 Lambda Expression:
Runnable r2 = () -> System.out.println("Running with Lambda Expression"); r2.run(); // Output: Running with Lambda Expression
- Before Java 8:
2. Stream API
The Java 8 Stream API provides a way to process sequences of data elements in a declarative, functional style. Streams can process data in a declarative manner, similar to SQL queries on databases.
- Example: Filtering and Mapping
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamAPIExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> squaredEvenNumbers = numbers.stream() .filter(n -> n % 2 == 0) // Filters even numbers .map(n -> n * n) // Maps to square .collect(Collectors.toList()); // Collects results into a list System.out.println(squaredEvenNumbers); // Output: [4, 16, 36, 64, 100] } }
3. Optional class
The
Optional
class (java.util.Optional
) is a container object that may or may not contain a non-null value. It was introduced to help developers deal with null values more gracefully and to prevent NullPointerException
s. The provided code example demonstrates how to use Optional.ofNullable
to avoid null pointer issues, ifPresent
to execute code only if a value is present, and orElse
to provide a default value if the Optional
is empty. 4. Default and static methods in interfaces
Default methods in interfaces allow you to add new methods with implementations to existing interfaces without breaking backward compatibility. Static methods are utility methods tied to the interface itself. The example shows an interface
Calculator
with an abstract method add
and a default method multiply
, along with a class implementing the interface and using both methods. 5. Method references
Method references offer a concise syntax for referring to methods by name, simplifying code when used with Streams and lambdas.
The examples illustrate using method references with
forEach
for printing elements and with Collections.sort
for sorting a list, providing a more compact alternative to lambda expressions. 6. Date and Time API (
java.time
)Java 8's new
java.time
package provides a more intuitive and thread-safe way to handle dates and times compared to older classes. The example demonstrates obtaining the current date, time, and date-time, working with time zones, and formatting a date-time using DateTimeFormatter
.6. Using
forEach()
with IterablesJava 8 added a
forEach()
method to the Iterable
interface (which Collection
extends). This allows you to iterate over collections using lambda expressions, replacing traditional for-each
loops. - Example: Printing List Elements
import java.util.Arrays; import java.util.List; public class ForEachExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // Using forEach with a lambda expression names.forEach(name -> System.out.println("Hello, " + name)); /* Output: Hello, Alice Hello, Bob Hello, Charlie */ // Using forEach with a method reference (even more concise) names.forEach(System.out::println); /* Output: Alice Bob Charlie */ } }
7. Stream
flatMap()
The
flatMap()
operation is an intermediate operation in the Stream API used to flatten nested collections or structures into a single stream. It maps each element to a stream of its constituent elements and then concatenates those streams. - Example: Flattening a List of Lists
You can useflatMap
to transform aList<List<String>>
into a singleList<String>
. - Example: Finding All Cities Employees Have Lived In
By defining anEmployee
class with a list of cities,flatMap
can be used to collect all unique cities from a list of employees.
8. Parallel streams
Parallel streams allow processing elements concurrently using multiple threads, potentially improving performance on multi-core processors for large datasets. You can get a parallel stream with
parallelStream()
or parallel()
. - Example: Summing Numbers with Parallel Stream
You can compare the performance of sequential and parallel streams by summing even numbers in a list. While a small example might not show a difference, larger datasets can benefit significantly from parallel processing.
9.
CompletableFuture
CompletableFuture
simplifies asynchronous, non-blocking programming in Java 8. It allows chaining asynchronous operations, handling exceptions, and combining results. - Example: Asynchronous Task Execution and Chaining
You can demonstrate chainingCompletableFuture
tasks to simulate fetching a user ID and then fetching user details asynchronously. This showcases non-blocking operations.
10.
Collectors
class (Advanced Examples)The
Collectors
class provides methods to accumulate stream elements into data structures or perform aggregations. - Example: Grouping and Averaging with
Collectors.groupingBy()
You can group employees by age and calculate the average salary for each group, or partition them based on age.
11. Using
String.join()
Java 8 added a static
join()
method to the String
class, which allows you to concatenate multiple strings with a delimiter. This is very useful when working with collections of strings.- Example: Joining Strings from a List
import java.util.Arrays; import java.util.List; public class StringJoinerExample { public static void main(String[] args) { List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry"); String result = String.join(" - ", fruits); System.out.println(result); // Output: Apple - Banana - Cherry String csvNumbers = String.join(",", "1", "2", "3"); System.out.println(csvNumbers); // Output: 1,2,3 } }
12. Stream
reduce()
for AggregationThe
reduce()
method in the Stream API is a powerful terminal operation that combines all elements of a stream into a single result. It's useful for calculating sums, finding maximums/minimums, concatenating strings, and other aggregation tasks. The reduce
can be used for calculations like sum or min/max.- Example: Summing Numbers and Concatenating Strings
import java.util.Arrays; import java.util.List; import java.util.Optional; public class StreamReduceExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Summing elements (no initial value - returns Optional) Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b); sum.ifPresent(s -> System.out.println("Sum: " + s)); // Output: Sum: 15 // Summing elements (with initial value) int sumWithInitial = numbers.stream().reduce(0, (a, b) -> a + b); System.out.println("Sum with initial: " + sumWithInitial); // Output: Sum with initial: 15 List<String> words = Arrays.asList("Java", "8", "is", "awesome"); String combined = words.stream().reduce("", (a, b) -> a + " " + b).trim(); System.out.println("Combined string: " + combined); // Output: Combined string: Java 8 is awesome } }
13. Stream
distinct()
The
distinct()
intermediate operation returns a stream consisting of the distinct elements of the given stream, based on their equals()
method.- Example: Finding Unique Elements
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamDistinctExample { public static void main(String[] args) { List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 1); List<Integer> distinctNumbers = numbersWithDuplicates.stream() .distinct() // Filters out duplicates .collect(Collectors.toList()); System.out.println(distinctNumbers); // Output: [1, 2, 3, 4, 5] List<String> namesWithDuplicates = Arrays.asList("Alice", "Bob", "Alice", "Charlie"); List<String> distinctNames = namesWithDuplicates.stream() .distinct() .collect(Collectors.toList()); System.out.println(distinctNames); // Output: [Alice, Bob, Charlie] } }
14. Stream
sorted()
The
sorted()
intermediate operation returns a stream consisting of the elements of this stream, sorted according to natural order or a custom Comparator
. Geekster explains that sorted()
arranges stream elements based on natural order or a provided comparator.- Example: Sorting Numbers and Custom Objects
import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class StreamSortedExample { public static void main(String[] args) { List<Integer> unsortedNumbers = Arrays.asList(5, 2, 8, 1, 9); List<Integer> sortedNumbers = unsortedNumbers.stream() .sorted() // Natural order sorting .collect(Collectors.toList()); System.out.println("Sorted Numbers: " + sortedNumbers); // Output: Sorted Numbers: List<String> words = Arrays.asList("banana", "apple", "grape", "orange", "pear"); List<String> sortedWords = words.stream() .sorted() // Natural order sorting .collect(Collectors.toList()); System.out.println("Sorted Words: " + sortedWords); // Output: Sorted Words: [apple, banana, grape, orange, pear] // Custom sorting by string length List<String> sortedByLength = words.stream() .sorted(Comparator.comparing(String::length)) .collect(Collectors.toList()); System.out.println("Sorted by Length: " + sortedByLength); // Output: Sorted by Length: [pear, apple, grape, orange, banana] } }
15.
Collectors.groupingBy()
and partitioningBy()
These
Collectors
are powerful for transforming streams into Map
structures based on certain criteria.- Example: Grouping by Length and Partitioning by Condition
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class CollectorsGroupingExample { public static void main(String[] args) { List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant"); // Grouping by string length Map<Integer, List<String>> groupedByLength = words.stream() .collect(Collectors.groupingBy(String::length)); System.out.println("Grouped by Length: " + groupedByLength); /* Output: Grouped by Length: {3=[cat, dog], 5=[apple], 6=[banana], 8=[elephant]} */ // Partitioning words based on whether their length is even Map<Boolean, List<String>> partitionedByEvenLength = words.stream() .collect(Collectors.partitioningBy(s -> s.length() % 2 == 0)); System.out.println("Partitioned by Even Length: " + partitionedByEvenLength); /* Output: Partitioned by Even Length: {false=[apple, cat, dog], true=[banana, elephant]} */ } }
16.
Collectors.toMap()
with Duplicate KeysWhen converting a stream to a
Map
using Collectors.toMap()
, you need to handle duplicate keys if they might occur. Baeldung describes using a merge function in Collectors.toMap
to resolve conflicts when duplicate keys appear.- Example: Handling Duplicate Keys in
toMap()
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; public class ToMapDuplicateKeyExample { public static void main(String[] args) { List<String> items = Arrays.asList("apple", "banana", "apple", "orange"); // Strategy 1: Keep the first encountered value Map<String, Integer> firstOccurrenceMap = items.stream() .collect(Collectors.toMap( Function.identity(), // Key mapper: the string itself String::length, // Value mapper: the length of the string (oldValue, newValue) -> oldValue // Merge function: keep the old value )); System.out.println("First occurrence map: " + firstOccurrenceMap); // Output: First occurrence map: {banana=6, apple=5, orange=6} // Strategy 2: Keep the last encountered value Map<String, Integer> lastOccurrenceMap = items.stream() .collect(Collectors.toMap( Function.identity(), String::length, (oldValue, newValue) -> newValue // Merge function: keep the new value )); System.out.println("Last occurrence map: " + lastOccurrenceMap); // Output: Last occurrence map: {banana=6, apple=5, orange=6} } }
17. Stream
peek()
The
peek()
intermediate operation allows you to perform an action on each element of a stream as it is consumed, without altering the stream's elements. It's useful for debugging or logging.- Example: Debugging Stream Pipeline
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamPeekExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> processedNumbers = numbers.stream() .filter(n -> n % 2 == 0) // Filters even numbers .peek(n -> System.out.println("Filtered: " + n)) // Debugging/logging .map(n -> n * n) // Maps to square .peek(n -> System.out.println("Mapped: " + n)) // Debugging/logging .collect(Collectors.toList()); System.out.println("Final List: " + processedNumbers); /* Output: Filtered: 2 Mapped: 4 Filtered: 4 Mapped: 16 Final List: [4, 16] */ } }
18. Stream
anyMatch()
, allMatch()
, noneMatch()
These are terminal operations that return a boolean value based on whether elements in the stream match a given predicate.
- Example: Checking Conditions on Elements
import java.util.Arrays; import java.util.List; public class StreamMatchExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(2, 4, 6, 8, 10); boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0); System.out.println("Any even? " + anyEven); // Output: Any even? true boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); System.out.println("All even? " + allEven); // Output: All even? true boolean noneOdd = numbers.stream().noneMatch(n -> n % 2 != 0); System.out.println("None odd? " + noneOdd); // Output: None odd? true List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); boolean containsZ = names.stream().anyMatch(name -> name.contains("Z")); System.out.println("Contains 'Z'? " + containsZ); // Output: Contains 'Z'? false } }
19. Date and Time API -
TemporalAdjusters
TemporalAdjusters
provide static methods to easily adjust date and time objects, e.g., finding the first day of the month, the next Monday, etc.- Example: Finding Specific Dates
import java.time.DayOfWeek; import java.time.LocalDate; import java.time.temporal.TemporalAdjusters; public class TemporalAdjusterExample { public static void main(String[] args) { LocalDate today = LocalDate.now(); System.out.println("Today: " + today); // Output: Today: 2025-07-19 // First day of the month LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth()); System.out.println("First day of month: " + firstDayOfMonth); // Output: First day of month: 2025-07-01 // Next Tuesday LocalDate nextTuesday = today.with(TemporalAdjusters.next(DayOfWeek.TUESDAY)); System.out.println("Next Tuesday: " + nextTuesday); // Output: Next Tuesday: 2025-07-22 // Last day of the year LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear()); System.out.println("Last day of year: " + lastDayOfYear); // Output: Last day of year: 2025-12-31 } }
20. Combining Stream Operations
The real power of the Stream API lies in chaining multiple intermediate operations followed by a terminal operation to perform complex data manipulations concisely.
- Example: Finding Average Length of Unique Words Starting with 'A'
import java.util.Arrays; import java.util.List; import java.util.OptionalDouble; public class CombinedStreamExample { public static void main(String[] args) { List<String> words = Arrays.asList("Apple", "banana", "Apricot", "apple", "Avocado", "cherry"); OptionalDouble averageLength = words.stream() .map(String::toLowerCase) // Convert to lowercase .filter(s -> s.startsWith("a")) // Filter words starting with 'a' .distinct() // Get unique words .mapToInt(String::length) // Convert to IntStream of lengths .average(); // Calculate average averageLength.ifPresent(avg -> System.out.println("Average length of unique words starting with 'a': " + avg)); // Output: Average length of unique words starting with 'a': 6.0 (from apple, apricot, avocado) } }
These examples further illustrate the versatility and expressiveness of Java 8 features, allowing developers to write more functional, readable, and efficient code