Jim
Jim

Reputation: 4405

Map with a multiple types of generic items for value

I have 3 different Set act as a cache and each serves for different reason. Additionally the keys are of different types e.g. Integer, String etc
I was thinking to create a wrapper class around this, but then I thought of just having them all as part of 1 hashmap and based on the key I can pick the proper Set
But I am not sure what is the proper way to do that.
I'd like to avoid something like:

private final Map<Integer, Set<Object>> cache = new HashMap<>();

public boolean exists(Integer type, Object key) {
    return cache.get(type).contains(key);
}

public void addKey(Integer type, Object key) {
    if(type == CACHE_1) {
        Set<Object> set = cache.get(type);
        if(set == null) {
            set = new HashSet<>();
            cache.put(type, set);
        }
        set.add(key);
    }
}  

Is there a way to make it more type specific?

Update
These can be called as:

addKey(CACHE_1, "foo");  
addKey(CACHE_2, 123);  

or

if(exists(CACHE_1, "foo")  
if(exists(CACHE_2, 123)  

Upvotes: 0

Views: 395

Answers (1)

Eugene
Eugene

Reputation: 120848

I am not sure you will like this, but you could use a "heterogeneous container". For example, define the Key of the Map:

  static class Key<T> {
    private final Integer one;
    private final String two;
    private final Long three;
    private final Class<T> cls;

    private Key(Integer one, String two, Long three, Class<T> cls) {
      this.one = one;
      this.two = two;
      this.three = three;
      this.cls = cls;
    }

    public Class<T> getCls() {
      return cls;
    }

    public static Key<Integer> ofInteger(Integer one){
      return new Key<>(one, null, null, Integer.class);
    }

    public static Key<String> ofString(String two){
      return new Key<>(null, two, null, String.class);
    }

    public static Key<Long> ofLong(Long three){
      return new Key<>(null, null, three, Long.class);
    }

    @Override
    public int hashCode() {
      return Objects.hash(one, two, three);
    }

    // simplified for example purpose
    public boolean equals(Object obj) {
      Key<?> other = (Key<?>)obj;
      return Objects.equals(this.one, other.one) &&
        Objects.equals(this.two, other.two) &&
        Objects.equals(this.three, other.three);
    }

  }

Then define the container:

  static class Holder {

    private static final Map<Key<?>, Set<Object>> CACHE = new HashMap<>();

    public static <T> boolean exists(Key<?> type, T key) {
      return CACHE.get(type).contains(key);
    }

    public static <T> void addKey(Key<T> type, T key) {
      CACHE.computeIfAbsent(type, x -> new HashSet<>()).add(key);
    }

    public static <T> Set<T> byKey(Key<T> key) {
      Set<Object> set = CACHE.get(key);
      return Optional.ofNullable(set).orElse(Collections.emptySet()).stream().map(key.getCls()::cast).collect(Collectors.toSet());
    }

  }

And some usage:

  public static void main(String[] args) {
    Holder.addKey(Key.ofInteger(1), 11);
    Holder.addKey(Key.ofInteger(1), 22);
    Holder.addKey(Key.ofInteger(1), 33);
    Set<Integer> setI = Holder.byKey(Key.ofInteger(1));

    Holder.addKey(Key.ofString("1"), "11");
    Holder.addKey(Key.ofString("2"), "22");
    Holder.addKey(Key.ofString("3"), "33");
    Set<String> setS = Holder.byKey(Key.ofString("1"));

    System.out.println(setI);
    System.out.println(setS);
  }
 

Upvotes: 1

Related Questions