Back to Functional Interfaces
Q11Question 11 of 13

Write a UnaryOperator<String> that trims and lowercases a name

Key Concepts
  • UnaryOperator<T> is a specialisation of Function<T, T> where input and output are the same type
  • Ideal for type-preserving transformations like normalising strings
  • List.replaceAll(UnaryOperator) rewrites each element in place using the operator
  • Inherits andThen and compose from Function for chaining transformation steps
  • Prefer strip() (Java 11) over trim() for Unicode-aware whitespace handling
  • Keep normalisation in a single named operator for consistent canonicalisation across code paths

Definition

UnaryOperator<String> is a Function<String, String>: same type in, same type out, perfect for normalising a name.
  • UnaryOperator extends Function with identical input and output types.
  • Ideal for type-preserving transformations: normalise, trim, lowercase, encrypt.
  • List.replaceAll(UnaryOperator) rewrites each element in place.
  • Inherits andThen and compose from Function for chaining.
  • Prefer strip() (Java 11, Unicode-aware) over trim() for modern code.

Priya at the National Bank of India tidies up messy names: strips the extra spaces and makes everything lowercase so the records are consistent. Same thing in, cleaned-up same thing out.

Understanding the Concepts

How do you write a UnaryOperator for name normalisation?

UnaryOperator extends Function with both type parameters equal.

JAVA
package com.bank.hr;

import java.util.*;
import java.util.function.UnaryOperator;

public class NormaliseName {
    public static void main(String[] args) {
        UnaryOperator<String> normalise = s -> s.strip().toLowerCase();

        List<String> names = new ArrayList<>(List.of(
            "  Rajesh ", "PRIYA", " amit  "));

        names.replaceAll(normalise);
        System.out.println(names);
    }
}
TEXT
[rajesh, priya, amit]

Why use UnaryOperator instead of Function?

UnaryOperator signals intent: the operation preserves the type. APIs like List.replaceAll specifically require UnaryOperator, not Function.

DIAGRAM
| Type | Signature | Use case |
|---|---|---|
| UnaryOperator<T> | T -> T | Type-preserving: normalise, encrypt |
| Function<T, R> | T -> R | Type-changing: Employee -> String |

How do you chain normalisation steps?

Because UnaryOperator is a Function, it supports andThen and compose.

JAVA
UnaryOperator<String> stripOp = String::strip;
UnaryOperator<String> lowerOp = String::toLowerCase;

// Chain: strip then lowercase
UnaryOperator<String> normalise =
    ((UnaryOperator<String>) String::strip)
        .andThen(String::toLowerCase)::apply;

// Or simply:
UnaryOperator<String> simple = s -> s.strip().toLowerCase();

Why prefer strip() over trim()?

DIAGRAM
| Method | Whitespace handled | Introduced |
|---|---|---|
| trim() | ASCII whitespace only (chars <= 0x20) | Java 1.0 |
| strip() | Unicode whitespace (Character.isWhitespace) | Java 11 |
JAVA
String s = "\u2003Rajesh\u2003";  // em space (Unicode)
System.out.println(s.trim());   // "\u2003Rajesh\u2003" (not trimmed!)
System.out.println(s.strip());  // "Rajesh" (trimmed correctly)

What are common mistakes?

Mistake: using different normalisation in different code paths.

JAVA
// Path 1: lowercase only
String key1 = name.toLowerCase();

// Path 2: trim + lowercase
String key2 = name.strip().toLowerCase();

// These produce different keys for "  Rajesh " → phantom duplicates!
// CORRECT: use the same UnaryOperator everywhere
static final UnaryOperator<String> NORMALISE = s -> s.strip().toLowerCase();

What should a senior engineer know?

  • Keep normalisation in a single, well-named operator so every code path uses the same canonicalisation.
  • Inconsistent normalisation causes phantom duplicates: two entries for the same person that look different.
  • UnaryOperator.identity() returns the input unchanged, useful as a default.
  • For streams, .map(normalise) works because UnaryOperator is a Function.

What are common interview follow-up questions?

  • "What is BinaryOperator?" A specialisation of BiFunction where all types are the same.
  • "Can UnaryOperator be used with mapToInt?" No; mapToInt requires ToIntFunction. Use IntUnaryOperator for primitive ints.
  • "How do you normalise in a stream?" .map(normalise) applies the operator to each element.

Visual Overview

DIAGRAM
  Input: "  Rajesh "
       │
       │  strip()
       ▼
  "Rajesh"
       │
       │  toLowerCase()
       ▼
  "rajesh"

  List.replaceAll(normalise):
  ["  Rajesh ", "PRIYA", " amit  "]
       │
       ▼
  ["rajesh", "priya", "amit"]

Key Takeaway

UnaryOperator is a Function that signals the operation preserves the type. For name normalisation, define it as s -> s.strip().toLowerCase() and use it in List.replaceAll to rewrite names in place or in .map() to transform a stream.

Keep the normalisation in a single named operator and reuse it everywhere names are compared or stored. At the National Bank of India, inconsistent normalisation between code paths caused phantom duplicate records; a shared UnaryOperator eliminated the problem.

Prefer strip() over trim() for Unicode-aware whitespace handling in modern Java. UnaryOperator inherits andThen and compose from Function for chaining.

Remember one sentence: UnaryOperator normalises strings with the same type in and out, and a single shared instance prevents inconsistent canonicalisation.

---

Share
Use ← → arrow keys to navigate