Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
295: The Flyweight Pattern
A few years ago, I was helping a buddy who was building a 2D strategy game. He had a map with thousands of individual forest tiles. Each tile was its own object, and each object held a reference to a high-res texture, a wind-animation script, and the X/Y coordinates of where it sat on the map. It worked fine for a small village, but as soon as he tried to render a full kingdom, the JVM started choking. He was staring at a massive OutOfMemoryError and his first instinct was to just bump the heap size to 8GB. I told him that was like trying to fix a leaky faucet by buying a bigger sink; we needed to change how he was storing his data.
The problem was that he was duplicating the "heavy" data—the texture and the animation—thousands of times, even though every "Pine Tree" tile used the exact same assets. This is exactly where the Flyweight pattern comes in. The core idea is to stop creating redundant objects and instead share a single instance of the heavy data across many contexts.
Splitting Intrinsic and Extrinsic State
To make this work, you have to divide your object's data into two categories: intrinsic and extrinsic. This is the part that usually trips people up, but it's simpler than it sounds.
Intrinsic state is the data that is constant and independent of the object's context. In our forest example, the texture and the tree species are intrinsic. Every "Oak Tree" looks like an "Oak Tree" regardless of where it is on the map. Extrinsic state is the data that varies. The X and Y coordinates are extrinsic; they change depending on where the tree is placed.
By moving the intrinsic state into a separate "Flyweight" object, you can have 10,000 trees on your map but only two or three actual Flyweight objects in memory (one for Oak, one for Pine, one for Birch).
Managing the Shared Pool with a Factory
You can't just let the client code instantiate these shared objects whenever they feel like it, or you'll end up right back where we started with memory bloat. You need a Factory to act as the gatekeeper. The factory checks if a Flyweight for a specific type already exists in a cache (usually a HashMap). If it does, it returns the existing one; if not, it creates it, stores it, and then returns it.
public class TreeType {
private final String name;
private final String color;
private final String textureData; // Imagine this is a huge byte array
public TreeType(String name, String color, String textureData) {
this.name = name;
this.color = color;
this.textureData = textureData;
}
public void draw(int x, int y) {
System.out.println("Drawing " + name + " at " + x + "," + y + " using " + textureData);
}
}
public class TreeFactory {
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
if (!treeTypes.containsKey(name)) {
System.out.println("Creating new TreeType: " + name);
treeTypes.put(name, new TreeType(name, color, texture));
}
return treeTypes.get(name);
}
}
Wait, you might be wondering: "Where is the Tree object then?" That's the trick. The "Tree" object becomes a very thin wrapper that only holds the extrinsic state and a reference to the Flyweight.
Injecting Context into the Flyweight
Because the Flyweight object doesn't know its own coordinates, you have to pass that information back into it when it's time to actually do something, like rendering. I call this "injecting the context." Instead of the Flyweight saying "I am at X,Y," the thin wrapper says "Hey Flyweight, render yourself at my X,Y."
public class Tree {
private int x, y;
private TreeType type; // This is the shared Flyweight reference
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void display() {
type.draw(x, y);
}
}
If you've ever used Integer.valueOf(int) in Java, you've already interacted with a Flyweight. Java caches integers from -128 to 127 because those are used so frequently. Instead of creating a new object every time you need the number 10, Java just gives you a reference to the one it already has cached. It's a small optimization, but when multiplied by millions of operations, it's a lifesaver.
📋 Practical Task
Building a Memory-Efficient Particle System
You are building a visual effects system for a game that needs to render 100,000 "Spark" particles on screen. Each particle has a color, a shape (e.g., "Circle", "Square", "Star"), and a size. These three properties are the same for all particles of a certain effect type. However, each particle has its own currentX and currentY positions.
Your Task:
- Create a
ParticleTypeclass (the Flyweight) that stores thecolor,shape, andsize. - Implement a
ParticleFactorythat ensures only one instance of aParticleTypeexists for any unique combination of color, shape, and size. - Create a
Particleclass (the context) that stores thecurrentXandcurrentYand holds a reference to itsParticleType. - In your
mainmethod, simulate the creation of 100,000 particles, but only use 3 distinct types (e.g., "Red Circle Small", "Blue Square Large", "Yellow Star Medium"). - Print the total number of
ParticleTypeobjects created to prove that you've only instantiated 3, despite having 100,000Particleobjects.
There are no comments for now.