Java 8 Lambdas, Functional Interface & "static" and "default" methods

  • 4.4/5
  • 8346
  • Jul 20, 2024

Java 8: "static" and "default" methods in interface

All "interface" methods are implicitly "public" and "abstract," unless declared as "static" or "default."

As of Java 8, it is now possible to inherit concrete methods (static and default) from an interface.

package java8.defaultAndStaticMethods;
public interface InterfaceWithConcreteMethods {
// abstract method
String getName();
// 'static' concrete method
static int doubleIt(int number) {
return 2 * number;
}
// 'default' concrete method
default int multiply(int a, int b) {
return a * b;
}
}

The implementation class is not required to implement "static" or "default" methods.

package java8.defaultAndStaticMethods;
public class ImplA implements InterfaceWithConcreteMethods {
@Override
public String getName() {
return "ImplA";
}
// no need to implement "static" or "default" methods
}
view raw ImplA.java hosted with ❤ by GitHub

Optionally, an implementation class can inherit "default" interface methods but not "static" interface methods.

package java8.defaultAndStaticMethods;
public class ImplB implements InterfaceWithConcreteMethods{
// implemented and not overridden
public static int doubleIt(int number) {
return 2 * number;
}
/*
Not allowed
public default int multiply(int a, int b) {
return a * b;
}
*/
@Override
public String getName() {
return "ImplA";
}
}
view raw ImplB.java hosted with ❤ by GitHub

Functional Interfaces

A functional interface is an interface that contains only one abstract method.

package java8.functionalInterfaces;
@FunctionalInterface
public interface FunctionalInterfaceA {
// single abstract method
String getName();
}

In this SAM (single abstract method) rule, "default," "static," and methods inherited from "Object" class do not count.

package java8.functionalInterfaces;
@FunctionalInterface
public interface FunctionalInterfaceB {
// single abstract method
String getName();
// 'static' concrete method
static int doubleIt(int number) {
return 2 * number;
}
// 'default' concrete method
default int multiply(int a, int b) {
return a * b;
}
/*
* In 'Object' class -
*
* @IntrinsicCandidate
* public native int hashCode();
*
* */
@Override
int hashCode();
}

In the example above, the "int hashCode()" method is overridden from the "Object" class.

Java 8 lambdas

A "Lambda" expression is an instance of a class that implements a functional interface.

Lambdas resemble "methods," but they are instances, not "methods" or "anonymous-methods."

Using a custom Functional Interface

"Lambda" expressions only work with Functional Interfaces.

package java8.lambda;
public class LambdasDemo {
@FunctionalInterface
interface UserName {
// single abstract method
String getFullName(String firstName, String lastName);
}
public static void main(String[] args) {
// Before Java 8
UserName userNameBeforeJava8 = new UserName() {
@Override
public String getFullName(String firstName, String lastName) {
return firstName + " " + lastName;
}
};
String fullNameBeforeJava8 = userNameBeforeJava8.getFullName("Tech", "Burps");
// With Java 8
UserName userNameInJava8 = (firstName, lastName) -> firstName + " " + lastName;
String fullNameInJava8 = userNameInJava8.getFullName("Tech", "Burps");
}
}

A functional interface defines the target type of a lambda expression.

package java8.lambda;
public class LambdasDemoA {
@FunctionalInterface
interface Doubler<U, V> {
// single abstract method
V doubleIt(U u);
}
public static void main(String[] args) {
Doubler<String, String> stringDoubler = (s1) -> s1 + s1;
Doubler<Integer, Long> intDoubler = (s1) -> Long.valueOf(2 * s1);
// use {} for multiline implementation
Doubler<Integer, Long> intDoublerMultiLine = (s1) -> {
int v = 2 * s1;
// with Java >= 10
//var v = 2 * s1;
return Long.valueOf(v);
};
/*
* Not valid - Cannot infer type: lambda expression requires an explicit target type
*
* var intDoubler = (s1) -> Long.valueOf(2 * s1);
*/
}
}

The "var" keyword introduced in "Java 10" cannot be used to infer a lambda expression. ("Cannot infer type": lambda expressions require an explicit target type.)

The "java.util.function" package

The "java.util.function" package comes with a set of pre-defined functional interfaces.

1) Predicate

Predicate is a functional interface that has the functional method "boolean test(T t)". It accepts "one argument" and returns a boolean (true if the input argument matches the predicate, otherwise false).

package java8.javaUtilFunction.predicate;
import java.util.function.Predicate;
public class PredicateDemo {
public static void main(String[] args) {
// functional method boolean test(T t)
Predicate<String> contains = s -> s.contains("abc");
// or Predicate<String> predicate = (String s) -> s.contains("abc");
String input = "xyzabcuvw";
System.out.println(input + " contains 'abc': " + contains.test(input)); // xyzabcuvw contains 'abc': true
}
}

1.1) Predicate as a method parameter

Predicates can be passed as method parameters:

package java8.javaUtilFunction.predicate;
import java.util.function.Predicate;
public class PredicateAsParameter {
// Passing "Predicate" as a parameter
public static <T> boolean checkIt(T t, Predicate<T> predicate) {
return predicate.test(t);
}
public static void main(String[] args) {
// Passing "Predicate" as a parameter
System.out.println(checkIt("abc", (String s) -> s.contains("abc"))); // true
System.out.println(checkIt(7, (Integer s) -> s % 2 == 0)); // false
}
}

1.2) Composed predicate (AND and OR)

The "default Predicate and(Predicate other)" method in the "Predicate" interface is a "non-abstract" method. It returns the logical AND of this predicate and the other predicate.

The "default Predicate or(Predicate other)" method in the "Predicate" interfaceis a "non-abstract" method. It returns the logical OR of this predicate and the other predicate.

package java8.javaUtilFunction.predicate;
import java.util.function.Predicate;
public class ComposedPredicate {
public static void main(String[] args) {
// default abstract method - Predicate<T> and(Predicate<? super T> other)
Predicate<String> p1 = s -> s.contains("xyz");
Predicate<String> p2 = s -> s.length() <= 5;
Predicate<String> p1AndP2 = p1.and(p2);
System.out.println(p1AndP2.test("bxyzc")); // true
System.out.println(p1AndP2.test("abxyzc")); // false
Predicate<String> p1OrP2 = p1.or(p2);
System.out.println(p1OrP2.test("abcd")); // true
System.out.println(p1OrP2.test("abxyzc")); // true
}
}

1.3) Predicate negation

The "default Predicate negate()" method in the "Predicate" interface is a "non-abstract" method. It returns a predicate that represents the logical negation of this predicate.

package java8.javaUtilFunction.predicate;
import java.util.function.Predicate;
public class PredicateNegate {
public static <T> boolean checkIt(T t, Predicate<T> predicate) {
return predicate.test(t);
}
public static void main(String[] args) {
Predicate<Integer> isEven = s -> s % 2 == 0;
System.out.println("Is even: " + checkIt(4, isEven)); // true
System.out.println("Is even: " + checkIt(4, isEven.negate())); // false
}
}

2) BiPredicate

BiPredicate is a functional interface that has the functional method "boolean test(T t, U u)". It accepts "two arguments" and returns a boolean (true if the input arguments match the predicate, otherwise false).

The "BiPredicate" interface also contains three "default" "non-abstract" methods: and(), or(), and negate().

package java8.javaUtilFunction.bipredicate;
import java.util.function.BiPredicate;
public class BiPredicateDemo {
public static <T, U> boolean checkIt(T t, U u, BiPredicate<T, U> predicate) {
return predicate.test(t, u);
}
public static void main(String[] args) {
BiPredicate<String, Integer> checkLength = (s, n) -> s.length() == n;
BiPredicate<Integer, Integer> isDividedBy = (n1, n2) -> n1 % n2 == 0;
BiPredicate<Integer, Integer> isEquals = (n1, n2) -> n1 == n2;
//and
BiPredicate<Integer, Integer> isDividedByAndIsEquals = isDividedBy.and(isEquals);
System.out.println(checkIt(10, 2, isDividedByAndIsEquals)); // false
System.out.println(checkIt(10, 10, isDividedByAndIsEquals)); // true
//or
BiPredicate<Integer, Integer> isDividedByOrIsEquals = isDividedBy.or(isEquals);
System.out.println(checkIt(10, 2, isDividedByOrIsEquals)); // true
System.out.println(checkIt(10, 10, isDividedByOrIsEquals)); // true
//negate
System.out.println(checkIt(10, 2, isDividedByOrIsEquals.negate())); // false
}
}

3) Supplier

Supplier is a functional interface that has the functional method "T get()". It accepts "zero arguments" and returns a result of type T.

package java8.javaUtilFunction.supplier;
import java.util.function.Supplier;
public class SupplierDemo {
public static void main(String[] args) {
Supplier<Double> randomNumber = () -> Math.random();
System.out.println(randomNumber.get()); // 0.5528909748432393
}
}

4) Consumer

Consumer is a functional interface that has the functional method "void accept(T t)". It accepts a single argument and returns no result.

The "default Consumer andThen(Consumer after)" method in the "Consumer" interface is a "non-abstract" method. It returns a composed Consumer that performs in sequence this operation followed by the after operation.

package java8.javaUtilFunction.consumer;
import java.util.function.Consumer;
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> printName = name -> System.out.println("My Name is: " + name);
Consumer<String> printNameInBlockLetters = name -> System.out.println(("My Name is: " + name.toUpperCase()));
// andThen
Consumer<String> printComposedName = printName.andThen(printNameInBlockLetters);
printComposedName.accept("Leonardo DiCaprio");
}
}

My Name is: Leonardo DiCaprio
My Name is: LEONARDO DICAPRIO

5) BiConsumer

BiConsumer is a functional interface that has the functional method "void accept(T t, U u)". It accepts two arguments and returns no result.

The "default BiConsumer andThen(BiConsumer after)" is a "default" "non-abstract" method in the "BiConsumer" interface. It returns a composed BiConsumer that performs in sequence this operation followed by the after operation.

package java8.javaUtilFunction.biconsumer;
import java.util.function.BiConsumer;
public class BiConsumerDemo {
public static void main(String[] args) {
BiConsumer<String, Integer> printDetails = (name, age) -> System.out.println(
"My Name is '" + name + "', I am '" + age + "'.");
BiConsumer<String, Integer> printDetailsInCaps = (name, age) -> System.out.println(
"My Name is '" + name.toUpperCase() + "', I am '" + age + "'.");
// andThen
BiConsumer<String, Integer> printComposedDetails = printDetails.andThen(printDetailsInCaps);
printComposedDetails.accept("Leonardo DiCaprio", 32);
}
}

My Name is 'Leonardo DiCaprio', I am '32'.
My Name is 'LEONARDO DICAPRIO', I am '32'.

6) Function

Function is a functional interface that has the functional method "R apply(T t)". It accepts one argument and returns one result.

package java8.javaUtilFunction.function;
import java.util.function.Function;
public class FunctionDemo {
public static void main(String[] args) {
Function<String, Integer> length = s -> s.length();
String goddess = "Marilyn Monroe";
System.out.println(
"Length of '" + goddess + "' is " + length.apply(goddess)); // Length of 'Marilyn Monroe' is 14
}
}

The "default Function compose(Function before)" is a "default" "non-abstract" method in the "Function" interface.

It returns a composed function that first applies the before function to its input, and then applies this function to the result.

package java8.javaUtilFunction.function;
import java.util.function.Function;
public class ComposedFunction {
public static void main(String[] args) {
Function<Double, Double> doubleMe = n -> 2 * n;
Function<Double, Double> addFive = n -> 5 + n;
Function<Double, Double> divideMe = n -> n / 2;
Function<Double, Double> composed = doubleMe.compose(addFive).compose(divideMe);
// ((10/2) + 5) * 2 = 20.0
System.out.println(composed.apply(10.0)); // 20.0
}
}

The "default Function andThen(Function after)" is a "default" "non-abstract" method in the "Function" interface.

It returns a composed function that first applies this function to its input, and then applies the after function to the result.

package java8.javaUtilFunction.function;
import java.util.function.Function;
public class FunctionAndThen {
public static void main(String[] args) {
Function<Double, Double> doubleMe = n -> 2 * n;
Function<Double, Double> addFive = n -> 5 + n;
Function<Double, Double> divideMe = n -> n / 2;
Function<Double, Double> composed = doubleMe.andThen(addFive).andThen(divideMe);
// ((2 * 10) + 5) / 2 = 12.5
System.out.println(composed.apply(10.0)); // 12.5
}
}

7) BiFunction

BiFunction is a functional interface that has the functional method "R apply(T t, U u)". It accepts two arguments and produces a result.

package java8.javaUtilFunction.bifunction;
import java.util.function.BiFunction;
public class BiFunctionDemo {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> sum = (n, m) -> n + m;
int a = 10, b = 20;
System.out.println(
"Sum of '" + a + "' and '" + b + "' is: " + sum.apply(a, b)); // Sum of '10' and '20' is: 30
}
}

The "default BiFunction andThen(Function after)" is a "default" "non-abstract" method in the "BiFunction" interface.

It returns a composed function that first applies this function to its input, and then applies the after function to the result.

package java8.javaUtilFunction.bifunction;
import java.util.function.BiFunction;
import java.util.function.Function;
public class FunctionAndThen {
public static void main(String[] args) {
BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
Function<String, String> upperCase = s -> s.toUpperCase();
BiFunction<String, String, String> composed = concat.andThen(upperCase);
System.out.println(composed.apply("Tech", "Burps")); // TECHBURPS
}
}

8) UnaryOperator

UnaryOperator is a functional interface that extends another functional interface Function. Its functional method is R apply(T t), the one extended from Function.

UnaryOperator represents an operation on a single operand that produces a result of the same type as its operand.

This is a specialization of Function for the case where the operand and result are of the same type.

package java8.javaUtilFunction.unaryoperator;
import java.util.function.UnaryOperator;
public class UnaryOperatorDemo {
public static void main(String[] args) {
UnaryOperator<String> upperCase = s -> s.toUpperCase();
System.out.println(upperCase.apply("techburps")); // TECHBURPS
}
}

The "static UnaryOperator identity()" is a "static" "non-abstract" method in the "UnaryOperator" interface. It returns a unary operator that always returns its input argument.

package java8.javaUtilFunction.unaryoperator;
import java.util.function.UnaryOperator;
public class UnaryOperatorIdentity {
public static void main(String[] args) {
UnaryOperator<String> identity = UnaryOperator.identity();
System.out.println(identity.apply("Apple")); // Apple
}
}

9) BinaryOperator

BinaryOperator is a functional interface that extends another functional interface BiFunction. Its functional method is R apply(T t, U u), the one extended from BiFunction.

It represents an operation upon two operands of the same type, producing a result of the same type as the operands.

This is a specialization of BiFunction for the case where the operands and the result are all of the same type.

package java8.javaUtilFunction.binaryoperator;
import java.util.function.BinaryOperator;
public class BinaryOperatorDemo {
public static void main(String[] args) {
BinaryOperator<Integer> sum = (a, b) -> a + b;
System.out.println(sum.apply(10, 20)); //30
}
}

The "static BinaryOperator minBy(Comparator comparator)" is a "static" "non-abstract" method in the "BinaryOperator" interface. It returns a BinaryOperator which returns the lesser of two elements according to the specified Comparator.

The "static BinaryOperator maxBy(Comparator comparator)" is a "static" "non-abstract" method in the "BinaryOperator" interface. It returns a BinaryOperator which returns the greater of two elements according to the specified Comparator.

package java8.javaUtilFunction.binaryoperator;
import java.util.function.BinaryOperator;
public class BinaryOperatorMinByMaxBy {
public static void main(String[] args) {
BinaryOperator<Integer> minByA = BinaryOperator.minBy((a, b) -> a.compareTo(b));
System.out.println(minByA.apply(10, 20)); // 10
BinaryOperator<Integer> minByB = BinaryOperator.minBy((a, b) -> b.compareTo(a));
System.out.println(minByB.apply(10, 20)); // 20
BinaryOperator<Integer> maxByA = BinaryOperator.maxBy((a, b) -> a.compareTo(b));
System.out.println(maxByA.apply(10, 20)); // 20
BinaryOperator<Integer> maxByB = BinaryOperator.maxBy((a, b) -> b.compareTo(a));
System.out.println(maxByB.apply(10, 20)); // 10
}
}

Lambda Benefits

1) A lambda helps in creating a function without belonging to any class.

2) It allows you to treat a piece of functionality as a method argument or code as data.

3) A lambda expression can be passed around as if it were an object and executed on demand.

Index
Modern Java - What’s new in Java 9 to Java 17

32 min

How to Install Java on Windows, Linux and macOS?

2 min

How to Set Java Path in Windows, Linux and Mac?

2 min

Differences between JDK, JRE and JVM

2 min

What is ClassLoader in Java ?

2 min

Object Oriented Programming (OOPs) Concept

17 min

Concurrency in Java: Creating and Starting a Thread

12 min

Concurrency in Java: Interrupting and Joining Threads

5 min

Concurrency in Java: Race condition, critical section, and atomic operations

13 min

Concurrency in Java: Reentrant, Read/Write and Stamped Locks

11 min

Concurrency in Java: "synchronized" and "volatile" keywords

10 min

Concurrency in Java: using wait(), notify() and notifyAll()

6 min

Concurrency in Java: What is "Semaphore" and its use?

2 min

Concurrency in Java: CompletableFuture and its use

18 min

Concurrency in Java: Producer-consumer problem using BlockingQueue

2 min

Concurrency in Java: Producer-Consumer Problem

2 min

Concurrency in Java: Thread pools, ExecutorService & Future

14 min

Java 8 Lambdas, Functional Interface & "static" and "default" methods

28 min

Method Reference in Java (Instance, Static, and Constructor Reference)

9 min

What's new in Java 21: A Tour of its Most Exciting Features

14 min

Java Memory Leaks & Heap Dumps (Capturing & Analysis)

9 min

Memory footprint of the JVM (Heap & Non-Heap Memory)

15 min