I have a factory that creates objects of class MyClass, returning already generated ones when they exist. As I have the creation method (getOrCreateMyClass) taking multiple parameters, which is the best way to use a Map to store and retrieve the objects?
My current solution is the following, but it doesn’t sound too clear to me.
I use the hashCode method (slightly modified) of class MyClass to build an int based on the parameters of class MyClass, and I use it as the key of the Map.
import java.util.HashMap;
import java.util.Map;
public class MyClassFactory {
static Map<Integer, MyClass> cache = new HashMap<Integer, MyClass>();
private static class MyClass {
private String s;
private int i;
public MyClass(String s, int i) {
}
public static int getHashCode(String s, int i) {
final int prime = 31;
int result = 1;
result = prime * result + i;
result = prime * result + ((s == null) ? 0 : s.hashCode());
return result;
}
@Override
public int hashCode() {
return getHashCode(this.s, this.i);
}
}
public static MyClass getOrCreateMyClass(String s, int i) {
int hashCode = MyClass.getHashCode(s, i);
MyClass a = cache.get(hashCode);
if (a == null) {
a = new MyClass(s, i);
cache.put(hashCode , a);
}
return a;
}
}
You really shouldn’t be using the hashcode as the key in your map. A class’s hashcode is not intended to necessarily guarantee that it will not be the same for any two non-equal instances of that class. Indeed, your hashcode method could definitely produce the same hashcode for two non-equal instances. You do need to implement
equalsonMyClassto check that two instances ofMyClassare equal based on the equality of theStringandintthey contain. I’d also recommend making thesandifieldsfinalto provide a stronger guarantee of the immutability of eachMyClassinstance if you’re going to be using it this way.Beyond that, I think what you actually want here is an interner…. that is, something to guarantee that you’ll only ever store at most 1 instance of a given
MyClassin memory at a time. The correct solution to this is aMap<MyClass, MyClass>… more specifically aConcurrentMap<MyClass, MyClass>if there’s any chance ofgetOrCreateMyClassbeing called from multiple threads. Now, you do need to create a new instance ofMyClassin order to check the cache when using this approach, but that’s inevitable really… and it’s not a big deal becauseMyClassis easy to create.Guava has something that does all the work for you here: its Interner interface and corresponding Interners factory/utility class. Here’s how you might use it to implement
getOrCreateMyClass:Note that using a strong interner will, like your example code, keep each
MyClassit holds in memory as long as the interner is in memory, regardless of whether anything else in the program has a reference to a given instance. If you usenewWeakInternerinstead, when there isn’t anything elsewhere in your program using a givenMyClassinstance, that instance will be eligible for garbage collection, helping you not waste memory with instances you don’t need around.If you choose to do this yourself, you’ll want to use a
ConcurrentMapcache and useputIfAbsent. You can take a look at the implementation of Guava’s strong interner for reference I imagine… the weak reference approach is much more complicated though.