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::myInstanceMethod
MyType
" 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)