Java's UnaryOperator<T> is one of the functional interfaces in java.util.function. Its purpose is simple: accept a value of type T and return another value of the same type.

For example, a UnaryOperator<String> accepts a String and returns a String. A UnaryOperator<Integer> accepts an Integer and returns an Integer.

The UnaryOperator interface

UnaryOperator<T> extends Function<T,T>. It does not declare a new abstract apply() method of its own; it inherits apply(T) from Function. Because the input and result types are the same, UnaryOperator is a convenient specialization of Function.

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
    // Inherited from Function<T, T>:
    // T apply(T value);
}

For example, an operator could remove every nonnumeric character from a string. The input is a String and the result is also a String, which makes the operation a natural fit for UnaryOperator<String>.

Definition of the term unary.

Definition of the term unary.

Implementation of the UnaryOperator example

You can implement UnaryOperator with a normal class, although the interface itself was introduced with Java 8. In this example, the type argument String means both the input to apply() and its return value must be a String.

package com.mcnz.lambda;

import java.util.function.UnaryOperator;

public class UnaryOperatorTest {

    public static void main(String[] args) {
        UnaryOperator<String> extensionAdder =
            text -> text + ".txt";

        String result = extensionAdder.apply("example-function");

        System.out.println(result);
    }
}

Java can infer the parameter type, and parentheses are optional for a lambda with one inferred parameter. The operator can therefore be reduced to a single expression:

UnaryOperator<String> extensionAdder = text -> text + ".txt";

Where Java uses UnaryOperator

UnaryOperator appears naturally in APIs that repeatedly transform a value without changing its type. One example is Stream.iterate(), whose second argument calculates the next value from the current one:

static <T> Stream<T> iterate(
    T seed,
    UnaryOperator<T> next
)

For example, this operator generates the next integer by adding one to the current value:

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

Stream.iterate(1, increment)
      .limit(5)
      .forEach(System.out::println);

The output is 1 through 5.

Another common example is List.replaceAll(UnaryOperator<E>):

List<String> names =
    new ArrayList<>(List.of("duke", "james", "gosling"));

names.replaceAll(String::toUpperCase);

System.out.println(names);

A useful rule of thumb is simple: use Function<T,R> when the input and output types may differ, and use UnaryOperator<T> when they must be the same type.