R4R Style Learning Portal

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]
      
       
  • 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
      
       
 
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 NullPointerExceptions. 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 Iterables
Java 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 use flatMap to transform a List<List<String>> into a single List<String>.
  • Example: Finding All Cities Employees Have Lived In
    By defining an Employee 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 chaining CompletableFuture 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 Aggregation
The 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 Keys
When 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