2

I'm using a Hashmap as my in-memory cache. So basically, this is what I have:

private static final Map<String, Object> lookup = new HashMap<String, Object>();

    public static Object get(CacheHelper key) {
        return lookup.get(key.getId());
    }

    public static void store(CacheHelper key, Object value) {
        lookup.put(key.getId(), value);
    }

That's fine. But for every Object I "get" from the Map, I have to cast, which is very ugly. I want to put ArrayList and many other different things into it.

Does anybody know an other solution to be typesafe ?

(For sure, I can create for every type a getter and setter, but is that the only solution ?

So, is there a better way to create an in-memory cache or does somebody have an idea how to wrap the hashmap to be more safe ?

4
  • 1
    How many different types of objects are you trying to stuff in this cache? Commented Nov 11, 2014 at 17:07
  • Let us assume about 15 types. That would typically be normal Commented Nov 11, 2014 at 17:14
  • And as Thomas alluded too down below, there is no commonality to call upon when it comes to these types? Commented Nov 11, 2014 at 17:16
  • No, just the parent: object Commented Nov 12, 2014 at 9:16

5 Answers 5

7

One solution to this problem is to make your CacheHelper type generic, with CacheHelper<T>. Then create a wrapper for your map:

class MyCache {
  private final Map<CacheHelper<?>, Object> backingMap = new HashMap<>();
  public <T> void put(CacheHelper<T> key, T value) {
    backingMap.put(key, value);
  }
  @SuppressWarnings("unchecked")
  // as long as all entries are put in via put, the cast is safe
  public <T> T get(CacheHelper<T> key) {
    return (T) backingMap.get(key);
  }
}

The Java compiler actually uses this approach internally; see e.g. here. You don't have to pass around explicit Class objects, but you do have to know what type is actually associated with each key, which is as it should be in well-behaved applications.

Sign up to request clarification or add additional context in comments.

Comments

3

You could store the type of the elements ass well, pass the type you expect to the get() method and check there. AFAIK there's no built-in way to make storing different types typesafe without some common superclass or interface.

Basically you'd do this:

private static final Map<String, Object> lookup = new HashMap<>();
private static final Map<String, Class<?>> types= new HashMap<>();   

public static <T> T get(CacheHelper key, Class<T> expectedType ) {
  Class<?> type = types.get(key.getId());
  if( type == null || expectedType == null || expectedType.isAssignableFrom( type ) ) {
    throw new IllegalArgumentException("wrong type");
  }   
  return (T)lookup.get(key.getId());
}

//if null values should be allowed, you'd need to change the signature to use generics 
//and pass the expected type as well, e.g. <T> void store(CacheHelper key, T value, Class<T> type) 
public static void store(CacheHelper key, Object value ) {
    lookup.put(key.getId(), value);
    types.put( key.getId(), value.getClass() );
}

Note that this still wouldn't catch any compile time errors since you're basically disabling generics inside your cache. Without a common superclass/interface or a generic type on cache itself there's no compile time way to catch those errors - at least no easy one.

If you just don't want to do the casts yourself you can hide them inside get() and live with the possible class cast exception. In that case you can let the compiler infer the type of T from the call (by assignment, explicitly set or type of the parameter).

However, passing the expected class easily provides additional information that you can use to create better error messages etc. which you'd not get from a ClassCastException.

Comments

1

You can pass a Class object as a parameter and use the Class.cast method :

public static <T> T get(CacheHelper key, Class<T> clazz){
    return clazz.cast(lookup.get(key.getId());
}

Comments

0

You can use generics feature if java version is 7 (or upper)

public static <T> T get(CacheHelper key){
    return (T)(lookup.get(key.getId());
}

And you can then call it as follows :

Myclass.<String>get(key);

In the above example I supposed the Myclass be a class containing the get() method

Comments

0

The get needs a Class<T>, as because of type erasure a cast (T) is a senseless no-op.

Either incorporate the Class all the way; IDs per class:

private final Map<Class<?>, Map<String, Object>> lookup = new HashMap<>();

public <T> T get(Class<T> klass, String id) {
    Map<String, Object> mapById = lookup.get(klass);
    if (mapById == null) {
        return null;
    }
    Object value = mapById.get(id);
    return klass.cast(value);
}

public <T> void store(Class<T> klass, String id, T value) {
    Map<String, Object> mapById = lookup.get(klass);
    if (mapById == null) {
        mapById = new HashMap<>();
        lookup.put(klass, mapById);
    }
    mapById.put(id, value);
}

The store not using value.getClass() to allow a get by interface or base class:

store(Number.class, "x", 3.14);
store(Number.class, "x", 3);
store(Number.class, "x", new BigDecimal("3.14"));

Or do only

public static <T> T get(Class<T> klass, String id) {
    Object value = lookup.get(id);
    return klass.cast(value);
}

(I simplified the original code a bit, for the readers.)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.