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 - Lambda & Functional Interface

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
             }
          }
           

           

           



          Thanks…..

20 min read
Nov 19, 2024
By Nitesh Synergy
Share