11

How would I write a generic InternPool<T> in Java? Does it need a Internable interface?

String in Java has interning capabilities; I want to intern classes like BigDecimal and Account.

skaffman
  • 398,947
  • 96
  • 818
  • 769
user400860
  • 111
  • 3

6 Answers6

6

Something like this:

public class InternPool<T> {

    private WeakHashMap<T, WeakReference<T>> pool = 
        new WeakHashMap<T, WeakReference<T>>();

    public synchronized T intern(T object) {
        T res = null;
        // (The loop is needed to deal with race
        // conditions where the GC runs while we are
        // accessing the 'pool' map or the 'ref' object.)
        do {
            WeakReference<T> ref = pool.get(object);
            if (ref == null) {
                ref = new WeakReference<T>(object);
                pool.put(object, ref);
                res = object;
            } else {
                res = ref.get();
            }
        } while (res == null);
        return res;
    }
}

This depends on the pool element class implementing the equals and hashCode to provide "equality by value" and to obey to the API contracts for those methods. But BigDecimal certainly does.


UPDATE - for an explanation of why we need a WeakHashMap<T, WeakReference<T>> rather than a WeakHashMap<T, T>, see the javadocs. The short version is that the key weak-links in the latter won't be broken by the GC because the corresponding entry references are making the values strongly reachable.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • I assume you don't need to check if ref.get() is null. – Peter Lawrey Jul 24 '10 at 07:58
  • I thought not, but I've realized that there is a race condition with the GC. If the GC runs immediately after the call to `get`, the `ref` returned might be broken by the time we look at it. The whole lot needs to be in a loop. Updated. – Stephen C Jul 24 '10 at 09:03
  • Why do you need a WeakReference for the map value? Aren't you already using WeakhashMap so if there is no strong reference to the object the entry will be removed? – as3rdaccount Feb 26 '14 at 14:50
  • Thanks Stephen! +1 for the javadoc – as3rdaccount Feb 26 '14 at 15:21
6

For an example take a look at Interner from Guava. It does not require an Internable interface, it just relies on equals and hashCode.

Greg Kopff
  • 15,945
  • 12
  • 55
  • 78
finnw
  • 47,861
  • 24
  • 143
  • 221
3

I would separate the solution into two classes to have cleaner code and also this way getting rid of loop:

public class WeakPool<T> {
    private final WeakHashMap<T, WeakReference<T>> pool = new WeakHashMap<T, WeakReference<T>>();
    public T get(T object) {
        final T res;
        WeakReference<T> ref = pool.get(object);
        if (ref != null) {
            res = ref.get();
        } else {
            res = null;
        }
        return res;
    }
    public void put(T object) {
        pool.put(object, new WeakReference<T>(object));
    }
}

and the interning class using the weak pool is very simple:

public class InternPool<T> {

    private final WeakPool<T> pool = new WeakPool<T>();

    public synchronized T intern(T object) {
        T res = pool.get(object);
        if (res == null) {
            pool.put(object);
            res = object;
        }
        return res;
    }
}
Csaba Toth
  • 10,021
  • 5
  • 75
  • 121
Peter Verhas
  • 268
  • 1
  • 6
2

This more sounds like that you're looking for flyweight pattern.

Flyweight is a software design pattern. A flyweight is an object that minimizes memory use by sharing as much data as possible with other similar objects

Click the link, it contains a Java example.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • +1. I think these are both the same pattern (or maybe a Flyweight is just one application of an Interner) but I prefer "Flyweight" because it is easier to google – finnw Jul 24 '10 at 10:07
1

Just a quick caveat:

It has not been explicitly mentioned above, but it should be obvious that the objects being interned must be of an immutable type.

On a second note: You don't need to use another weak reference to the object as the value in the map, a reference to a static would suffice if you just rely on the map's keyset for the data. For example, declare:

WeakHashMap<T,Boolean>

And insert pairs as:

pool.put (object, Boolean.TRUE);

Which is a minor saving of a WeakReference instance (if you can't reuse the one used for the key).

...or create a WeakSet class, as @PeterVerhas has done with his WeakPool.

simon.watts
  • 976
  • 10
  • 14
  • 1
    so with your method how can be retrieved the intern representation of an object? iterating by all the keys and checking for equality??? – Sergio Aug 07 '15 at 17:42
  • if someone is looking for optimizing memory use, the performance are quite better than guava weak interners. https://docs.geotools.org/stable/javadocs/org/geotools/util/CanonicalSet.html. – elbo Apr 27 '22 at 10:08
-1

Shouldnt

"WeakReference ref = pool.get(object);"

instead be

WeakReference ref = pool.intern(object);

??

FrederikH
  • 139
  • 2
  • 7