Java Lambda Expressions

Introduction

Lambda expressions are a powerful feature introduced in Java 8. They provide a concise and expressive way to write code that is more functional and easier to read. In this tutorial, we will explore what lambda expressions are, how they work, and how to use them in Java.

What are Lambda expressions?

Lambda expressions are a way to write inline functions that can be passed around like variables. They allow you to define a block of code that can be executed later, and pass it as an argument to another method or function.

Lambda expressions are a form of anonymous function, which means that they do not have a name or a return type. Instead, they consist of a list of parameters, a lambda operator “->”, and a body of code.

Syntax of Lambda expressions

The syntax of a lambda expression is as follows:

(parameters) -> { body }

Here, parameters are the arguments that the lambda expression takes, and the body is the code that it executes. The “->” operator separates the parameters from the body.

Example:

(int a, int b) -> { return a + b; }

In this example, the lambda expression takes two integer parameters, adds them together, and returns the result.

Java Lambda Expressions Example

Here are detailed code examples with output on the uses of lambda expressions in Java:

Functional Interfaces

Example 1: Using a lambda expression to implement a functional interface with a single abstract method (SAM).

@FunctionalInterface
interface MyFunctionalInterface {
    void doSomething(int a, int b);
}

public class LambdaExample {
    public static void main(String[] args) {
        // Creating a lambda expression and assigning it to a functional interface
        MyFunctionalInterface myFunctionalInterface = (a, b) -> System.out.println(a + b);
        
        // Calling the method on the functional interface, which executes the lambda expression
        myFunctionalInterface.doSomething(3, 4); // Output: 7
    }
}

In this example, we create a functional interface MyFunctionalInterface with a single abstract method doSomething. We then create a lambda expression (a, b) -> System.out.println(a + b) that takes two integer arguments and prints their sum to the console. Finally, we assign the lambda expression to a variable of type MyFunctionalInterface and call the doSomething method on it, which executes the lambda expression.

Example 2: Using a lambda expression to implement the Runnable interface.

public class LambdaExample {
    public static void main(String[] args) {
        // Creating a lambda expression that implements the run method of the Runnable interface
        Runnable myRunnable = () -> System.out.println("Hello from Runnable!");
        
        // Creating a new thread and passing the lambda expression as a parameter to the constructor
        Thread thread = new Thread(myRunnable);
        thread.start(); // Output: Hello from Runnable!
    }
}

In this example, we create a lambda expression () -> System.out.println("Hello from Runnable!") that implements the run method of the Runnable interface. We then create a new thread and pass the lambda expression as a parameter to the constructor. When we start the thread, the run method is executed, which executes the lambda expression and prints “Hello from Runnable!” to the console.

Lambda expressions with Collections

Example 1: Using a lambda expression to iterate over a list and print each element.

rust
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
// Output: Alice, Bob, Charlie

In this example, we create a list of strings names and use a lambda expression name -> System.out.println(name) with the forEach method to iterate over each element of the list and print it to the console.

Example 2: Using a lambda expression with the map method to convert a list of integers to their squares.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
List<Integer> squares = numbers.stream()
        .map(n -> n * n)
        .collect(Collectors.toList());
System.out.println(squares); // Output: [1, 4, 9, 16]

In this example, we create a list of integers numbers and use the stream method to create a stream of elements from the list. We then use the map method with a lambda expression n -> n * n to square each element of the stream, and use the collect method with the toList collector to convert the stream back into a list. Finally, we print the resulting list of squares to the console.

Lambda Expression as the Method References

Lambda expressions in Java can be further simplified by using method references. A method reference is a shorthand syntax that refers to a method of a class or an instance without invoking it. Method references can be used to simplify lambda expressions and make code more concise and readable. In this section, we will explore several examples of using method references with lambda expressions.

Example 1: Using a method reference to call a static method.

import java.util.function.Function;

public class MethodReferenceExample {
    public static void main(String[] args) {
        Function<String, Integer> parseIntFunction = Integer::parseInt;
        int value = parseIntFunction.apply("42");
        System.out.println(value);
    }
}

Output: 42

In this example, we create a Function that takes a String argument and returns an Integer. We use a method reference Integer::parseInt to call the static method parseInt on the Integer class, which takes a String argument and returns an int. When we call the apply method on the parseIntFunction with the argument “42”, the parseInt method is called with the argument “42” and returns the integer value 42, which is then returned by the apply method and assigned to the value variable. Finally, we print the value variable to the console.

Example 2: Using a method reference to call an instance method on an object.

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

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        names.forEach(System.out::println);
    }
}

Output:

Alice
Bob
Charlie

In this example, we create a list of strings names and use a method reference System.out::println with the forEach method to call the println method on the System.out object for each element of the list. This is equivalent to using a lambda expression name -> System.out.println(name) as we did in the previous example, but using a method reference can make the code more concise and easier to read.

Example 3: Using a method reference to call a constructor.

import java.util.function.Supplier;

public class MethodReferenceExample {
    public static void main(String[] args) {
        Supplier<StringBuilder> stringBuilderSupplier = StringBuilder::new;
        StringBuilder sb = stringBuilderSupplier.get();
        sb.append("Hello, ");
        sb.append("World!");
        System.out.println(sb.toString());
    }
}

Output: Hello, World!

In this example, we create a Supplier that takes no arguments and returns a new StringBuilder object by using the constructor reference StringBuilder::new. When we call the get method on the stringBuilderSupplier, a new StringBuilder object is created and returned, which we then use to append the strings “Hello, ” and “World!” and print the result to the console.

Lambda Expression as the User-defined Method References

In addition to using built-in methods in method references, we can also use user-defined methods. In this section, we will explore several examples of using user-defined method references with lambda expressions.

Example 1: Using a user-defined method reference to call an instance method.

import java.util.function.Consumer;

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        Person person = new Person("Alice");
        Consumer<Person> sayHelloConsumer = Person::sayHello;
        sayHelloConsumer.accept(person);
    }
}

Output: Hello, my name is Alice

In this example, we create a Person class with a sayHello method that prints a greeting to the console. We create a Consumer that takes a Person argument and calls the sayHello method on it. We use a user-defined method reference Person::sayHello to refer to the sayHello method of the Person class. When we call the accept method on the sayHelloConsumer with the person object as an argument, the sayHello method is called on the person object, resulting in the output Hello, my name is Alice.

Example 2: Using a user-defined method reference to call a static method.

import java.util.function.Function;

class StringUtil {
    public static String reverse(String s) {
        StringBuilder sb = new StringBuilder(s);
        return sb.reverse().toString();
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        Function<String, String> reverseFunction = StringUtil::reverse;
        String reversed = reverseFunction.apply("Hello, World!");
        System.out.println(reversed);
    }
}

Output: !dlroW ,olleH

In this example, we create a StringUtil class with a static reverse method that takes a String argument and returns the reversed String. We create a Function that takes a String argument and returns a String. We use a user-defined method reference StringUtil::reverse to refer to the reverse method of the StringUtil class. When we call the apply method on the reverseFunction with the argument “Hello, World!”, the reverse method is called on the StringUtil class with the argument “Hello, World!”, resulting in the output !dlroW ,olleH.

Example 3: Using a user-defined method reference with multiple arguments.

import java.util.function.BiFunction;

class Calculator {
    public static int add(int a, int b) {
        return a + b;
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> addFunction = Calculator::add;
        int result = addFunction.apply(3, 5);
        System.out.println(result);
    }
}

Output: 8

In this example, we create a Calculator class with a static add method that takes two int arguments and returns their sum. We create a BiFunction that takes two Integer arguments and returns an Integer. We use a user-defined method reference Calculator::add to refer to the add method of the Calculator class. When we call the apply method on the addFunction with the arguments 3 and 5, the add method is called on the Calculator class with the arguments 3 and 5, resulting in the output 8.

Example 4: Using a user-defined method reference with a constructor.

import java.util.function.Function;

class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        Function<String, Person> personConstructor = Person::new;
        Person person = personConstructor.apply("Alice");
        System.out.println(person.getName());
    }
}

Output: Alice

In this example, we create a Person class with a constructor that takes a String argument and sets the name instance variable. We create a Function that takes a String argument and returns a Person object. We use a user-defined method reference Person::new to refer to the constructor of the Person class. When we call the apply method on the personConstructor with the argument “Alice”, a new Person object is created with the name instance variable set to “Alice”, resulting in the output Alice.

Example 5: Using a user-defined method reference with an instance method and a parameter.

import java.util.function.Function;

class StringUtil {
    private int multiplier;

    public StringUtil(int multiplier) {
        this.multiplier = multiplier;
    }

    public String multiply(String s) {
        StringBuilder sb = new StringBuilder(s);
        for (int i = 0; i < multiplier - 1; i++) {
            sb.append(s);
        }
        return sb.toString();
    }
}

public class MethodReferenceExample {
    public static void main(String[] args) {
        StringUtil stringUtil = new StringUtil(3);
        Function<String, String> multiplyFunction = stringUtil::multiply;
        String result = multiplyFunction.apply("hello");
        System.out.println(result);
    }
}

Output: hellohellohello

In this example, we create a StringUtil class with an instance method multiply that takes a String argument and multiplies it by the multiplier instance variable. We create a Function that takes a String argument and returns a String. We create a StringUtil object with a multiplier value of 3. We use a user-defined method reference stringUtil::multiply to refer to the multiply instance method of the StringUtil object. When we call the apply method on the multiplyFunction with the argument “hello”, the multiply method is called on the stringUtil object with the argument “hello”, resulting in the output hellohellohello.

Conclusion

In conclusion, lambda expressions and method references are powerful features of Java that allow us to write more concise and expressive code. They can be used in various contexts, including functional interfaces, collections, and user-defined methods. By using lambda expressions and method references, we can write code that is easier to read, write, and maintain.

Also, see the example code JavaExamples_NoteArena in our GitHub repository. See complete examples in our GitHub repositories.

Follow us on social media
Follow Author