Skip to content

Flyweight

The Flyweight pattern uses sharing to efficiently support a large number of fine-grained objects.

Core Idea

The core idea of the Flyweight pattern is simple: if an object instance is immutable once created, there's no need to create the same instance repeatedly. Instead, a shared instance can be returned to the caller, saving memory and reducing the overhead of creating objects, thereby improving performance.

Flyweight in Java Standard Library

There are many examples of the Flyweight pattern in the Java standard library. For instance, wrapper types such as Byte and Integer are immutable, so creating instances with the same value repeatedly is unnecessary. When creating an Integer using the static factory method Integer.valueOf(), if the int value is between -128 and +127, a cached instance will be returned:

java
// Flyweight pattern example
public class Main {
    public static void main(String[] args) {
        Integer n1 = Integer.valueOf(100);
        Integer n2 = Integer.valueOf(100);
        System.out.println(n1 == n2); // true
    }
}

In the case of Byte, since there are only 256 possible values, all instances created with Byte.valueOf() are cached objects.

Using Factory Methods

The Flyweight pattern can be achieved by using factory methods to create objects. Inside the factory method, a cached instance is often returned instead of creating a new one, enabling the reuse of immutable instances.

Tip: Always use factory methods instead of the new operator to create instances to take advantage of the Flyweight pattern.

Real-world Applications

In practice, the Flyweight pattern is mainly used in caching. When a client repeatedly requests certain objects, there's no need to query the database or read files every time. Instead, the data can be returned directly from memory.

Let's consider an example using a Student class with a static factory method that returns cached objects:

java
public class Student {
    // Cache for holding instances:
    private static final Map<String, Student> cache = new HashMap<>();

    // Static factory method:
    public static Student create(int id, String name) {
        String key = id + "\n" + name;
        // Check the cache first:
        Student std = cache.get(key);
        if (std == null) {
            // Not found, create a new instance:
            System.out.println(String.format("Creating new Student(%s, %s)", id, name));
            std = new Student(id, name);
            // Add to the cache:
            cache.put(key, std);
        } else {
            // Found in the cache:
            System.out.println(String.format("Returning cached Student(%s, %s)", std.id, std.name));
        }
        return std;
    }

    private final int id;
    private final String name;

    private Student(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

In real-world applications, mature caching libraries like Guava's Cache are often used, as they provide useful features such as limiting the maximum cache size and expiring entries after a specified duration.

Exercise

Implement caching using the Flyweight pattern.

Summary

The Flyweight pattern's design philosophy is to reuse existing objects as much as possible, often applied as an optimization within factory methods.

Flyweight has loaded