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 | |
} |
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"; | |
} | |
} |
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
The "default 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
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
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
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
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
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
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
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
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
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
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
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
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
The "static
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.