Static Factory Methods

OOP · lesson 23 of 43 · 3 min read

Why a named static method often beats new: validation, caching and hidden subtypes.

Open this lesson in the learning hub

Key points

  • A static factory is just a static method that returns an instance. Make the constructor private so it is the only door.
  • It has a name, so Currency.of and Currency.parse can differ where two constructors could not.
  • It does not have to build anything new. Returning a cached instance is invisible to the caller and often free.
  • It can return a subtype, so the caller depends on the interface and you keep the implementation to yourself.
  • The JDK is full of them: List.of, Optional.of, Integer.valueOf, Path.of.
  • The cost: no subclassing without a public or protected constructor, and factories are harder to spot in the docs.

Example

import java.util.HashMap;
import java.util.Map;

public class Main {
    static final class Currency {
        private static final Map<String, Currency> CACHE = new HashMap<>();
        private final String code;

        private Currency(String code) { this.code = code; }   // constructor is private

        static Currency of(String code) {                     // named, validating, cached
            if (code == null || code.length() != 3) {
                throw new IllegalArgumentException("need a 3 letter code");
            }
            return CACHE.computeIfAbsent(code.toUpperCase(), Currency::new);
        }

        static int cached() { return CACHE.size(); }

        @Override public String toString() { return "Currency[" + code + "]"; }
    }

    public static void main(String[] args) {
        Currency a = Currency.of("usd");
        Currency b = Currency.of("USD");

        System.out.println(a + " and " + b);
        System.out.println("same instance? " + (a == b));    // cached, so true
        Currency.of("eur");
        System.out.println("objects built  = " + Currency.cached());

        try {
            Currency.of("dollars");
        } catch (IllegalArgumentException e) {
            System.out.println("rejected: " + e.getMessage());
        }
    }
}

Give creation a name and a private constructor, and you can change how it works later.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.