Learn the differences between Supplier
and Function
in Java 8, including their purpose, method signatures, use cases, and practical examples to help you write cleaner and more efficient functional code.
🔍 Introduction
Java 8 introduced several functional interfaces under the java.util.function
package to support functional programming. Two commonly used interfaces are:
Supplier<T>
: for providing values without input.Function<T, R>
: for transforming input into output.
Although both are single-method interfaces, they serve different purposes. Let's break them down.
1️⃣ What is a Supplier?
📌 Purpose:
Represents a supplier of results. It takes no arguments and returns a result.
📌 Method:
java
T get();
📌 Signature:
java
@FunctionalInterface
public interface Supplier<T> {
T get();
}
✅ Example:
java
import java.util.function.Supplier;
public class SupplierExample {
public static void main(String[] args) {
// Lambda
Supplier<String> lambdaSupplier = () -> "Hello, Supplier!";
// Method Reference
Supplier<String> methodRefSupplier = SupplierExample::getMessage;
System.out.println(lambdaSupplier.get()); // Output: Hello, Supplier!
System.out.println(methodRefSupplier.get()); // Output: Hello, Method Reference!
}
public static String getMessage() {
return "Hello, Method Reference!";
}
}
2️⃣ What is a Function?
📌 Purpose:
Represents a function that takes one argument and produces a result.
📌 Method:
java
R apply(T t);
📌 Signature:
java
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
✅ Example:
java
import java.util.function.Function;
public class FunctionExample {
public static void main(String[] args) {
// Lambda
Function<String, Integer> lambdaFunction = s -> s.length();
// Method Reference
Function<String, Integer> methodRefFunction = String::length;
System.out.println(lambdaFunction.apply("Hello, Function!")); // Output: 16
System.out.println(methodRefFunction.apply("Hello, Method Reference!")); // Output: 23
}
}
🔄 Key Differences: Supplier vs Function
FeatureSupplierFunctionTakes Input?❌ No✅ Yes (one input of type T
)Returns Output?✅ Yes (type T
)✅ Yes (type R
)Method Nameget()apply(T t)
Common Use CasesLazy initialization, cachingData transformation, mapping
🛠️ Real-World Examples
✅ Supplier Example: Lazy Initialization
java
import java.util.function.Supplier;
public class LazyInitialization {
private static Supplier<ExpensiveObject> expensiveObjectSupplier = ExpensiveObject::new;
public static void main(String[] args) {
// Object is created only when get() is called
ExpensiveObject obj = expensiveObjectSupplier.get();
obj.performAction();
}
}
class ExpensiveObject {
ExpensiveObject() {
System.out.println("ExpensiveObject created!");
}
void performAction() {
System.out.println("Action performed!");
}
}
🧠 Use Case: Delay creating heavy objects until actually needed.
✅ Function Example: Mapping List of Strings to Lengths
java
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class FunctionExample {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
Function<String, Integer> stringLengthFunction = s -> s.length();
List<Integer> lengths = fruits.stream()
.map(stringLengthFunction)
.collect(Collectors.toList());
System.out.println(lengths); // Output: [5, 6, 6]
}
}
🧠 Use Case: Convert input data (strings) into something else (lengths).
✅ Summary
- Use
Supplier<T>
when you don't need any input but want to generate or retrieve a value. - Use
Function<T, R>
when you need to transform an input of type T
into a result of type R
.
Choosing the right interface helps write cleaner, more expressive, and modular Java 8 code.