TheDeveloperBlog.com

Home | Contact Us

C-Sharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript | SQL | PHP | Angular | HTML

Java Lambda Expressions: Consumer, Supplier and Function

This Java 8 article uses lambda expressions. It creates Function, Supplier and Consumer classes and passes them to methods.

Lambdas are used to create function objects.

With them, we can specify methods inside other methods—and even pass methods as arguments to other methods.

A lambda has a shape, one determined by its parameters and return values (if any) and their types. Classes like Function, Supplier, Consumer, accept lambdas with specific shapes.

Example expression. This program creates a Function object from a lambda expression. The lambda expression accepts one argument, an Integer, and returns another Integer.

Left side: On the left of a lambda expression, we have the parameters. Two or more parameters can be surrounded by "(" and ")" chars.

Right side: This is the return expression—it is evaluated using the parameters. It is executed and, when required, returned.

Apply: In this program, we call apply() on the Function object. This executes and returns the expression—10 is changed to 20.

Based on:

Java 8

Java program that uses lambda expression

import java.util.function.*;

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

	// Create a Function from a lambda expression.
	// ... It returns the argument multiplied by two.
	Function<Integer, Integer> func = x -> x * 2;

	// Apply the function to an argument of 10.
	int result = func.apply(10);
	System.out.println(result);
    }
}

Output

20

Supplier, lambda arguments. A Supplier object receives no arguments. We use an empty argument list to specify a lambda expression with no arguments.

Tip: A Supplier provides values. We call get() on it to retrieve its value—it may return different values when called more than once.

Java program that uses Supplier, lambdas

import java.util.function.*;

public class Program {

    static void display(Supplier<Integer> arg) {
	System.out.println(arg.get());
    }

    public static void main(String[] args) {

	// Pass lambdas to the display method.
	// ... These conform to the Supplier class.
	// ... Each returns an Integer.
	display(() -> 10);
	display(() -> 100);
	display(() -> (int) (Math.random() * 100));
    }
}

Output

10
100
21

Predicate Lambda, ArrayList. The term predicate is used in computer science to mean a boolean-returning method. A Predicate object receives one value and returns true or false.

RemoveIf: This method on ArrayList receives a Predicate. Here, we remove all elements starting with the letter "c."

Java program that uses removeIf, Predicate lambda

import java.util.ArrayList;

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

	// Create ArrayList and add four String elements.
	ArrayList<String> list = new ArrayList<>();
	list.add("cat");
	list.add("dog");
	list.add("cheetah");
	list.add("deer");

	// Remove elements that start with c.
	list.removeIf(element -> element.startsWith("c"));
	System.out.println(list.toString());
    }
}

Output

[dog, deer]

Consumer. Opposite a Supplier, a Consumer acts upon a value but returns nothing. It means a void method. We can use a consumer to call println or other void methods.

Also: A Consumer can be used to mutate data, as in an array, ArrayList or even just a class field.

Java program that uses Consumer

import java.util.function.*;

public class Program {

    static void display(int value) {

	switch (value) {
	case 1:
	    System.out.println("There is 1 value");
	    return;
	default:
	    System.out.println("There are " + Integer.toString(value)
		    + " values");
	    return;
	}
    }

    public static void main(String[] args) {

	// This consumer calls a void method with the value.
	Consumer<Integer> consumer = x -> display(x - 1);

	// Use the consumer with three numbers.
	consumer.accept(1);
	consumer.accept(2);
	consumer.accept(3);
    }
}

Output

There are 0 values
There is 1 value
There are 2 values

UnaryOperator. This functional object receives a value of a certain type (like Integer) and returns a same-typed value. So it operates on, and returns, a value.

Java program that uses UnaryOperator

import java.util.function.*;

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

	// This returns one value of the same type as its one parameter.
	// ... It means the same as the Function below.
	UnaryOperator<Integer> operator = v -> v * 100;

	// This is a generalized form of UnaryOperator.
	Function<Integer, Integer> function = v -> v * 100;

	System.out.println(operator.apply(5));
	System.out.println(function.apply(6));
    }
}

Output

500
600

UnaryOperator, ArrayList. This example uses a lambda expression as a UnaryOperator argument to the ArrayList's replaceAll method. It adds ten to all elements.

ArrayList

Note: The forEach method on ArrayList does not change element values. ReplaceAll allows this action.

Java program that uses replaceAll, UnaryOperator

import java.util.ArrayList;

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

	// Add ten to each element in the ArrayList.
	ArrayList<Integer> list = new ArrayList<>();
	list.add(5);
	list.add(6);
	list.add(7);
	list.replaceAll(element -> element + 10);
	// ... Display the results.
	System.out.println(list);
    }
}

Output

[15, 16, 17]

BiConsumer, HashMap. A BiConsumer is a functional object that receives two parameters. Here we use a BitConsumer in the forEach method on HashMap.

HashMap

Note: The forEach lambda here, which is a valid BiConsumer, prints out all keys, values, and the keys' lengths.

Java that uses BiConsumer, HashMap forEach

import java.util.HashMap;

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

	HashMap<String, String> hash = new HashMap<>();
	hash.put("cat", "orange");
	hash.put("dog", "black");
	hash.put("snake", "green");
	// Use lambda expression that matches BiConsumer to display HashMap.
	hash.forEach((string1, string2) -> System.out.println(string1 + "..."
		+ string2 + ", " + string1.length()));
    }
}

Output

cat...orange, 3
snake...green, 5
dog...black, 3

Identifiers. These do not matter in a lambda expression. The identifiers do not impact external parts of the program, but can be accessed on both sides of the lambda.

Note: As with variables, there is no reason to name the lambda expression variable a specific thing. Here we use the word "carrot."

Java that uses unusual lambda identifier

import java.util.function.Consumer;

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

	// The identifier in the lambda expression can be anything.
	Consumer<Integer> consumer = carrot -> System.out.println(carrot);
	consumer.accept(1989);
    }
}

Output

1989

Function apply versus method. Here we benchmark a Function object, which we invoke with apply(), against a static method. Both code blocks do the same thing.

Result: The Function object's apply() method is much slower than the static method. The lambda syntax has less optimization.

Thus: If a method can be called with no loss of code clarity, this may result in better performance over a lambda or functional object.

Methods

Java that times Function apply, method call

import java.util.function.*;

public class Program {

    static int method(int element) {
	return element + 1;
    }

    public static void main(String[] args) {

	Function<Integer, Integer> function = element -> element + 1;

	long t1 = System.currentTimeMillis();

	// Version 1: apply a function specified as a lambda expression.
	for (int i = 0; i < 10000000; i++) {
	    int result = function.apply(i);
	    if (result == -1) {
		System.out.println(false);
	    }
	}

	long t2 = System.currentTimeMillis();

	// Version 2: call a static method.
	for (int i = 0; i < 10000000; i++) {
	    int result = method(i);
	    if (result == -1) {
		System.out.println(false);
	    }
	}

	long t3 = System.currentTimeMillis();

	// ... Benchmark results.
	System.out.println(t2 - t1);
	System.out.println(t3 - t2);
    }
}

Results

93 ms,    Function apply()
 6 ms,    method call

Filter. This method works on streams like IntStream. It returns a modified stream. And we can use methods like findFirst to access elements from filtered streams.

Filter

Sum. With a lambda expression, IntStream and the reduce() method we can sum an array. This approach has better parallel potential. But it is slow in simple cases.

Sum

Lambda calculus was introduced in 1936. In this form of mathematics, a function is an object. In a higher-order function, we pass a function object as an argument to a function.

Lambda calculus is a conceptually simple universal model of computation.

Lambda calculus: Wikipedia

At first, functional object names in Java are confusing. What is a Supplier? What is a Consumer? What is supplying what, and who is consuming?

With practice, and some effort, the functional object system in this language is powerful and expressive. It is beautiful. It condenses syntax for complex logical forms.

And with more expressive programs, it becomes possible to improve program quality. Fewer bugs may crawl about. A lambda has strict requirements: the compiler helps checks its validity.


Related Links

Adjectives Ado Ai Android Angular Antonyms Apache Articles Asp Autocad Automata Aws Azure Basic Binary Bitcoin Blockchain C Cassandra Change Coa Computer Control Cpp Create Creating C-Sharp Cyber Daa Data Dbms Deletion Devops Difference Discrete Es6 Ethical Examples Features Firebase Flutter Fs Git Go Hbase History Hive Hiveql How Html Idioms Insertion Installing Ios Java Joomla Js Kafka Kali Laravel Logical Machine Matlab Matrix Mongodb Mysql One Opencv Oracle Ordering Os Pandas Php Pig Pl Postgresql Powershell Prepositions Program Python React Ruby Scala Selecting Selenium Sentence Seo Sharepoint Software Spellings Spotting Spring Sql Sqlite Sqoop Svn Swift Synonyms Talend Testng Types Uml Unity Vbnet Verbal Webdriver What Wpf