default keyword.@FunctionalInterface" annotation to make it explicit that it is intended to be a functional interface.
The annotation is optional but recommended as it provides compile-time checking and clear documentation.java.util.function package:Predicate<T>: Takes input of type T and returns boolean (boolean test(T t);).Function<T,R>: Takes input of type T and returns result of type R (R apply(T t);).Consumer<T>: Takes input of type T and returns void. It should performs side effects. (void accept(T t);).Supplier<T>: Takes no input and returns result of type T (T get();).UnaryOperator<T>: Takes input of type T and returns result of same type T (T apply(T t);).BinaryOperator<T>: Takes two inputs of type T and returns result of type T (T apply(T t, T u);).java.util.function.Predicate
@FunctionalInterface
public interface Predicate<T> {
// This is the only abstract method in the intervace which makes it a functional interface
boolean test(T t);
// Default and Static methods don't break the functional interface contract
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object);
}
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>) target.negate();
}
}
MyFunctionalInterface foo(MyFunctionalInterface myFunctionalInterface) { ... }MyType::myStaticMethod
import java.util.function.UnaryOperator;
/*
// UnaryOperator extends Function<T,T>: it takes a parameter of type T and returns the same type T
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
}
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
*/
public class MainClass {
// Static method that matches UnaryOperator<String>::apply signature: String -> String
public static String toLowerCase(String value) {
return value == null ? "" : value.toLowerCase();
}
public static String convert(String value, UnaryOperator<String> myStringConverter) {
return myStringConverter.apply(value);
}
public static void main(String[] args) {
final String convertedValue = MainClass.convert("tESt", MainClass::toLowerCase);
System.out.println(convertedValue); // Output: test
}
}
myInstance::myInstanceMethod
import java.util.function.UnaryOperator;
public class MainClass {
// Instance method that matches UnaryOperator<String>::apply signature: String -> String
public String toUpperCase(String value) {
return value == null ? "" : value.toUpperCase();
}
public static String convert(String value, UnaryOperator<String> myStringConverter) {
return myStringConverter.apply(value);
}
public static void main(String[] args) {
final MainClass mainClass = new MainClass();
final String convertedValue = MainClass.convert("tESt", mainClass::toUpperCase);
System.out.println(convertedValue); // Output: TEST
}
}
MyType::myInstanceMethodMyType" as the first parameter:String::toUpperCase" implies that
the functional interface is of type "Function<String, String>":
it takes a String parameter and calls toUpperCase() on it.s -> s.toUpperCase()Function<String, String> toUpperCaseFunction = String::toUpperCase;
MainClass::toUpperCase" implies
that the functional interface is of type "Function<MainClass, String>":
it takes a MainClass parameter and calls toUpperCase() on it.mainClassInstance -> mainClassInstance.toUpperCase()Function<MainClass, String> toUpperCaseFunction = MainClass::toUpperCase;
import java.util.function.Function;
import java.util.function.UnaryOperator;
public class MainClass {
private String value;
public MainClass(String value) {
this.value = value;
}
public String toUpperCase() {
return value == null ? "" : value.toUpperCase();
}
// Accepts UnaryOperator<String>::apply signature: String -> String
public static void convert(String value, UnaryOperator<String> myStringConverter) {
System.out.println(myStringConverter.apply(value));
}
// Accepts Function<MainClass, String>::apply signature: MainClass -> String
public static void convert(MainClass object, Function<MainClass, String> myFunction) {
System.out.println(myFunction.apply(object));
}
public static void main(String[] args) {
// First example: String::toUpperCase matches UnaryOperator<String>::apply
// The String parameter becomes the object on which toUpperCase() is called
MainClass.convert("hello", String::toUpperCase); // Output: HELLO
// Second example: MainClass::toUpperCase matches Function<MainClass, String>::apply
// The MainClass parameter becomes the object on which toUpperCase() is called
MainClass.convert(new MainClass("hello"), MainClass::toUpperCase); // Output: HELLO
}
}
MyType::new
import java.util.function.Function;
import java.util.function.Supplier;
public class MainClass {
private String value;
public MainClass() {
this.value = "default";
System.out.println("Created with default value");
}
public MainClass(String value) {
this.value = value;
System.out.println("Created with value: " + value);
}
// Uses no-arg constructor: Supplier<MainClass>
public static MainClass build(Supplier<MainClass> supplier) {
return supplier.get(); // Equivalent to: () -> new MainClass()
}
// Uses constructor with parameter: Function<String, MainClass>
public static MainClass build(String value, Function<String, MainClass> myFunction) {
return myFunction.apply(value); // Equivalent to: t -> new MainClass(t)
}
public static void main(String[] args) {
// Uses no-arg constructor
MainClass instance1 = MainClass.build(MainClass::new); // Output: Created with default value
MainClass instance2 = MainClass.build(() -> new MainClass()); // Output: Created with default value
// Uses constructor with parameter
MainClass instance3 = MainClass.build("test", MainClass::new); // Output: Created with value: test
MainClass instance4 = MainClass.build("test", value -> new MainClass(value)); // Output: Created with value: test
}
}
The compiler determines which constructor to use based on the functional interface's signature:Supplier<T> with MyClass::new uses the no-argument constructor.Function<T, R> with MyClass::new uses the constructor that takes a parameter of type R.BiFunction<T, U, R> with MyClass::new would use a constructor taking two parameters of types U and R.(parameters -> statements)
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
public class MainClass {
public static String convert(String value, UnaryOperator<String> myStringConverter) {
return myStringConverter.apply(value);
}
public static String combine(String a, String b, BinaryOperator<String> combiner) {
return combiner.apply(a, b);
}
public static boolean test(String value, Predicate<String> tester) {
return tester.test(value);
}
public static void main(String[] args) {
// Simple expression lambda
final String convertedValue = MainClass.convert(" test ", t -> t == null ? "" : t.trim());
System.out.println(convertedValue); // Output: test
// Lambda with multiple parameters
String combined = MainClass.combine("Hello", "Lambda", (a, b) -> a + " " + b);
System.out.println(combined); // Output: Hello Lambda
// Block syntax lambda
final String convertedValue2 = MainClass.convert(" test ", t -> {
if (t == null) {
return "";
}
return t.trim();
});
System.out.println(convertedValue2); // Output: test
}
}
java.util.function" package:Predicate<T>: boolean test(T t)boolean test(T t)"
which declares a single parameter ("t" of type "T") and returns a true/false result.
Implementations typically apply some logic to the argument "t".
Predicates support logical operations like and(), or(), and negate() for method chaining.Predicate<Integer> isOdd = t -> t % 2 != 0; // Output: true System.out.println(isOdd.test(1)); Predicate<Integer> isEven = isOdd.negate(); System.out.println(isEven.test(1)); // Output: false
IntPredicate: boolean test(int t)LongPredicate: boolean test(long t)DoublePredicate: boolean test(double t)BiPredicate<T, U>: boolean test(T t, U u)boolean test(T t, U u)"
which declares two parameters ("t" of type "T", "u" of type "U") and returns a true/false result.
Implementations typically apply logic to the arguments "t" and "u".
Like Predicate, it supports logical operations for method chaining.BiPredicate<Object, Object> equals = (a, b) -> Objects.equals(a, b); System.out.println(equals.test(1, 2)); // Output: false BiPredicate<Object, Object> deepEquals = Objects::deepEquals; System.out.println(deepEquals.test(1, 2)); // Output: false BiPredicate<String, String> eitherNull = (a, b) -> a == null || b == null; System.out.println(eitherNull.test(null, "test")); // Output: true BiPredicate<String, String> bothNotNull = eitherNull.negate(); System.out.println(bothNotNull.test(null, "test")); // Output: false
Consumer<T>: void accept(T t)void accept(T t)"
which declares a single parameter ("t" of type "T") and does not return a value.
Implementations typically consume the value of "t" and produce a side effect.
Consumers support method chaining with andThen().
Consumer<String> println = t -> System.out.println(t);
println.accept("test"); // output: test
IntConsumer: void accept(int t)LongConsumer: void accept(long t)DoubleConsumer: void accept(double t)BiConsumer<T, U>: void accept(T t, U u)void accept(T t, U u)"
with two parameters ("t" of type "T", "u" of type "U") and no return value.
Implementations typically consume both arguments and produce side effects.
Supports method chaining with andThen().BiConsumer<String, String> printlnIfElse = (a, b) -> System.out.println(a == null ? b : a); printlnIfElse.accept(null, "test"); // Output: test
ObjIntConsumer<T>: void accept(T t, int u)ObjLongConsumer<T>: void accept(T t, long u)ObjDoubleConsumer<T>: void accept(T t, double u)Supplier<T>: T get()T get()"
which declares no parameters and returns a value of type "T".Supplier<Double> random = () -> Math.random(); System.out.println(random.get()); // Output: 0.7086586779711673
BooleanSupplier: boolean getAsBoolean()IntSupplier: int getAsInt()LongSupplier: long getAsLong()DoubleSupplier: double getAsDouble()Function<T, R>: R apply(T t)R apply(T t)"
which declares one parameter ("t" of type "T") and returns a value of type "R".
Implementations typically apply some logic to the argument "t".
Functions support method chaining with compose() and andThen().
Function<Integer, String> valueOf = t -> String.valueOf(t);
System.out.println(valueOf.apply(1)); // Output: 1
Function<String, String> trim = s -> s.trim();
System.out.println(trim.apply(" hello")); // Output: hello
Function<String, String> toUpperCase = s -> s.toUpperCase();
System.out.println(toUpperCase.apply(" hello")); // Output:HELLO
Function<String, String> trimThenToUpperCase = trim.andThen(toUpperCase);
System.out.println(trimThenToUpperCase.apply(" hello")); // Output: HELLO
IntFunction<R>: R apply(int t)LongFunction<R>: R apply(long t)DoubleFunction<R>: R apply(double t)ToIntFunction<T>: int applyAsInt(T t)ToLongFunction<T>: long applyAsLong(T t)ToDoubleFunction<T>: double applyAsDouble(T t)IntToLongFunction: long applyAsLong(int t)IntToDoubleFunction: double applyAsDouble(int t)LongToIntFunction: int applyAsInt(long t)LongToDoubleFunction: double applyAsDouble(long t)DoubleToIntFunction: int applyAsInt(double t)DoubleToLongFunction: long applyAsLong(double t)BiFunction<T, U, R>: R apply(T t, U u)R apply(T t, U u)"
with two parameters ("t" of type "T", "u" of type "U") and returns a value of type "R".
Implementations typically apply logic to both arguments.
Supports method chaining with andThen().BiFunction<Integer, Integer, String> valueOfMax = (a, b) -> "id-".concat(String.valueOf(Math.max(a, b))); System.out.println(valueOfMax.apply(1, 2)); // Output: id-2
ToIntBiFunction<T, U>: int applyAsInt(T t, U u)ToLongBiFunction<T, U>: long applyAsLong(T t, U u)ToDoubleBiFunction<T, U>: double applyAsDouble(T t, U u)UnaryOperator<T> extends Function<T, T>: T apply(T t)Function including compose() and andThen().UnaryOperator<Integer> abs = t -> Math.abs(t); System.out.println(abs.apply(-1)); // Output: 1
IntUnaryOperator: int applyAsInt(int t)LongUnaryOperator: long applyAsLong(long t)DoubleUnaryOperator: double applyAsDouble(double t)BinaryOperator<T> extends BiFunction<T, T, T>: T apply(T t, T u)BiFunction including andThen().BinaryOperator<Integer> add = (a, b) -> a + b; System.out.println(add.apply(3, 4)); // Output: 7
IntBinaryOperator: int applyAsInt(int t, int u)LongBinaryOperator: long applyAsLong(long t, long u)DoubleBinaryOperator: double applyAsDouble(double t, double u)