Explain Codes LogoExplain Codes Logo

Java 8 Lambdas, Function.identity() or t->t

java
function-identity
lambda-expressions
performance-optimization
Anton ShumikhinbyAnton Shumikhin·Nov 22, 2024
TLDR

Opt for Function.identity() when your requirement is a function that simply returns its input. Chosen for its expressive brevity in Java 8, it offers potential performance efficiencies by reusing the same instance in various situations. Here's how to use it with streams:

Set<String> set = list.stream().map(Function.identity()).collect(Collectors.toSet());

Certainly more compact and expressive than its lambda counterpart t -> t, these two are, however, functionally the same.

Deep dive into Function.identity()

Function.identity(), which is a part of the Java standard library, returns a function that outputs its input argument, providing a clean style to represent a common pattern in functional programming.

Wondering why this over (t -> t)? Let's dive deeper:

Performance and memory

Although Function.identity() and t -> t are carbon copies when you look from a functional standpoint, here's how they differ under the hood in the JVM:

  • Function.identity() is a singleton, reused wherever fetched, thus a memory savior.
  • Each t -> t lambda could lead to a brand new instance creation each time it's called.

Readability against familiarity

Function.identity() might appear as an enigma to the novices owing to its abstract name. Conversely, people may find the vanilla lambda expressions, for instance, t -> t, more instinctive as they depict the action: map element t on to itself.

Debugging and tracing

In cases necessitating debugging, tracing the t -> t origin could be straightforward since it's explicitly defined in your codebase; whereas, with Function.identity(), a little backtracking could be necessary to decipher where it's applied.

Lambda expressiveness in Stream API

Customizing with lambda expressions

At times, a few tweaks to your identity function can do the trick, and in such scenarios, a lambda happily extends that control:

someStream.map(s -> s.trim()).distinct()... // No haircut, just a trim

The above snippet showcases where a mild transformation calls for a lambda instead of Function.identity().

Function.identity() limitations

Certain operations like Stream.mapToInt anticipate a to-int function and wouldn't accommodate Function.identity() without much ado, resulting in a compilation error. In such specialized scenarios, you’d want to fall back on a lambda expression:

intSum = stringStream.mapToInt(str -> Integer.parseInt(str)).sum(); // No Instagram. Just parseIntagram

Here, Function.identity() won't be feasible since mapToInt requires an ToIntFunction.

Trade-offs and nuances

When to prefer t -> t

There are instances where the lambda variant gets the nod:

  • Writing generic code making provisions for future replacement with more complex operations.
  • Situations where the exact type of the functional interface isn't Function<T, T>; Function.identity() may not be applicable without casting.

Context-driven selection

Choosing between Function.identity() and t -> t often boils down to personal preference and the context of usage:

  • In simple scenarios where a function merely echoes its input and clarity is undeterred, Function.identity() is a sure shot.
  • Where clarity or customization is the priority, the lambda expression t -> t wins hands down.