Use sharing to efficiently support a large number of fine-grained objects by externalising state.
Flyweight minimises memory usage by sharing as much data as possible among similar objects. It separates object state into intrinsic (shared, immutable, stored in the flyweight) and extrinsic (unique per context, passed in at runtime). One flyweight object serves many contexts.
The key insight: if many objects share the same internal state, there's no need to store that state in each object — store it once in a shared flyweight and pass the context-specific part in when needed.
TreeType flyweight used by all.A text editor stores each character as an object with font, size, colour, and the character value itself. A document with 100,000 characters creates 100,000 objects, most sharing the same font/size/colour — massive, redundant memory consumption.
Flyweight stores font, size, and colour once in a shared CharacterStyle object. Each character position only stores the character value and a reference to the shared style — dramatically reducing memory.
// Flyweight — stores INTRINSIC state (shared, immutable) public class TreeType { private final String name; // intrinsic private final String color; // intrinsic private final String texture; // intrinsic (large data!) public TreeType(String name, String color, String texture) { this.name=name; this.color=color; this.texture=texture; } // Extrinsic state (x, y) passed in at call time — NOT stored here public void draw(int x, int y) { System.out.printf("Drawing %s tree [%s] at (%d,%d)%n", name, color, x, y); } } // FlyweightFactory — cache of shared TreeType instances public class TreeFactory { private static final Map<String, TreeType> cache = new HashMap<>(); public static TreeType getTreeType(String name, String color, String tex) { String key = name + "_" + color; return cache.computeIfAbsent(key, k -> { System.out.println("Creating new TreeType: " + key); return new TreeType(name, color, tex); // expensive creation once }); } public static int getCacheSize() { return cache.size(); } } // Context — stores EXTRINSIC state (unique per tree instance) public class Tree { private final int x, y; // extrinsic — unique per tree private final TreeType type; // reference to shared flyweight public Tree(int x, int y, TreeType type) { this.x=x; this.y=y; this.type=type; } public void draw() { type.draw(x, y); // passes extrinsic state to flyweight } } // Forest — creates 10,000 trees using only 2 TreeType objects public class Forest { private final List<Tree> trees = new ArrayList<>(); public void plantTree(int x, int y, String name, String color, String tex) { TreeType type = TreeFactory.getTreeType(name, color, tex); trees.add(new Tree(x, y, type)); // Tree is tiny — just x,y + reference } public void draw() { trees.forEach(Tree::draw); } } // Client public class Main { public static void main(String[] args) { Forest forest = new Forest(); for (int i = 0; i < 5000; i++) forest.plantTree(i*2, i*3, "Oak", "Green", "oak_texture"); for (int i = 0; i < 5000; i++) forest.plantTree(i*4, i*5, "Pine", "DarkGreen", "pine_texture"); System.out.println("TreeType objects in cache: " + TreeFactory.getCacheSize()); // Output: TreeType objects in cache: 2 (not 10,000!) forest.draw(); // all 10,000 trees rendered } }
==) — sharing breaks thisInteger.valueOf() caches Integer objects for -128 to 127 — classic Flyweight in the JDKString.intern() — String pool interns identical strings as shared flyweightsBoolean.TRUE / Boolean.FALSE — only two shared instances ever createdConnection objects shared among many client requests== to compare tree objects gets wrong results since different Tree contexts share the same TreeType — subtle equality bugsgetFlyweight() without synchronisation can create duplicate flyweight objectsfinal. No setters. Once created, a flyweight never changes — this is the single most important ruleequals() and hashCode() on the flyweight based on intrinsic state — never rely on reference equality for flyweightsLinkedHashMap with LRU eviction or max size) to prevent unbounded growth if variety is highConcurrentHashMap.computeIfAbsent() for atomic get-or-create operations — the standard safe approach in Java// ❌ BREAKING — mutable intrinsic state in flyweight public class BrokenTreeType { public String color; // ❌ mutable! One context changes it, all see the change public void setColor(String c) { this.color = c; } // ❌ NEVER do this } // Thread A: treeType.setColor("Red") → all trees using this type turn red // ✅ FIX — all intrinsic fields final, no setters public class TreeType { private final String name; private final String color; // ✅ final — never changes after creation private final String texture; // ✅ final // No setters } // ❌ BREAKING — storing extrinsic state in flyweight public class BrokenTreeType { private int x, y; // ❌ extrinsic! Shared by all — overwrites per request public void draw() { /* uses this.x, this.y */ } // ❌ race condition } // ✅ FIX — extrinsic state passed as parameters, never stored public void draw(int x, int y) { // ✅ x,y are parameters, not fields System.out.printf("Drawing %s at (%d,%d)%n", name, x, y); } // ❌ BREAKING — non-thread-safe factory public static TreeType getTreeType(String key) { if (!cache.containsKey(key)) { // ❌ check-then-act race condition cache.put(key, new TreeType(key)); // two threads create duplicates } return cache.get(key); } // ✅ FIX — atomic computeIfAbsent on ConcurrentHashMap private static final ConcurrentHashMap<String, TreeType> cache = new ConcurrentHashMap<>(); public static TreeType getTreeType(String name, String color, String tex) { return cache.computeIfAbsent(name + "_" + color, k -> new TreeType(name, color, tex)); // ✅ atomic — thread-safe }
ConcurrentHashMap.computeIfAbsent() for thread-safe factory creation.