DEV Community

Cover image for Surfing with FP Java - Mastering UnaryOperator<T>
André Borba
André Borba

Posted on

Surfing with FP Java - Mastering UnaryOperator<T>

Introduction

In the last episode, we mastered Supplier, the source that generates values from nothing.

Now, let's turn to UnaryOperator, a specialization of Function built for one very specific job: transforming a value into another value of the exact same type.

If Predicate decides, Function transforms, Consumer acts, and Supplier provides, then UnaryOperator refines. It takes something, and gives back more of the same kind, changed.

What Is UnaryOperator?

Here's the definition:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}
Enter fullscreen mode Exit fullscreen mode

Input: an object of type T.
Output: another object, also of type T.
Purpose: express a transformation that never changes the type, only the value.

Why Use UnaryOperator?

You could always write this with a plain Function<T, T>:

Function<String, String> trim = String::trim;
Enter fullscreen mode Exit fullscreen mode

That works, but it doesn't say anything extra about the shape of the transformation, a reader still has to check both generic parameters to confirm they match. UnaryOperator states the contract directly:

UnaryOperator<String> trim = String::trim;
Enter fullscreen mode Exit fullscreen mode

This one-type guarantee matters most where the JDK itself expects it, like List.replaceAll.

Practical Examples

1. In-Place List Transformation

List<String> names = new ArrayList<>(List.of("filipe", "andré", "borba"));

UnaryOperator<String> capitalize = s -> s.substring(0, 1).toUpperCase() + s.substring(1);

names.replaceAll(capitalize);
// [Filipe, André, Borba]
Enter fullscreen mode Exit fullscreen mode

2. Simple Same-Type Math

UnaryOperator<Integer> increment = n -> n + 1;

System.out.println(increment.apply(41)); // 42
Enter fullscreen mode Exit fullscreen mode

3. Composition (Inherited from Function)

UnaryOperator<String> trim = String::trim;
UnaryOperator<String> upper = String::toUpperCase;

Function<String, String> pipeline = trim.andThen(upper);

System.out.println(pipeline.apply("  borba  ")); // "BORBA"
Enter fullscreen mode Exit fullscreen mode

4. Iterative Generation with Stream.iterate

UnaryOperator<Integer> doubleIt = n -> n * 2;

List<Integer> powersOfTwo = Stream.iterate(1, doubleIt)
    .limit(5)
    .toList();

System.out.println(powersOfTwo); // [1, 2, 4, 8, 16]
Enter fullscreen mode Exit fullscreen mode

Real-World Patterns

  • In-Place Normalization: Trimming, upper/lower-casing, or sanitizing collections via replaceAll.
  • State Transitions: Modeling a value moving to the next state of the same type, e.g. an Order with an updated status.
  • Iterative Algorithms: Feeding Stream.iterate or recursive-style computations that keep producing the same type.
  • Configuration Overrides: Applying a same-type patch or decorator to a config or builder object.

Best Practices

  • Prefer Immutability: Return a new instance instead of mutating the input, especially for domain objects.
  • Reserve It for Same-Type Transforms: If input and output types differ, use Function<T, R> instead, don't force the fit.
  • Name for the Operation: increment, trim, capitalize communicate intent better than op.

Common Pitfalls

  • Silent Mutation: Since T equals T, it's tempting to mutate the argument directly instead of returning a new value, breaking purity.
  • Forcing the Fit: Wrapping a Function<T, R> as UnaryOperator<T> by ignoring a type mismatch defeats its purpose.
  • Overusing in Long Compositions: A long chain of andThen calls can become as unreadable as the imperative code it replaced.

Functional Analogy

Think of UnaryOperator as a polishing machine:

  • Raw material T goes in.
  • The machine reshapes it.
  • What comes out is still T, just refined.

Conclusion

UnaryOperator is Function's disciplined sibling; it exists to make same-type transformations explicit and safe. Wherever a value needs to become a better version of itself, in place, in a pipeline, or across iterations, UnaryOperator is the right tool.

What's Next

In the next episode, we'll meet BinaryOperator, a step further: instead of transforming one value, it combines two values of the same type into one. Get ready to reduce.

Top comments (6)

Collapse
 
ygritte profile image
ygritte •

It might be also fun in the future for you to detach slightly from java.
You are actually touching much more general topics which can be interesting to even Pythonistas etc.
Maybe 80% general and 20% heavily specific would be cool to pull off.
I mean for example, what you have written is ok, but have you ever considered the following:
So this unary maps dots from Set A to Set A.
You started touching sublist etc. to suggest its generality.
How about generating/traversing a tree level by level?
Have you considered approaching the N-Queens problem this way?
I mean you clearly wrote that Streams have to do something with recursion (sometimes).
The word - imo - you have been circling around is corecursion.
It is indeed insanely close to recursion, but it is much more closer to what you were jumping around here, once again imo.

But the TL;DR is: You could write articles which of course have your language in the spotlight at the end, but you could invite more viewers by offering them knowledge that is language agnostic, perhaps.

Collapse
 
borba profile image
André Borba •

Yeah, I actually really liked your comment haha.

Just to clarify a few things, though:

I'm not really "discovering" these concepts through Java. I've known most of the Java/Streams-related stuff for years. I'm mostly just sharing things I already know, even if I'm deliberately keeping the discussion somewhat superficial. I've never really had the interest (or the need) to go much deeper into some of these topics.
That said, I do think the idea of approaching deeper/general CS concepts from a language-agnostic perspective is really interesting. I think it depends a lot on the purpose of the post/series, though. Sometimes the goal is simply to talk about something at a relatively superficial level and make it accessible. And that's fine too, perhaps that particular post simply isn't aimed at someone who already knows the underlying concepts deeply.

The part about lists, sublists, BFS, lazy sequences, tree traversal, etc. is actually SUPER interesting to me. That's very much the kind of direction I could see myself exploring in future posts/series. I already had some of those ideas in mind, actually.
And N-Queens is a great example too. That's also something I've had on my radar for a while. I really like the idea of approaching problems like that from the perspective of generation/traversal rather than immediately framing everything as "here's how to solve N-Queens in Java."

And the corecursion point was particularly interesting. I hadn't really been thinking about the whole thing under that name, so I'll definitely look into it more.

So yeah, ironically, I started reading your comment thinking "hmm, I'm not sure I agree with some of this" and ended up thinking "okay, wait, this actually gives me several ideas" lol.

I think the language-agnostic angle could be really cool for some of the future series, while I still want to keep some posts intentionally more language-specific and superficial. I don't necessarily think every post needs to go all the way down the abstraction rabbit hole.

Collapse
 
ygritte profile image
ygritte •

I'm not interested in lecturing you. I'm not interested in what I know and seeing you repeat it. I'm interested in cool articles, so sometimes I have to throw in a little bit of a jab, to unsettle an author to make him try something crazy once in a while.

Thread Thread
 
ygritte profile image
ygritte •

@borba but remember one thing besides all of this.
The name borba has been entered onto @highflyer910 Santa's Naughty List.
She is currently attending to other duties, but one day Thea Santa might show up under one of your christmas trees.

Thread Thread
 
borba profile image
André Borba •

Hahahaha okay, now I get it 😂😂😂😂

So the goal was not to teach me, but to poke me hard enough that I start doing something slightly insane once in a while.

Fair enough. I actually like that and really appreciate.

I guess this means I have to start writing more provocative and slightly crazier stuff now. Maybe push the ideas further, play with some concepts I normally wouldn't bother exploring, and see where they go.

Because apparently I have a much more important problem to solve first:

I need to get Borba off Santa's Naughty List.

I don't know what Thea Santa has planned for me, but I'm not willing to find out by waking up one Christmas morning and finding her under my tree.

So yeah... expect some increasingly unhinged articles from me. 😂

🎄 I'm going to try to earn my way back onto the Nice List. 🎄
🎅

Thread Thread
 
highflyer910 profile image
Thea •

I leave you two alone for a while, and apparently I have a Naughty List now 😂
Careful, @borba. Increasingly unhinged articles might get you off the Naughty List... or move you even higher on it. I'm not sure yet 🎄:D