I'm always excited to take on new projects and collaborate with innovative minds.

Email

contact@niteshsynergy.com

Website

https://www.niteshsynergy.com/

Java 8 Features

Java 8 introduced several new features and enhancements that revolutionized the way developers write Java code. Here's a breakdown of the key features:

Java 8 introduced several new features and enhancements that revolutionized the way developers write Java code. Here's a breakdown of the key features:

 

Java 8 introduced several new features and enhancements that revolutionized the way developers write Java code. Here's a breakdown of the key features:

 

1. Lambda Expressions

  • Description: Enables functional programming by allowing you to write anonymous functions (short and concise).
  • Syntax:

(parameters) -> expression
List<String> names = Arrays.asList("Nitesh", "Kr", "Synergy");
names.forEach(name -> System.out.println(name));

 

 

 

package com.niteshsynergy.java8;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.*;

public class Java8Demo1 {
   public static void main(String[] args) {

       // Lambda for Addition
       Calculator add = (a, b) -> a + b;

       // Lambda for Subtraction
       Calculator subtract = (a, b) -> a - b;

       // Lambda for Multiplication
       Calculator multiply = (a, b) -> a * b;

       // Lambda for Division (Handles division by zero with a fallback value of 0)
       Calculator divide = (a, b) -> b != 0 ? a / b : 0;

       // Demonstrate usage of Calculator lambdas
       System.out.println("Addition: " + add.calculate(10, 5));       // Output: 15
       System.out.println("Subtraction: " + subtract.calculate(10, 5)); // Output: 5
       System.out.println("Multiplication: " + multiply.calculate(10, 5)); // Output: 50
       System.out.println("Division: " + divide.calculate(10, 5));       // Output: 2

       // List of names for sorting demonstration
       List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

       // Lambda for custom sorting by string length
       names.sort((a, b) -> Integer.compare(a.length(), b.length()));

       System.out.println("Sorted Names: " + names); // Output: Sorted list by name length

       // Predicate to check if a number is positive
       Predicate<Integer> predicate = i -> i > 0; 
       if (predicate.test(10)) // Test predicate with value 10
           System.out.println("Positive");
       else
           System.out.println("Negative");

       // Consumer to print a string with a welcome message
       Consumer<String> consumer = s -> {
           System.out.println(s + " welcome"); // Print message with "welcome"
       };
       consumer.accept("Nitesh"); // Output: Nitesh welcome

       // Consumer to check if a number is even
       Consumer<Integer> integerConsumer = number -> {
           if (number % 2 == 0)
               System.out.println("Even");
       };
       integerConsumer.accept(2); // Output: Even

       // List of integers for predicate demonstration
       List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

       // Predicate to check if a number is positive
       Predicate<Integer> isPositive = n -> n > 0;

       System.out.println(isPositive.test(5));  // Output: true
       System.out.println(isPositive.test(-3)); // Output: false

       // List of names for consumer demonstration
       List<String> names1 = Arrays.asList("Alice", "Bob", "Charlie");
       Consumer<String> printName = name -> System.out.println(name);

       // Use forEach with Consumer to print each name
       names.forEach(printName); // Output: Alice, Bob, Charlie

       // Supplier to generate a random number
       Supplier<Integer> randomNumber = () -> new Random().nextInt(100);

       System.out.println(randomNumber.get()); // Output: Random number (e.g., 42)
       System.out.println(randomNumber.get()); // Output: Another random number (e.g., 85)

       // Function to convert a string to uppercase
       Function<String, String> toUpperCase = str -> str.toUpperCase();

       System.out.println(toUpperCase.apply("hello")); // Output: HELLO
       System.out.println(toUpperCase.apply("java"));  // Output: JAVA

       // BiFunction to concatenate two strings
       BiFunction<String, String, String> concatenate = (str1, str2) -> str1 + str2;

       System.out.println(concatenate.apply("Hello, ", "World!")); // Output: Hello, World!
       System.out.println(concatenate.apply("Java ", "8"));       // Output: Java 8
   }
}

// Functional Interface for basic calculator operations
@FunctionalInterface
interface Calculator {
   int calculate(int a, int b);
}
 

    1. Functional Interface (Calculator):
      • Defines the calculate method for custom arithmetic operations (addition, subtraction, etc.).
    2. Lambdas for Arithmetic Operations (add, subtract, multiply, divide):
      • Implements operations like a + b (line: Calculator add = ...).
    3. Predicate Example (Predicate<Integer>):
      • test method checks if a number is positive (Predicate<Integer> predicate = ...).
    4. Consumer for String (Consumer<String>):
      • accept method appends "welcome" and prints a message (Consumer<String> consumer = ...).
    5. Consumer for Integer (Consumer<Integer>):
      • Checks if a number is even (Consumer<Integer> integerConsumer = ...).
    6. Sorting Names (names.sort):
      • Sorts a list by string length using a lambda (names.sort((a, b) -> ...)).
    7. forEach Method (names.forEach(printName)):
      • Iterates over a list of names and prints each (Consumer<String> printName = ...).
    8. Supplier for Random Numbers (Supplier<Integer>):
      • Generates random numbers (Supplier<Integer> randomNumber = ...).
    9. Function for String Transformation (Function<String, String>):
      • Converts strings to uppercase (Function<String, String> toUpperCase = ...).
    10. BiFunction for Concatenation (BiFunction<String, String, String>):
      • Combines two strings into one (BiFunction<String, String, String> concatenate = ...).


 

2. Functional Interfaces

  • Description: An interface with a single abstract method (SAM).
  • Key Annotation: @FunctionalInterface (Optional but recommended).
  • Common Interfaces:
    • Predicate<T>: Takes one argument, returns a boolean.
    • Consumer<T>: Takes one argument, returns nothing.
    • Function<T, R>: Takes one argument, returns a result.

@FunctionalInterface
interface Greeting {
   void sayHello(String name);
}

Greeting greeting = name -> System.out.println("Hello, " + name);
greeting.sayHello("Nitesh");
 

List of Predefined Functional Interfaces in Java 8

Java 8 provides several predefined functional interfaces in the java.util.function package:

1. Consumer

  • Purpose: Represents an operation that takes a single input and performs an action without returning a result.
  • Methods:
    • void accept(T t)

2. Supplier

  • Purpose: Represents a function that supplies a value without taking any input.
  • Methods:
    • T get()

3. Predicate

  • Purpose: Represents a condition (boolean-valued function) on a single input.
  • Methods:
    • boolean test(T t)

4. Function

  • Purpose: Represents a function that takes one input and produces one output.
  • Methods:
    • R apply(T t)

5. BiFunction

  • Purpose: Represents a function that takes two inputs and produces one output.
  • Methods:
    • R apply(T t, U u)

6. UnaryOperator

  • Purpose: A specialization of Function for operations on a single operand that return the same type.
  • Methods:
    • T apply(T t)

7. BinaryOperator

  • Purpose: A specialization of BiFunction for operations on two operands of the same type that return the same type.
  • Methods:
    • T apply(T t1, T t2)

8. BiConsumer

  • Purpose: Represents an operation that takes two inputs and performs an action without returning a result.
  • Methods:
    • void accept(T t, U u)

9. DoublePredicate, IntPredicate, LongPredicate

  • Purpose: Specialized versions of Predicate for double, int, and long types.
  • Methods:
    • boolean test(double d) (or int/long)

10. DoubleConsumer, IntConsumer, LongConsumer

  • Purpose: Specialized versions of Consumer for double, int, and long types.
  • Methods:
    • void accept(double d) (or int/long)

11. ToDoubleFunction, ToIntFunction, ToLongFunction

  • Purpose: Converts an input to a double, int, or long respectively.
  • Methods:
    • double applyAsDouble(T t) (or int/long)

12. ToDoubleBiFunction, ToIntBiFunction, ToLongBiFunction

  • Purpose: Converts two inputs to a double, int, or long respectively.
  • Methods:
    • double applyAsDouble(T t, U u) (or int/long)

13. DoubleSupplier, IntSupplier, LongSupplier

  • Purpose: Specialized versions of Supplier for double, int, and long types.
  • Methods:
    • double getAsDouble() (or int/long)

14. ObjDoubleConsumer, ObjIntConsumer, ObjLongConsumer

  • Purpose: Takes an object and a primitive type (double, int, or long) and performs an operation.
  • Methods:
    • void accept(T t, double d) (or int/long)

15. Comparator (from java.util)

  • Purpose: Compares two objects for ordering.
  • Methods:
    • int compare(T o1, T o2)

These interfaces are building blocks for functional programming in Java and are commonly used with lambda expressions and method references.

 

Predefined Functional Interfaces in Java (Before Java 8 and Other Libraries)

1. Runnable (Introduced in Java 1.0)

  • Purpose: Represents a task that can be run concurrently in a thread.
  • Method:
    • void run()

2. Callable (Introduced in Java 5)

  • Purpose: Represents a task that can return a result and may throw an exception.
  • Method:
    • V call() throws Exception

3. Comparator (Introduced in Java 1.2, present in java.util package)

  • Purpose: Compares two objects for ordering.
  • Method:
    • int compare(T o1, T o2)

4. ActionListener (Introduced in Java 1.0)

  • Purpose: Represents an action event listener for GUI components.
  • Method:
    • void actionPerformed(ActionEvent e)

5. EventListener (Introduced in Java 1.0)

  • Purpose: Represents the general interface for all event listeners.
  • Method: No method (marker interface for event handling)

6. RunnableFuture (Introduced in Java 5)

  • Purpose: Extends Runnable and Future, combining the ability to run a task and return a result.
  • Method:
    • boolean cancel(boolean mayInterruptIfRunning)
    • V get()

7. Predicate (Before Java 8)

  • While Predicate<T> was formally introduced in Java 8, a simple condition-checking pattern (like boolean test(T t)) can be implemented in earlier versions manually.

8. Stream’s forEach (Prior to Java 8)

  • Even before Java 8's Stream.forEach(), you could use loops or iterators for similar purposes, though Stream itself was introduced in Java 8.


     

Other Common Java Functional Interfaces

1. Optional (Java 8)

  • While Optional itself isn't a functional interface, it often works with functional programming principles. It is used to represent a value that may or may not be present.

2. Observer (Java 1.0)

  • Purpose: Allows an object to listen to and react to state changes in another object (older event-based model).
  • Method:
    • void update(Observable o, Object arg)

3. Iterable (Java 1.0)

  • Purpose: Represents a collection of elements that can be iterated.
  • Method:
    • Iterator<T> iterator()

Java 8 introduced the most significant collection of predefined functional interfaces (Function, Predicate, Consumer, Supplier, etc.). Before Java 8, there were functional-like interfaces such as Runnable, Callable, and Comparator, but Java 8 provided a formal structure for lambda expressions and functional programming. Additionally, libraries like Guava, Apache Commons, and Spring further expanded the list of functional interfaces for specialized use cases.

 

Key Functional Interfaces in java.util.function

  1. Consumer<T>
    • Definition: Represents an operation that accepts a single input argument and returns no result.
    • Use Case: Used for performing side-effect operations like printing, modifying a variable, etc.
    • Method:
      • void accept(T t)
    • Code Example:

import java.util.function.Consumer;

public class ConsumerExample {
   public static void main(String[] args) {
       Consumer<String> printConsumer = str -> System.out.println(str);
       printConsumer.accept("Hello, Consumer!");
   }
}
Output: Hello, Consumer!

 

Supplier<T>

  • Definition: Represents a supplier of results. It does not take any input but returns a result.
  • Use Case: Used for generating or supplying objects, often in lazy evaluation.
  • Method:
    • T get()
  • Code Example:

 

import java.util.function.Supplier;

public class SupplierExample {
   public static void main(String[] args) {
       Supplier<Integer> randomNumber = () -> (int)(Math.random() * 100);
       System.out.println("Random number: " + randomNumber.get());
   }
}
 

 

  •  
    • Output: Random number like 42

 

Predicate<T>

  • Definition: Represents a predicate (boolean-valued function) of one argument.
  • Use Case: Used for testing conditions and filtering.
  • Method:
    • boolean test(T t)
  • Code Example:

import java.util.function.Predicate;

public class PredicateExample {
   public static void main(String[] args) {
       Predicate<Integer> isEven = num -> num % 2 == 0;
       System.out.println(isEven.test(4));  // Output: true
       System.out.println(isEven.test(5));  // Output: false
   }
}
 

Output:

true false
 

Function<T, R>

  • Definition: Represents a function that accepts one argument and produces a result.
  • Use Case: Used for transforming or computing results based on an input.
  • Method:
    • R apply(T t)
  • Code Example:

    import java.util.function.Function;

    public class FunctionExample {
       public static void main(String[] args) {
           Function<String, Integer> stringLength = str -> str.length();
           System.out.println("Length of 'Hello': " + stringLength.apply("Hello"));
       }
    }
     

    •  
      • Output: Length of 'Hello': 5

     

    BiConsumer<T, U>

    • Definition: Represents an operation that accepts two input arguments and returns no result.
    • Use Case: Used for performing operations on pairs of values, like logging or modifying two variables.
    • Method:
      • void accept(T t, U u)

     

    import java.util.function.BiConsumer;

    public class BiConsumerExample {
       public static void main(String[] args) {
           BiConsumer<String, Integer> printNameAndAge = (name, age) -> 
               System.out.println(name + " is " + age + " years old.");
           printNameAndAge.accept("Alice", 25);
       }
    }
     

     

    •  
      • Output: Alice is 25 years old.

     

     

    BiFunction<T, U, R>

    • Definition: Represents a function that accepts two arguments and produces a result.
    • Use Case: Used for performing transformations or computations on pairs of values.
    • Method:
      • R apply(T t, U u)
    • Code Example:

      import java.util.function.BiFunction;

      public class BiFunctionExample {
         public static void main(String[] args) {
             BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
             System.out.println("Sum: " + add.apply(5, 3));
         }
      }
       

       

      •  
        • Output: Sum: 8

       

       

      UnaryOperator<T>

      • Definition: A special case of Function that takes one argument of type T and returns a result of the same type T.
      • Use Case: Used for operations that return the same type of input.
      • Method:
        • T apply(T t)
      • Code Example:

         

        import java.util.function.UnaryOperator;

        public class UnaryOperatorExample {
           public static void main(String[] args) {
               UnaryOperator<String> toUpperCase = str -> str.toUpperCase();
               System.out.println(toUpperCase.apply("hello"));
           }
        }
         

        • Output: HELLO

         

        BinaryOperator<T>

        • Definition: A special case of BiFunction where both input arguments and the return type are of the same type T.
        • Use Case: Used for combining two values of the same type.
        • Method:
          • T apply(T t1, T t2)
        • Code Example:

           

          import java.util.function.BinaryOperator;

          public class BinaryOperatorExample {
             public static void main(String[] args) {
                 BinaryOperator<Integer> multiply = (a, b) -> a * b;
                 System.out.println("Multiplication: " + multiply.apply(4, 5));
             }
          }
           

          • Output: Multiplication: 20

           

           

          1. ToIntFunction<T>
            • Definition: Represents a function that takes one argument and returns an int value.
            • Use Case: Used for extracting an int value from an object.
            • Method:
              • int applyAsInt(T t)
            • Code Example:

              import java.util.function.ToIntFunction; public class ToIntFunctionExample {    public static void main (String[] args) {        ToIntFunction<String> stringToLength = str -> str.length();        System.out.println("Length: " + stringToLength.applyAsInt("Java" ));    } }

              Output: Length: 4

          2. ToDoubleFunction<T>
            • Definition: Represents a function that takes one argument and returns a double value.
            • Use Case: Used for extracting a double value from an object.
            • Method:
              • double applyAsDouble(T t)
            • Code Example:

              import java.util.function.ToDoubleFunction; public class ToDoubleFunctionExample {    public static void main (String[] args) {        ToDoubleFunction<String> stringToDouble = str -> str.length() * 1.5 ;        System.out.println("Double value: " + stringToDouble.applyAsDouble("Java" ));    } }

              Output: Double value: 6.0

          Summary of Use Cases:

          • Consumer: Performing actions (e.g., printing or modifying data).
          • Supplier: Providing data or generating results (e.g., random numbers, factory methods).
          • Predicate: Testing conditions (e.g., checking for even numbers, filtering elements).
          • Function: Transforming or computing results based on input (e.g., string length, converting data types).
          • BiConsumer: Handling two input arguments (e.g., processing pairs of values).
          • BiFunction: Performing operations on pairs of arguments (e.g., adding two integers).
          • UnaryOperator: Modifying a single argument and returning the same type (e.g., uppercasing a string).
          • BinaryOperator: Combining two values of the same type (e.g., adding two integers).
          • ToIntFunction/ToDoubleFunction: Extracting primitive types from objects (e.g., length of string as integer or double).

           

           

           

           

          package com.niteshsynergy.java8;

          import java.util.function.*;

          public class Demo02FunctionTypes {
             public static void main(String[] args) {
                 
                 // Consumer Example: Performing an action without returning anything
                 Consumer<String> printConsumer = str -> System.out.println("Consumer Output: " + str);
                 printConsumer.accept("Hello, Consumer!");
                 
                 // Supplier Example: Providing a value
                 Supplier<Integer> randomNumber = () -> (int)(Math.random() * 100);
                 System.out.println("Supplier Output: " + randomNumber.get());
                 
                 // Predicate Example: Testing a condition
                 Predicate<Integer> isEven = num -> num % 2 == 0;
                 System.out.println("Predicate Output: Is 4 even? " + isEven.test(4));
                 System.out.println("Predicate Output: Is 5 even? " + isEven.test(5));

                 // Function Example: Transforming an input to an output
                 Function<String, Integer> stringLength = str -> str.length();
                 System.out.println("Function Output: Length of 'Java' is " + stringLength.apply("Java"));

                 // BiConsumer Example: Performing an action on two input values
                 BiConsumer<String, Integer> printNameAndAge = (name, age) -> 
                     System.out.println("BiConsumer Output: " + name + " is " + age + " years old.");
                 printNameAndAge.accept("Alice", 30);
                 
                 // BiFunction Example: Performing a transformation on two input values
                 BiFunction<Integer, Integer, Integer> addNumbers = (a, b) -> a + b;
                 System.out.println("BiFunction Output: Sum of 5 and 3 is " + addNumbers.apply(5, 3));

                 // UnaryOperator Example: Applying an operation on a single argument
                 UnaryOperator<String> toUpperCase = str -> str.toUpperCase();
                 System.out.println("UnaryOperator Output: 'hello' to uppercase is " + toUpperCase.apply("hello"));

                 // BinaryOperator Example: Applying an operation on two arguments of the same type
                 BinaryOperator<Integer> multiplyNumbers = (a, b) -> a * b;
                 System.out.println("BinaryOperator Output: Product of 4 and 5 is " + multiplyNumbers.apply(4, 5));
                 
                 // ToIntFunction Example: Extracting an integer value from an input
                 ToIntFunction<String> stringToLength = str -> str.length();
                 System.out.println("ToIntFunction Output: Length of 'Hello' is " + stringToLength.applyAsInt("Hello"));
                 
                 // ToDoubleFunction Example: Extracting a double value from an input
                 ToDoubleFunction<String> stringToDouble = str -> str.length() * 1.5;
                 System.out.println("ToDoubleFunction Output: Length of 'Hello' as double is " + stringToDouble.applyAsDouble("Hello"));
             }
          }
           

           

          Output Example:

          Consumer Output: Hello, Consumer!
          Supplier Output: 42
          Predicate Output: Is 4 even? true
          Predicate Output: Is 5 even? false
          Function Output: Length of 'Java' is 4
          BiConsumer Output: Alice is 30 years old.
          BiFunction Output: Sum of 5 and 3 is 8
          UnaryOperator Output: 'hello' to uppercase is HELLO
          BinaryOperator Output: Product of 4 and 5 is 20
          ToIntFunction Output: Length of 'Hello' is 5
          ToDoubleFunction Output: Length of 'Hello' as double is 7.5
           

           

           

          Explanation of Code:

          1. Consumer: Prints a string to the console.
          2. Supplier: Generates and returns a random number between 0 and 100.
          3. Predicate: Checks if a number is even (returns true or false).
          4. Function: Takes a string and returns its length.
          5. BiConsumer: Prints a person's name and age.
          6. BiFunction: Adds two numbers and returns their sum.
          7. UnaryOperator: Converts a string to uppercase.
          8. BinaryOperator: Multiplies two integers and returns the result.
          9. ToIntFunction: Returns the length of a string as an integer.
          10. ToDoubleFunction: Multiplies the length of a string by 1.5 and returns the result as a double.
             

           

          package com.niteshsynergy.java8;

          import java.util.Arrays;
          import java.util.List;
          import java.util.Random;
          import java.util.function.*;

          public class FunctionTypes {
             public static void main(String[] args) {
                 // List of names for example usage
                 List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

                 // Predicate example: checking if a number is positive
                 Predicate<Integer> predicate = i -> i > 0;  // Predicate checks a condition on a single input
                 if (predicate.test(10))
                     System.out.println("Positive");  // Will print "Positive" as 10 is greater than 0
                 else
                     System.out.println("Negative");  // Will not print

                 // Consumer example: accepting a string and printing a message
                 Consumer<String> consumer = s -> {
                     System.out.println(s + " welcome"); // This prints a welcome message
                 };
                 consumer.accept("Nitesh"); // Will print "Nitesh welcome"

                 // Another Consumer example: checking if an integer is even and printing
                 Consumer<Integer> integerConsumer = num -> {
                     if (num % 2 == 0)
                         System.out.println("Even");  // Prints "Even" if the number is even
                 };
                 integerConsumer.accept(2); // Will print "Even"

                 // List of integers for predicate testing
                 List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

                 // Predicate example: checking if a number is positive
                 Predicate<Integer> isPositive = n -> n > 0;
                 System.out.println(isPositive.test(5));  // Output: true (5 is positive)
                 System.out.println(isPositive.test(-3)); // Output: false (-3 is negative)

                 // Consumer example: printing each name from the list
                 List<String> names1 = Arrays.asList("Alice", "Bob", "Charlie");
                 Consumer<String> printName = name -> System.out.println(name);
                 names.forEach(printName);  // Output: Alice, Bob, Charlie

                 // Supplier example: generating a random number
                 Supplier<Integer> randomNumber = () -> new Random().nextInt(100); // Generates a random number between 0 and 99
                 System.out.println(randomNumber.get()); // Output: Random number (e.g., 42)
                 System.out.println(randomNumber.get()); // Output: Another random number (e.g., 85)

                 // Function example: converting a string to uppercase
                 Function<String, String> toUpperCase = str -> str.toUpperCase(); // Converts string to uppercase
                 System.out.println(toUpperCase.apply("hello")); // Output: HELLO
                 System.out.println(toUpperCase.apply("java"));  // Output: JAVA

                 // BiFunction example: concatenating two strings
                 BiFunction<String, String, String> concatenate = (str1, str2) -> str1 + str2;
                 System.out.println(concatenate.apply("Hello, ", "World!")); // Output: Hello, World!
                 System.out.println(concatenate.apply("Java ", "8"));       // Output: Java 8
             }
          }
           

           


          → Next

           

          In Java 8, Streams provide a powerful way to process sequences of elements (such as collections or arrays) in a functional style. They allow you to process data in a concise and declarative manner, which improves code readability and maintainability. Streams can be created from collections, arrays, or individual values.

           

          1. Creating Streams from Collections

          A Collection (e.g., List, Set) is one of the most common sources of data from which you can create a stream.

          • Using stream() method: Collections in Java 8 implement the Stream interface, and you can create a stream using the stream() method.

          import java.util.List;
          import java.util.Arrays;

          public class StreamFromCollection {
             public static void main(String[] args) {
                 List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
                 
                 // Create a stream from a collection
                 names.stream()
                      .filter(name -> name.startsWith("A"))
                      .forEach(System.out::println);  // Output: Alice
             }
          }
           

          Explanation: Here, we use the stream() method to create a stream from the List. We then use operations like filter() to process the elements in the stream. The forEach() terminal operation prints the result.

           

           

          Creating Streams from Arrays

          Arrays are another common data source for streams in Java. You can create a stream from an array using the Arrays.stream() method or the stream() method (if working with arrays in general).

          • Using Arrays.stream():

          import java.util.Arrays;

          public class StreamFromArray {
             public static void main(String[] args) {
                 String[] fruits = {"Apple", "Banana", "Mango", "Pineapple"};
                 
                 // Create a stream from an array
                 Arrays.stream(fruits)
                       .filter(fruit -> fruit.length() > 5)
                       .forEach(System.out::println);  // Output: Banana, Mango, Pineapple
             }
          }
          Explanation: The Arrays.stream() method creates a stream from the array fruits. The stream operations such as filter() and forEach() are used to process the elements.

           

           

          Creating Streams from Individual Values

          In addition to collections and arrays, you can create streams from individual values using the Stream.of() method. This method is useful when you want to create a stream from a small number of elements.

          • Using Stream.of():

           

          import java.util.stream.Stream;

          public class StreamFromValues {
             public static void main(String[] args) {
                 // Create a stream from individual values
                 Stream<String> stream = Stream.of("John", "Jane", "Mike", "Anna");
                 
                 // Use operations on the stream
                 stream.filter(name -> name.length() > 3)
                       .forEach(System.out::println);  // Output: John, Jane, Mike
             }
          }
           

          Explanation: Here, we use Stream.of() to create a stream from individual values. We apply the filter() operation to select names with more than 3 characters, and forEach() is used to print the filtered results.

           

          Creating Streams from Primitive Arrays

          For arrays of primitive types (e.g., int[], double[], etc.), Java provides specialized stream types: IntStream, LongStream, and DoubleStream. These allow efficient handling of primitive data types without the overhead of autoboxing.

          • Using Arrays.stream() for primitive arrays:

            Example:

          import java.util.Arrays;

          public class StreamFromPrimitiveArray {
             public static void main(String[] args) {
                 int[] numbers = {1, 2, 3, 4, 5};
                 
                 // Create an IntStream from an int array
                 Arrays.stream(numbers)
                       .filter(num -> num % 2 == 0)
                       .forEach(System.out::println);  // Output: 2, 4
             }
          }
          Explanation: We create an IntStream from the int[] array using Arrays.stream(). The filter() method is applied to select even numbers, and forEach() prints the results.

           

           

          Summary of Ways to Create Streams:

          SourceMethod to Create StreamExample
          Collectionstream() methodList<String> list = ...; list.stream()
          ArrayArrays.stream() methodint[] arr = ...; Arrays.stream(arr)
          Individual ValuesStream.of() methodStream<String> stream = Stream.of("a", "b", "c")
          Primitive ArraysArrays.stream() for primitive types (int[], double[])int[] numbers = {1, 2, 3}; Arrays.stream(numbers)

          Why Use Streams?

          1. Declarative and Functional Style: Streams allow you to process data in a more functional way, making the code more readable and concise. Instead of writing explicit loops and conditional statements, you can use operations like filter(), map(), reduce(), and more.
          2. Parallel Processing: Streams can easily be processed in parallel, improving performance on multi-core systems.
          3. Chainable Operations: Streams support method chaining, enabling you to apply multiple operations in a fluent, readable manner.

           

          Real-World Use Case (Gaming):

          In a gaming project, suppose you have a list of players, and you want to filter out all players whose scores are below a certain threshold and then sort the remaining players by their scores.

           

           

          import java.util.List;
          import java.util.Arrays;
          import java.util.stream.Collectors;

          class Player {
             String name;
             int score;

             Player(String name, int score) {
                 this.name = name;
                 this.score = score;
             }

             public String toString() {
                 return name + ": " + score;
             }
          }

          public class StreamExample {
             public static void main(String[] args) {
                 List<Player> players = Arrays.asList(
                     new Player("Alice", 1500),
                     new Player("Bob", 800),
                     new Player("Charlie", 1200),
                     new Player("David", 2000)
                 );

                 // Use streams to filter and sort
                 List<Player> filteredPlayers = players.stream()
                         .filter(player -> player.score > 1000)
                         .sorted((p1, p2) -> Integer.compare(p2.score, p1.score))  // Sort by score descending
                         .collect(Collectors.toList());

                 filteredPlayers.forEach(System.out::println);  // Output: David: 2000, Alice: 1500, Charlie: 1200
             }
          }
           

           

          In Java 8, the Stream API provides a set of operations to process sequences of elements in a functional style. The Stream API can be categorized into two main types based on how data flows and is processed: Streams and Primitive Streams.

          Here’s a breakdown of the types of Stream API in Java:

           

          1. Stream (Reference Type Stream)

          • Stream<T>: The general stream type for working with objects (non-primitive types).

            • Purpose: Used to process data that is wrapped in reference types (e.g., String, Integer, Custom objects).
            • Operations: Can perform operations like filtering, mapping, sorting, reducing, etc., on objects.

            Example:

          import java.util.List;
          import java.util.Arrays;

          public class ReferenceStreamExample {
             public static void main(String[] args) {
                 List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

                 names.stream()
                      .filter(name -> name.length() > 3)
                      .forEach(System.out::println);  // Output: Alice, Charlie, David
             }
          }
           

           

          Features:

          • Supports operations like map(), filter(), forEach(), reduce().
          • Allows creating a pipeline of multiple operations on collections or other data sources.
          • Does not support primitive operations directly.

           

          Primitive Streams

          • IntStream, LongStream, and DoubleStream: These are specialized streams for handling primitive types (int, long, and double), providing better performance by avoiding autoboxing (the conversion between primitives and objects).

            • IntStream: Used for int values.
            • LongStream: Used for long values.
            • DoubleStream: Used for double values.

            Why Primitive Streams?

            • Efficiency: They avoid the overhead of boxing and unboxing primitive values into wrapper classes like Integer, Long, and Double.
            • Specialized Operations: These streams support operations like sum(), average(), and min(), which are optimized for primitive data types.

            Example:

          import java.util.stream.IntStream;

          public class PrimitiveStreamExample {
             public static void main(String[] args) {
                 IntStream.range(1, 6)
                          .filter(n -> n % 2 == 0)
                          .forEach(System.out::println);  // Output: 2, 4
             }
          }
           

           

          Features:

          • IntStream: Provides methods like sum(), average(), max(), and min().
          • LongStream and DoubleStream: Similar methods as IntStream but for long and double data types, respectively.
          • They offer mapToObj(), flatMapToInt(), etc., to transform between primitive and object streams.

           

           

          Stream Pipelines and Operations

          Regardless of whether you're using reference streams or primitive streams, the stream operations can be categorized into:

          • Intermediate Operations: These operations transform a stream into another stream (e.g., map(), filter(), distinct()). They are lazy, meaning they are not executed until a terminal operation is invoked.
          • Terminal Operations: These operations produce a result or a side effect (e.g., collect(), forEach(), reduce()). A terminal operation marks the end of the stream pipeline.
          • Short-circuiting Operations: These operations allow the processing to stop early based on a condition, such as anyMatch(), allMatch(), findFirst(), etc.

           

          Key Differences Between Stream Types:

          Stream TypeFor Primitive TypesFor Objects/Reference Types
          StreamNoYes
          IntStreamYesNo
          LongStreamYesNo
          DoubleStreamYesNo

           

           

                                                                          Types Of Stream API In Java 8

           

          image-180.png

          Intermediate operations are operations that transform a stream into another stream. These operations are lazy, meaning they do not perform any processing until a terminal operation is invoked. When you perform intermediate operations, you create a stream pipeline that is executed when a terminal operation is triggered. Since they are lazy, intermediate operations can be chained together and only evaluated when necessary, which can help improve performance by avoiding unnecessary computations.

          Here are the key Intermediate Operations available in Java 8 Streams:

          package com.niteshsynergy.java8.stream;

          import com.niteshsynergy.Emp;
          import java.util.*;
          import java.util.stream.Collectors;
          import java.util.stream.Stream;

          public class Demo01IntermediateOperations {
             public static void main(String[] args) {
                 // filter() – Filters elements based on a condition
                 List<String> list1 = Arrays.asList("apple", "banana", "carrot", "Mango", "Anans", "Appu", "Awla");
                 list1.stream()
                      .filter(s -> s.toLowerCase().startsWith("a"))
                      .forEach(System.out::println);

                 // map() – Transforms each element of the stream
                 list1.stream()
                      .map(s -> s.replaceAll("a", "b"))
                      .forEach(System.out::println);

                 // distinct() – Removes duplicates
                 List<Integer> list2 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
                 list2.stream()
                      .distinct()
                      .forEach(System.out::println);

                 // sorted() – Sorts the stream
                 List<Integer> list3 = Arrays.asList(9, 1, 5, 3, 7, 2, 6, 8, 4);
                 list3.stream()
                      .sorted()
                      .forEach(System.out::println);

                 // limit() – Limits the number of elements in the stream
                 List<Integer> list4 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
                 list4.stream()
                      .limit(5)
                      .forEach(System.out::println);

                 // skip() – Skips the first n elements
                 list4.stream()
                      .skip(5)
                      .forEach(System.out::println);
             }
          }
           

          image-181.png

          Terminal Operations are the operations that trigger the processing of the stream and produce a result, a side-effect, or a final computation. Once a terminal operation is invoked, the stream is consumed and can no longer be used. These operations mark the end of the stream pipeline and cause the intermediate operations to be evaluated.

          Here’s a breakdown of some key Terminal Operations in Java 8 Streams:

          package com.niteshsynergy.java8.stream;

          import com.niteshsynergy.Emp;
          import java.util.*;
          import java.util.function.Function;
          import java.util.stream.Collectors;

          public class Demo03TerminalOperations {

             public static void main(String[] args) {

                 // collect() – Collects elements into a List
                 List<String> list = Arrays.asList("apple", "banana", "cherry");

                 List<String> result = list.stream()
                                           .collect(Collectors.toList());

                 System.out.println(result); // Output: [apple, banana, cherry]

                 // collect() – Collects elements into a Map using identity as key and entire list as value
                 list.stream()
                     .collect(Collectors.toMap(Function.identity(), l -> list))
                     .forEach((key, value) -> System.out.println("Identity: " + key + " => " + value));

                 // forEach() – Performs an action for each element
                 list.stream()
                     .forEach(System.out::println);

                 // reduce() – Reduces the stream to a single value (e.g., sum)
                 List<Integer> integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
                 Integer sum = integers.stream()
                                       .reduce(0, Integer::sum);

                 System.out.println("Sum using reduce(): " + sum);
             }
          }
           

          image-182.png

           

          Short-circuiting operations in Java 8 Streams are special types of terminal operations that terminate the stream processing early. These operations evaluate the stream lazily, which means that they stop processing elements as soon as the condition is met. This can result in performance optimization, especially when working with large datasets, as it avoids unnecessary processing.

          Short-circuiting operations are used when you don’t need to process the entire stream, but only a part of it. They "short-circuit" the evaluation once a result is determined.

          package com.niteshsynergy.java8.stream;

          import java.util.Arrays;
          import java.util.List;
          import java.util.Optional;

          public class Demo04ShortCircuitingOperations {

             public static void main(String[] args) {
                 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

                 // anyMatch() – Returns true if any element matches the condition
                 boolean anyMatch = list.stream().anyMatch(x -> x > 4);
                 System.out.println("Any match > 4: " + anyMatch);  // Output: true

                 // allMatch() – Returns true if all elements match the condition
                 boolean allMatch = list.stream().allMatch(x -> x > 0);
                 System.out.println("All match > 0: " + allMatch);  // Output: true

                 // noneMatch() – Returns true if no elements match the condition
                 boolean noneMatch = list.stream().noneMatch(x -> x < 0);
                 System.out.println("None match < 0: " + noneMatch);  // Output: true

                 // findFirst() – Returns the first element in the stream
                 Optional<Integer> first = list.stream().findFirst();
                 first.ifPresent(val -> System.out.println("First element: " + val));  // Output: 1

                 // findAny() – Returns any element from the stream (useful in parallel streams)
                 Optional<Integer> any = list.stream().findAny();
                 any.ifPresent(val -> System.out.println("Any element: " + val));  // Output: 1 (or any)
             }
          }
           

           

          Gaming Example: Player Ranking and Filtering

          import java.util.*;
          import java.util.stream.*;

          public class GamingExample {
             public static void main(String[] args) {
                 // Creating a list of players with scores and rankings
                 List<Player> players = Arrays.asList(
                     new Player("Alice", 1000, "Bronze", Arrays.asList("Quest1", "Quest2")),
                     new Player("Bob", 1500, "Gold", Arrays.asList("Quest2", "Quest3")),
                     new Player("Charlie", 2500, "Platinum", Arrays.asList("Quest1", "Quest4")),
                     new Player("David", 800, "Silver", Arrays.asList("Quest3", "Quest5")),
                     new Player("Eve", 1300, "Gold", Arrays.asList("Quest4", "Quest6"))
                 );

                 // Stream pipeline to process players:
                 List<String> highRankingPlayers = players.stream()
                     // 1. Filter out players who have less than 1000 points
                     .filter(player -> player.getScore() >= 1000)
                     
                     // 2. Map the filtered players to their names
                     .map(Player::getName)
                     
                     // 3. Apply a transformation to convert all player names to uppercase
                     .map(String::toUpperCase)
                     
                     // 4. Filter players whose names start with 'A'
                     .filter(name -> name.startsWith("A"))
                     
                     // 5. Convert the list of names to a stream of quests each player is associated with
                     .flatMap(name -> players.stream()
                                             .filter(player -> player.getName().equals(name))
                                             .flatMap(player -> player.getQuests().stream()))
                     
                     // 6. Remove duplicate quests
                     .distinct()
                     
                     // 7. Sort quests alphabetically
                     .sorted()
                     
                     // 8. Limit the output to the first 3 quests
                     .limit(3)
                     
                     // 9. Collect the result into a List
                     .collect(Collectors.toList());

                 // Displaying the results
                 System.out.println("High-ranking players' quests: " + highRankingPlayers);
             }
          }

          // Player class with name, score, rank, and quests
          class Player {
             private String name;
             private int score;
             private String rank;
             private List<String> quests;

             public Player(String name, int score, String rank, List<String> quests) {
                 this.name = name;
                 this.score = score;
                 this.rank = rank;
                 this.quests = quests;
             }

             public String getName() {
                 return name;
             }

             public int getScore() {
                 return score;
             }

             public String getRank() {
                 return rank;
             }

             public List<String> getQuests() {
                 return quests;
             }
          }
           

          Explanation of the Stream Pipeline:

          1. filter(player -> player.getScore() >= 1000): Filters out players with scores less than 1000, ensuring we only process high-scoring players.
          2. map(Player::getName): Maps the players to their names, extracting the relevant data from each Player object.
          3. map(String::toUpperCase): Converts the player names to uppercase for uniform formatting.
          4. filter(name -> name.startsWith("A")): Filters the list of player names to only include those starting with the letter "A".
          5. flatMap(name -> players.stream().filter(player -> player.getName().equals(name)).flatMap(player -> player.getQuests().stream())): For each player, we extract their quests, and use flatMap to flatten the nested lists into a single stream of quest names.
          6. distinct(): Removes any duplicate quest names from the resulting stream.
          7. sorted(): Sorts the quest names in alphabetical order.
          8. limit(3): Limits the result to the first 3 quests, demonstrating a cap on the stream's size.
          9. collect(Collectors.toList()): Collects the result into a list, which is the final output.

          Output:

          High-ranking players' quests: [Quest1, Quest2, Quest3]
           

           

          Java 8 Stream Operations with 20 Method Chain

          import java.util.*;
          import java.util.stream.*;

          public class StreamMethodChainExample {
             public static void main(String[] args) {
                 // Create a list of players with names, scores, ranks, and quests
                 List<Player> players = Arrays.asList(
                     new Player("Alice", 1000, "Bronze", Arrays.asList("Quest1", "Quest2")),
                     new Player("Bob", 1500, "Gold", Arrays.asList("Quest2", "Quest3")),
                     new Player("Charlie", 2500, "Platinum", Arrays.asList("Quest1", "Quest4")),
                     new Player("David", 800, "Silver", Arrays.asList("Quest3", "Quest5")),
                     new Player("Eve", 1300, "Gold", Arrays.asList("Quest4", "Quest6"))
                 );

                 // Chain of 20 stream methods
                 List<String> result = players.stream()
                     // 1. Filter players with a score greater than or equal to 1000
                     .filter(player -> player.getScore() >= 1000)
                     // 2. Map to player's name
                     .map(Player::getName)
                     // 3. Convert names to uppercase
                     .map(String::toUpperCase)
                     // 4. Filter names that start with 'A'
                     .filter(name -> name.startsWith("A"))
                     // 5. Map to player's quests by finding players by name
                     .flatMap(name -> players.stream()
                                             .filter(player -> player.getName().equals(name))
                                             .flatMap(player -> player.getQuests().stream()))
                     // 6. Remove duplicates from the quest names
                     .distinct()
                     // 7. Sort quest names alphabetically
                     .sorted()
                     // 8. Limit to the first 3 quest names
                     .limit(3)
                     // 9. Map quests to their length
                     .map(String::length)
                     // 10. Filter quests that are longer than 4 characters
                     .filter(len -> len > 4)
                     // 11. Reduce to the total length of quest names
                     .reduce(0, Integer::sum)
                     // 12. Add a constant value (total length of all quests) to it
                     .map(totalLength -> totalLength + 10)
                     // 13. Convert the total length to a string with a message
                     .map(length -> "Total quest name length with 10 added: " + length)
                     // 14. Filter if the string length is greater than 50
                     .filter(msg -> msg.length() > 50)
                     // 15. If not empty, concatenate " - Completed" at the end
                     .map(msg -> msg + " - Completed")
                     // 16. Sort the final string list alphabetically
                     .sorted()
                     // 17. Limit the results to top 1 string
                     .limit(1)
                     // 18. Convert to a list (collect)
                     .collect(Collectors.toList())
                     // 19. Print the result
                     .forEach(System.out::println);
             }
          }

          // Player class with name, score, rank, and quests
          class Player {
             private String name;
             private int score;
             private String rank;
             private List<String> quests;

             public Player(String name, int score, String rank, List<String> quests) {
                 this.name = name;
                 this.score = score;
                 this.rank = rank;
                 this.quests = quests;
             }

             public String getName() {
                 return name;
             }

             public int getScore() {
                 return score;
             }

             public String getRank() {
                 return rank;
             }

             public List<String> getQuests() {
                 return quests;
             }
          }

          Explanation of the Stream Method Chain:

          1. filter(player -> player.getScore() >= 1000): Filters out players with a score less than 1000.
          2. map(Player::getName): Extracts the names of the filtered players.
          3. map(String::toUpperCase): Converts all player names to uppercase.
          4. filter(name -> name.startsWith("A")): Filters names that start with "A".
          5. flatMap(): Extracts quests of players whose names match the filtered ones.
          6. distinct(): Removes any duplicate quest names.
          7. sorted(): Sorts the quest names alphabetically.
          8. limit(3): Limits the result to the first 3 quests.
          9. map(String::length): Converts each quest name to its length.
          10. filter(len -> len > 4): Filters quest names with length greater than 4.
          11. reduce(0, Integer::sum): Reduces the length of quest names to a total sum.
          12. map(totalLength -> totalLength + 10): Adds 10 to the total length.
          13. map(length -> "Total quest name length with 10 added: " + length): Converts the total length into a string with a message.
          14. filter(msg -> msg.length() > 50): Filters if the string message length is greater than 50.
          15. map(msg -> msg + " - Completed"): Appends "- Completed" to the message.
          16. sorted(): Sorts the final string messages alphabetically.
          17. limit(1): Limits the final result to only the top 1 string.
          18. collect(Collectors.toList()): Collects the result into a list.
          19. forEach(System.out::println): Prints the final result.

          Output:
          Total quest name length with 10 added: 17 - Completed


          You can ask any query just drop mail to us on say@niteshsynergy.com

           

           

 

Key Features of Optional in Java:
Avoiding Null Checks: Optional helps avoid explicit null checks. Instead of checking if (object == null), you can use Optional to wrap a potentially null value, making it easier to handle the absence of a value without needing to explicitly check for null.

 

43 min read
ноя 19, 2024
By Nitesh Synergy
Share