java.util.function Package
Master java.util.function: Predicate, Function, Supplier, Consumer, and their binary/triple variants for functional-style Java.
Master java.util.function: Predicate, Function, Supplier, Consumer, and their binary/triple variants for functional-style Java. The guide uses practical examples to explain when to use, when not to use and shows how to apply the ideas in a Spring Boot project. It closes with common pitfalls and production checks so you can apply the pattern with fewer surprises.
Introduction
Before Java 8, if you wanted to pass behavior around — say, a filter rule or a transformation — you wrote a custom interface:
// Pre-Java 8: every project had these lying around
interface StringProcessor {
boolean process(String input);
}
// Usage required an anonymous class — verbose
List<String> result = filter(list, new StringProcessor() {
public boolean process(String input) {
return input.length() > 3;
}
});
Java 8 gave us java.util.function — a standard library of purpose-built functional interfaces. No more custom interfaces for common patterns. The JDK ships Predicate for yes/no tests, Function for transformations, Consumer for side effects, and Supplier for lazy value production. These are the building blocks that back the Stream API, lambda expressions, and method references.
// Same filter, now using Predicate — concise and self-documenting
List<String> result = filter(list, s -> s.length() > 3);
Predicate<String> longerThanThree = s -> s.length() > 3;
List<String> filtered = list.stream()
.filter(longerThanThree)
.toList();
This post covers every interface in java.util.function: their signatures, composition methods, primitive specializations, and the pitfalls that trip people up in practice.
When to Use
| Interface | Signature | Use Case |
|---|---|---|
Predicate<T> |
T → boolean |
Filter conditions, validation rules |
Function<T,R> |
T → R |
Transform one type to another |
Supplier<T> |
() → T |
Lazy value production, factory methods |
Consumer<T> |
T → void |
Side-effect operations, forEach |
BinaryOperator<T> |
(T, T) → T |
Combining two values of same type |
UnaryOperator<T> |
T → T |
In-place transformation, identity operations |
BiPredicate<T,U> |
(T, U) → boolean |
Two-argument filter conditions |
BiFunction<T,U,R> |
(T, U) → R |
Two-argument transformations |
BiConsumer<T,U> |
(T, U) → void |
Two-argument side effects |
When NOT to Use
- Long-running IO operations: Functional interfaces are synchronous by design. Use
CompletableFutureor reactive types (Flux,Mono) for async work. - Checked exceptions: The functional interfaces do not declare checked exceptions. Wrap in unchecked exceptions or use a
Function<T, R>that re-throws. - Stateful predicates in streams: A
Predicateused as a stream filter should be stateless and pure — stateful predicates produce non-deterministic results when used in parallel pipelines.
Interface Taxonomy
classDiagram
class Predicate~T~ {
<<@FunctionalInterface>>
+test(T) boolean
+and(Predicate) Predicate
+or(Predicate) Predicate
+negate() Predicate
+isEqual(Object) Predicate
}
class Function~T,R~ {
<<@FunctionalInterface>>
+apply(T) R
+andThen(Function) Function
+compose(Function) Function
+identity() Function
}
class Supplier~T~ {
<<@FunctionalInterface>>
+get() T
}
class Consumer~T~ {
<<@FunctionalInterface>>
+accept(T) void
+andThen(Consumer) Consumer
}
class BinaryOperator~T~ {
<<@FunctionalInterface>>
+apply(T, T) T
+minBy(Comparator) BinaryOperator
+maxBy(Comparator) BinaryOperator
}
class UnaryOperator~T~ {
<<@FunctionalInterface>>
+apply(T) T
}
Predicate ..|> Object
Function ..|> Object
Supplier ..|> Object
Consumer ..|> Object
BinaryOperator --|> BiFunction
UnaryOperator --|> Function
Code Examples
Predicate — Filtering and Conditions
A Predicate<T> takes an argument of type T and returns a boolean. Think of it as a yes-or-no question about a value. You’ll see it most often in Stream.filter(predicate), where it decides which elements pass through and which get dropped. But Predicate shows up elsewhere in the JDK too: List.removeIf(Predicate), Optional.filter(Predicate), Collection.removeIf(Predicate) in Java 8+. If you’ve ever written if (someCondition(x)) as a gate before an operation, a Predicate is probably what you actually need.
Predicate gives you three composition methods. predicate.and(other) chains with AND, predicate.or(other) chains with OR, and predicate.negate() inverts the result. Predicate.isEqual(target) creates a predicate that uses Objects.equals() instead of ==, which comes in handy when matching a stream element against a specific value. This lets you build complex conditions from simple parts instead of nested if statements.
import java.util.function.Predicate;
import java.util.List;
Predicate<String> isNonBlank = s -> !s.isBlank();
Predicate<String> hasAtSymbol = s -> s.contains("@");
Predicate<String> isValidEmail = isNonBlank.and(hasAtSymbol);
// negate
Predicate<String> isInvalidEmail = isValidEmail.negate();
// Combining with or
Predicate<String> isAdminOrUser = s -> s.startsWith("admin_")
.or(s -> s.startsWith("user_"));
// static isEqual
Predicate<Object> isNull = Predicate.isEqual(null);
Predicate<String> isHello = Predicate.isEqual("hello");
// Using in stream filter
List<String> emails = List.of("alice@example.com", "bob", "charlie@test.com");
List<String> valid = emails.stream()
.filter(isValidEmail)
.toList(); // [alice@example.com, charlie@test.com]
Function — Transformations
A Function<T, R> converts an input of type T into an output of type R. It shows up in map(Function) in streams, Optional.map(Function), and as the parent of BiFunction. Typical examples: String::toUpperCase (String to String), User::getEmail (User to String), s -> s.length() (String to Integer). Functions are how you express data transformations declaratively.
andThen and compose let you chain functions. f.andThen(g) applies f first, then g to the result, so f.andThen(g).apply(x) equals g(f(x)). compose reverses this: f.compose(g) applies g first, then f, giving f(g(x)). Use andThen when each step feeds its output into the next. Function.identity() returns its input unchanged. It’s useful as a passthrough in collectors and mapping pipelines.
import java.util.function.Function;
import java.util.List;
Function<String, Integer> length = String::length;
Function<Integer, String> repeat = n -> "*".repeat(n);
// andThen — apply this first, then the other
Function<String, String> label = length.andThen(repeat);
// compose — apply the other first, then this
Function<String, Integer> totalStars = repeat.compose(length.compose(repeat));
// identity
Function<String, String> identity = Function.identity();
// Chain multiple transforms
record User(Long id, String name, String department) {}
List<User> users = List.of(
new User(1L, "Alice", "Engineering"),
new User(2L, "Bob", "Marketing")
);
List<String> deptNames = users.stream()
.map(u -> u.department())
.map(String::toUpperCase)
.distinct()
.toList(); // [ENGINEERING, MARKETING]
Supplier — Lazy Evaluation
A Supplier<T> produces a value of type T on demand, taking no arguments. The main use case is deferred computation: instead of creating an expensive default upfront and paying the cost even when you don’t need it, you pass a Supplier that only computes when get() is called. This is why Optional.orElseGet(Supplier) exists alongside the eager Optional.orElse(value).
Suppliers show up in factory patterns. A supplier is essentially a way to create objects without tying yourself to how they’re instantiated. () -> new Connection() is a Supplier<Connection>. Method references like MyService::createClient are the idiomatic way to write this. Note that Supplier does not memoize — every call to get() runs the supplier body again. For caching, use a lazy initialization holder or a memoizing wrapper. BooleanSupplier has getAsBoolean() instead of get() to avoid boxing.
import java.util.function.Supplier;
// Lazy default value — only computed when absent
Supplier<Connection> connectionSupplier = () -> createExpensiveConnection();
Connection conn = Optional.ofNullable(cachedConnection)
.orElseGet(connectionSupplier);
// Factory pattern
Supplier<LocalDate> todaySupplier = LocalDate::now;
// Singleton pattern
Supplier<List<String>> listSupplier = () -> new ArrayList<>(); // new list each time
// For constant list
Supplier<List<String>> constantList = List::of; // same list returned every time
Consumer — Side Effects
A Consumer<T> takes a T and returns nothing — it performs a side effect. The classic use is Stream.forEach(Consumer), where the consumer receives each element and does something with it: print it, log it, add it to a collection. Because consumers return void, you cannot chain them the way you chain Function. Instead, andThen runs two consumers sequentially on the same input — useful for composing side effects like logging followed by state updates.
BiConsumer<T, U> takes two arguments and performs a side effect. You’ll see it in Map.forEach((key, value) -> …) or List.replaceAll(UnaryOperator). The Obj* consumers — ObjIntConsumer, ObjLongConsumer, ObjDoubleConsumer — accept one object and one primitive. This means no boxing when you need to work with both. Use consumers when you’re doing something that produces no return value: logging, updating UI state, sending data to an external system.
import java.util.function.Consumer;
// forEach with Consumer
Consumer<String> printer = System.out::println;
List.of("a", "b", "c").forEach(printer);
// andThen chain
Consumer<String> upperPrinter = s -> System.out.println(s.toUpperCase());
Consumer<String> withTimestamp = s -> System.out.println("[INFO] " + s);
Consumer<String> combined = upperPrinter.andThen(withTimestamp);
combined.accept("hello"); // prints HELLO then [INFO] hello
// BiConsumer
BiConsumer<String, Integer> kvPrinter = (k, v) -> System.out.println(k + "=" + v);
kvPrinter.accept("score", 42); // score=42
BinaryOperator and UnaryOperator
BinaryOperator<T> is a BiFunction<T, T, T> — both inputs and the output are the same type T. It models operations that combine two T values into one: addition, string concatenation, list merging, finding max or min. The stream.reduce(identity, BinaryOperator) pattern uses a BinaryOperator to accumulate elements. BinaryOperator.minBy(Comparator) and BinaryOperator.maxBy(Comparator) are factory methods for finding extremes according to a comparator.
UnaryOperator<T> is a Function<T, T> — input and output types are identical. Use it when a transformation keeps the same type. List.replaceAll(UnaryOperator) applies a unary operator to every element in a list, mutating it in place. The difference between UnaryOperator and Function<T, T> is stylistic. UnaryOperator makes it obvious that the transformation preserves type, which makes APIs easier to read.
import java.util.function.BinaryOperator;
import java.util.Arrays;
// BinaryOperator — combine two same-typed values
BinaryOperator<Integer> sum = Integer::sum;
BinaryOperator<Integer> max = Integer::max;
BinaryOperator<Integer> min = Integer::min;
// Using with reduce
int total = Arrays.asList(1, 2, 3, 4, 5).stream()
.reduce(0, sum); // 15
// minBy / maxBy
BinaryOperator<Integer> youngest = BinaryOperator.minBy(Comparator.comparingInt(User::getAge));
BinaryOperator<Integer> oldest = BinaryOperator.maxBy(Comparator.comparingInt(User::getAge));
// UnaryOperator — transform a value to same type
UnaryOperator<String> toTitleCase = s ->
s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
UnaryOperator<String> addPrefix = s -> "ID-" + s;
UnaryOperator<String> composed = addPrefix.compose(toTitleCase);
composed.apply("alice"); // "ID-Alice"
Primitive-Focused Specializations
The generic functional interfaces (Predicate<T>, Function<T, R>, Supplier<T>, Consumer<T>) work with object references, so primitive values get boxed into wrapper types on every call. For small datasets this doesn’t matter much, but in tight loops or long stream pipelines processing millions of primitives, the boxing overhead adds up. The primitive specializations (IntPredicate, IntFunction<R>, IntSupplier, IntConsumer, and their Long and Double counterparts) exist so you can stay in primitives throughout the pipeline without the conversion overhead.
IntPredicate takes a primitive int and returns boolean. ToIntFunction<T> takes any reference type and returns an int without boxing. IntFunction<R> takes a primitive int and returns a reference type R. When working with primitive streams like IntStream, LongStream, or DoubleStream, these specializations keep the pipeline in primitives, avoiding the boxing that would otherwise happen on every map or filter call. This matters most when processing large datasets where the overhead compounds.
import java.util.function.IntPredicate;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import java.util.function.IntConsumer;
import java.util.function.ToIntFunction;
// IntPredicate — avoids boxing
IntPredicate isEven = n -> n % 2 == 0;
boolean result = isEven.test(42); // true
// ToIntFunction — converts any type to int
ToIntFunction<String> strLength = String::length;
strLength.applyAsInt("hello"); // 5
// IntSupplier — generates primitive ints
IntSupplier randomInt = () -> new Random().nextInt(100);
// IntFunction — takes int, returns any type
IntFunction<String> repeatStars = n -> "*".repeat(n);
repeatStars.apply(3); // "***"
// IntConsumer
IntConsumer printInt = System.out::println;
IntStream.range(1, 4).forEach(printInt); // 1, 2, 3
Failure Scenarios
| Scenario | Problem | Solution |
|---|---|---|
| Stateful predicate in parallel stream | Non-deterministic results | Use stateless, pure predicates |
| Checked exception in lambda | Lambda cannot throw checked exceptions | Wrap in unchecked exception or use a wrapper utility |
Function.identity() in map with null value |
NPE if the function returns null | Use explicit (s) -> s instead of Function.identity() |
Consumer.andThen when first throws |
Second consumer never runs | Log and handle errors before chaining |
IntFunction returning null for non-primitive result |
Boxing ambiguity | Use Function<Integer, R> if null is a valid return |
Trade-off Table
| Aspect | Custom Interface | java.util.function |
|---|---|---|
| Reusability | May be scattered across codebase | Standardized, predictable semantics |
| API familiarity | Requires documentation | Self-documenting by type name |
| Compatibility with Streams | Requires adapter | Native compatibility |
| Naming clarity | Domain-specific names | Generic names (Predicate, Function, etc.) |
Observability Checklist
// Wrapping functions for observability
public <T, R> Function<T, R> observedFunction(String name, Function<T, R> fn) {
return t -> {
long start = System.nanoTime();
try {
R result = fn.apply(t);
System.out.println("metric=" + name + " duration_ns=" + (System.nanoTime() - start));
return result;
} catch (RuntimeException e) {
System.out.println("metric=" + name + " error=true");
throw e;
}
};
}
// Predicate with logging
public <T> Predicate<T> loggedPredicate(String label, Predicate<T> predicate) {
return t -> {
boolean result = predicate.test(t);
System.out.println("predicate=" + label + " input=" + t + " result=" + result);
return result;
};
}
- Wrap stream pipeline stages in observability proxies for latency-critical operations.
- Use
Consumerfor side-effect logging in forEach — not for business logic. - Track predicate failure rates to identify broken validation rules.
- Instrument
Supplier.get()calls to measure lazy initialization costs. - Add metric tags for function names when using functional composition patterns.
Security Notes
- Lambda capture of mutable state: Lambdas that capture and mutate external state create data races in concurrent contexts. Keep captured variables effectively immutable.
- Deserializing lambdas: Serialized lambdas (used in distributed caches or session storage) can be a vector for code injection if the classloader is compromised. Avoid serializing lambdas from untrusted sources.
- Predicate injection: User-supplied predicates in search or filtering APIs must be sandboxed — a malicious predicate could cause denial-of-service via exponential complexity (ReDoS).
Pitfalls
Consumer.andThenexecutes left-to-right, not right-to-left:a.andThen(b)means “apply a, then apply b to the result of a” — this is the natural flow but easy to misread as the opposite.Function.composeorder:f.compose(g).apply(x)appliesfto the result ofg(x)— the composed function applies right-to-left.f.andThen(g)applies left-to-right.- Boxing in stream map:
stream.map(Integer::sum)wheresumisBinaryOperator<Integer>causes boxing. UsemapToIntandsum()for primitive optimizations. Predicate.isEqualusesObjects.equalsnot==: Two differentStringinstances with the same content are considered equal byPredicate.isEqual— this may or may not be the intended behavior.Supplieris evaluated once per.get()call: Each call toget()re-evaluates the supplier body — it is not memoized by default.
Interview Questions
Function.andThen and Function.compose?andThen creates a pipeline that executes the calling function first, then passes its result to the provided function. compose executes the provided function first, then passes its result to the calling function — it is right-to-left composition. For example, f.andThen(g) means g(f(x)), while f.compose(g) means f(g(x)). Use andThen for sequential transformations where each step builds on the previous; use compose when you want to pre-process input before a main transformation."IntPredicate and IntFunction exist?Predicate<T> and Function<T,R> operate on object references, which means primitive values must be boxed into wrapper types (Integer, Double, etc.) on every call. Primitive specializations (IntPredicate, IntFunction<R>, IntSupplier, etc.) operate directly on primitive types without boxing, eliminating the memory allocation and garbage collection overhead for primitive values. They exist purely for performance in performance-sensitive code paths like streams and collections."Consumer.andThen and Predicate.and?Consumer.andThen chains two consumers to execute sequentially — the second consumer runs on the result of the first. If the first consumer throws, the second never runs. Predicate.and composes two predicates using logical AND — both are evaluated even if the first is false (short-circuit evaluation is not guaranteed for the combined predicate, though the JVM may optimize it). Predicate.or and Predicate.negate similarly compose boolean logic."Predicate.isEqual do and how does it differ from direct equality comparison?Predicate.isEqual(Object target) returns a predicate that tests using Objects.equals(o, target) — it uses value equality (equals()), not reference equality (==). This means two different String instances containing the same characters will be considered equal. Use direct == comparison only when you need identity comparison. isEqual is useful when you want to match a specific object value in a collection without overriding equals on the matched-against object."java.util.function interfaces do not declare checked exceptions. If your lambda body throws a checked exception, you have three options: catch and wrap in an unchecked exception (RuntimeException), use a custom functional interface that declares the exception, or use a wrapper utility that translates checked to unchecked. Libraries like Vavr provide alternative functional interfaces (CheckedFunction1, etc.) that preserve checked exception signatures."BiFunction and when should you use it versus Function?BiFunction<T, U, R> represents a function that takes two arguments and produces a result — (T, U) -> R. Use it when your transformation requires two input values. Use Function<T, R> for single-argument transformations. Examples: BiFunction<String, Integer, String> for String.repeat(count), Function<String, String> for String.toLowerCase(). BiFunction does not have andThen directly — use BiFunction.andThen() after Java 9."Supplier.get() and a cached supplier pattern?Supplier.get() is called fresh on every invocation — it is not memoized. Each call to get() executes the supplier body. To cache the result, use Supplier<Double> cached = Suppliers.memoize(() -> expensiveComputation()) from Guava or implement a lazy holder pattern. For lazy initialization of expensive objects, Double-checked locking with a volatile field or the initialization-on-demand holder idiom is thread-safe without synchronization on every access."Predicate.or() return when combining predicates and how does it short-circuit?predicateA.or(predicateB) returns a new Predicate that evaluates to true if either predicate returns true. The second predicate is not evaluated if the first returns true — this is short-circuit evaluation. This matters when the second predicate has side effects or is expensive. Predicate.and() similarly short-circuits by skipping the second predicate when the first returns false. Predicate.negate() has no short-circuit concern."ToIntFunction, ToLongFunction, and ToDoubleFunction?Function for primitive return types — they accept any reference type and return a primitive without boxing. ToIntFunction<String> applies String::length returning int without boxing overhead. Without these, you would use Function<String, Integer> which boxes the int result. Use them in stream operations like stream.mapToInt(ToIntFunction) to stay in primitive streams."Consumer.andThen() and chaining multiple Consumer calls?a.andThen(b) executes a first, then b on the result of a — the input to b is the same object that a received (since Consumer returns void). This is sequential chaining: both consumers see the same input. Using a.accept(x); b.accept(x); is equivalent but more verbose. andThen is the functional style for composing side effects. Note that if a throws, b never runs — andThen does not provide error recovery."UnaryOperator and how does it differ from Function<T, T>?UnaryOperator<T> extends Function<T, T> with apply(T) returning T. It is semantically clearer — when the input and output types are the same, UnaryOperator documents this intent. Function<T, T> could theoretically transform T to a different type U, though they happen to share the same type variable. Use UnaryOperator for in-place transformations (e.g., list.replaceAll(UnaryOperator)) and Function<T, T> for general single-type transformations."IntFunction<R> and Function<Integer, R>?IntFunction<R> takes a primitive int and returns R — no boxing of the input. Function<Integer, R> takes the Integer wrapper type — boxing occurs on every call. IntFunction<R> is more efficient in streams: stream.mapToObj(IntFunction<String>) avoids boxing on the input side. Use primitive specializations (IntFunction, IntUnaryOperator, etc.) in performance-sensitive code paths that process primitives."BinaryOperator.minBy() and BinaryOperator.maxBy() and how do they work?BinaryOperator.minBy(Comparator<T>) returns a BinaryOperator<T> that returns the lesser element according to the given comparator. maxBy() returns the greater. These are convenience methods for BinaryOperator when you need to compare two values to find min/max. They are often used with stream.reduce(BinaryOperator.minBy(Comparator...)) for aggregating by minimum or maximum value."ObjDoubleConsumer<T> and similar specialized consumers?ObjDoubleConsumer<T> represents an operation that accepts an object of type T and a primitive double — (T, double) -> void. The Obj* variants exist for all combinations where one parameter is an object and one is a primitive. They avoid boxing when a consumer needs to work with both object and primitive types. For example, ObjDoubleConsumer<String> for logging with a numeric value without boxing the double."Function.identity() return and when should you use it versus (x) -> x?Function.identity() returns a function that always returns its input argument — x -> x. It is equivalent to (x) -> x but is the standard, reusable form. Use it when you need a function that passes through values unchanged, such as in collectors: Collectors.mapping(Function.identity(), Collectors.toList()) to collect elements unchanged into a list. The lambda form (x) -> x is equally valid but Function.identity() is self-documenting."BiConsumer.andThen() and BiConsumer chaining with two separate calls?biConsumerA.andThen(biConsumerB) runs A then B sequentially on the same arguments. Calling A.accept(a, b); B.accept(a, b); separately is equivalent. Both consumers see the same (a, b) input — neither transforms the input for the other. andThen is the functional composition idiom for side-effect operations. If A throws, B is never called."BiFunction and Function in terms of composition?BiFunction does not have its own compose or andThen methods in the same way Function does. However, BiFunction.andThen(Function) (available in Java 9+) allows composing a BiFunction with a Function to transform the result — (T, U) -> R followed by R -> V produces a BiFunction<T, U, V>. This is the composition pattern for two-argument functions."IntPredicate and Predicate<Integer> in terms of performance?IntPredicate takes a primitive int and returns boolean — no boxing of the input. Predicate<Integer> accepts a boxed Integer, boxing the input on every call. In tight loops or stream pipelines over primitive int values, IntPredicate is significantly faster due to avoiding boxing. For streams of Integer objects, boxing is already occurring upstream, so IntPredicate cannot help."Supplier look like in the context of lazy initialization and how does it differ from a method reference?Supplier for lazy initialization produces a value on demand — Supplier<Connection> connSupplier = () -> createConnection(). When used with Optional.orElseGet(connSupplier), the connection is only created if needed. A method reference like MyClass::createConnection is a cleaner form of the same thing. The key distinction is that Supplier.get() has no parameters; method references to static or instance methods with zero parameters can be used as Supplier implementations."ToIntBiFunction, ToLongBiFunction, and ToDoubleBiFunction?BiFunction what ToIntFunction is to Function — they accept two reference-type arguments (T, U) and return a primitive (int, long, double) without boxing the return value. They exist for the same performance reasons as their single-argument counterparts. Use them when you need a two-argument transformation that produces a primitive result."Further Reading
- Oracle: java.util.function package documentation — official Javadoc for all functional interfaces
- Baeldung: Java Lambda Expressions and Functional Interfaces — lambda syntax and functional interface patterns
- Functional programming in Java 8 — Oracle’s overview of functional programming with
java.util.function - Stream API and lambda performance — benchmarking study on lambda overhead
- Vavr: Functional data structures — richer functional types complementing
java.util.function - Lambda Expressions — lambdas as the calling syntax for functional interfaces
- Method References — companion to lambda expressions
- Stream API — streams heavily use java.util.function interfaces
Conclusion
java.util.function is the standard toolkit for functional programming in Java 8+. Before this package, projects typically rolled their own single-method interfaces for callbacks, transformations, and predicates. The JDK’s built-in set handles the common cases: Predicate for boolean tests, Function for transformations, Supplier for lazy production, Consumer for side effects, and their binary variants. These types show up throughout the JDK and most Java libraries. Spring uses Function<T, R> for bean transformers, Jackson uses Predicate for property filters, and reactive libraries use Function for map operations. Once you know these types, you’ll start seeing them everywhere.
Function composition with andThen and compose is worth understanding. andThen chains operations in execution order (apply this, then apply the other), while compose applies the other function first (right-to-left). For transformation pipelines, chaining functions with andThen produces readable, maintainable code.
The primitive specializations (IntPredicate, IntFunction, IntSupplier, etc.) avoid boxing primitive values into wrapper objects. In tight loops or large stream pipelines, boxing overhead compounds quickly. If you’re writing stream.map(Integer::sum) on an IntStream, switch to mapToInt and sum() to stay in primitives.
These interfaces work closely with java.util.stream.Stream — every stream operation consumes one. They also work with java.util.Optional, where map takes a Function and flatMap takes a function returning Optional.
Category
Related Posts
Abstract Classes in Java
Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.
Arithmetic Operators in Java
Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.
Array Basics in Java
Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.