Get map dynamically based on key’s class type and avoid “raw use of parameterized class ‘Map'”

I have a cache class in which I used 2 HashMaps to keep the cache.
I want to be able to choose the right map given key‘s class type so that:

  1. if key is Long, then get value from map longKeyCache
  2. if key is String, then get value from map stringKeyCache.
    (assume user will only pass in Long or String key)

For this purpose, I build function getMapToUse.
The problem is that I’ll have to declear its return type as Map without any type restrictions. As only so will the function compile correctly and will I be able to use the returned map to store cache in follow up codes (mapToUse.put(key, value)).

The code works but I got warning from IDE – Raw use of parameterized class 'Map'

Would there be a way to tackle this warning? Thanks in advance.

public class CacheManager {

    private final Map<Long,String> longKeyCache = new WeakHashMap<>();
    private final Map<String,Integer> stringKeyCache = new WeakHashMap<>();

    public <K, V> V getCache(K key, Function<K, V> valueLoader) {
        Map<K, V> mapToUse = getMapToUse(key);

        return Optional.ofNullable(mapToUse.get(key))
                // cache miss
                .orElseGet(() -> {
                    V value = valueLoader.apply(key);
                    mapToUse.put(key, value);
                    return value;
                });
    }

    // warning: Raw use of parameterized class 'Map'
    private <K> Map getMapToUse(K key) {
        if (key instanceof Long) {
            return longKeyCache;
        } else {
            return stringKeyCache;
        }
    }
}

Answer

If you want to retain this design (of having a CacheManager that “knows” of two maps that have keys and values of different types), then somewhere or the other you will have to cast the map into Map<K, V>. There is no escaping this because the compiler will not be able to guess which actual type is meant.

You will be aware that one of the purposes of generics is to allow implementing identical functionality that can use various types, but apply type-safety check at the same time.

The method getCache( K, Function<K, V> ) is returning V, which means that V could be either Integer or String. But which one?? The compiler must boil down to one at the place the method is being called.

So, here is how I would implement this: Because there is a small and known set of map key/value types involved, generics may not be the way. Instead I would go by simply using overloaded methods. Reason: The caller still has a consistent API that is compile-time type-checked, which is the main aim with generics.

public class CacheManager{
    private final Map<Long,String> longKeyCache = new WeakHashMap<>();
    private final Map<String,Integer> stringKeyCache = new WeakHashMap<>();
    
    public String getCache( Long key, Function<Long, String> valueLoader) {
        return Optional.ofNullable( longKeyCache.get(key) )
                // cache miss
                .orElseGet(() -> {
                    String value = valueLoader.apply(key);
                    longKeyCache.put(key, value);
                    return value;
                });
    }
    
    public Integer getCache( String key, Function<String, Integer> valueLoader) {
        return Optional.ofNullable( stringKeyCache.get(key) )
                // cache miss
                .orElseGet(() -> {
                    Integer value = valueLoader.apply(key);
                    stringKeyCache.put(key, value);
                    return value;
                });
    }
    
    /* Testing code from here on. */
    public static void main( String[] args ){
        CacheManager cm = new CacheManager();
        cm.addTestData();
        
        System.out.println( cm.getCache( 200L, k -> k.toString() ) );
        System.out.println( cm.getCache( "500S", k -> 800 ) );
        System.out.println( cm.getCache( "900", k -> 900 ) );
    }
    
    private void addTestData() {
        longKeyCache.put( 200L, "200S" );
        longKeyCache.put( 201L, "201S" );
        stringKeyCache.put( "500S", 500 );
        stringKeyCache.put( "501S", 501 );
    }
}