Используйте несколько типов Java HashMap

Я использую Hashmap в качестве кеша в памяти. Итак, в основном, это то, что у меня есть:

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);
    }

Это нормально. Но для каждого Объекта, который я "получаю" с Карты, я должен разыграть его, что очень некрасиво. Я хочу поместить в него ArrayList и много других разных вещей.

Кто-нибудь знает другое решение для безопасности типов?

(Конечно, я могу создать для каждого типа геттер и сеттер, но единственное ли это решение?

Итак, есть ли лучший способ создать кеш в памяти или у кого-нибудь есть идея, как обернуть хэш-карту, чтобы быть более безопасным?


person Christof Buechi    schedule 11.11.2014    source источник
comment
Сколько разных типов объектов вы пытаетесь поместить в этот кеш?   -  person DejaVuSansMono    schedule 11.11.2014
comment
Допустим, около 15 типов. Обычно это было бы нормально   -  person Christof Buechi    schedule 11.11.2014
comment
И, как упоминал ниже Томас, нет ли чего-то общего, когда дело доходит до этих типов?   -  person DejaVuSansMono    schedule 11.11.2014
comment
Нет, только родитель: объект   -  person Christof Buechi    schedule 12.11.2014


Ответы (5)


Одним из решений этой проблемы является создание универсального типа CacheHelper с CacheHelper<T>. Затем создайте оболочку для вашей карты:

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);
  }
}

Компилятор Java фактически использует этот подход внутри; см. напр. здесь. Вам не нужно обходить явные объекты Class, но вы должны знать, какой тип фактически связан с каждым ключом, как и должно быть в хорошо работающих приложениях.

person Louis Wasserman    schedule 11.11.2014

Вы также можете сохранить тип элементов, передать тип, который вы ожидаете, методу get() и проверить там. Насколько я знаю, нет встроенного способа сделать хранение разных типов безопасным без какого-либо общего суперкласса или интерфейса.

В основном вы бы сделали это:

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() );
}

Обратите внимание, что это все равно не уловит ошибок времени компиляции, поскольку вы в основном отключаете дженерики внутри своего кеша. Без общего суперкласса/интерфейса или универсального типа в самом кеше нет способа отловить эти ошибки во время компиляции - по крайней мере, простого.

Если вы просто не хотите выполнять приведение типов самостоятельно, вы можете скрыть их внутри get() и жить с возможным исключением приведения классов. В этом случае вы можете позволить компилятору вывести тип T из вызова (путем присваивания, явно заданного или типа параметра).

Однако передача ожидаемого класса легко предоставляет дополнительную информацию, которую вы можете использовать для создания более качественных сообщений об ошибках и т. д., которые вы не получили бы от ClassCastException.

person Thomas    schedule 11.11.2014

Вы можете передать объект Class в качестве параметра и использовать метод Class.cast:

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

Вы можете использовать функцию generics, если версия Java 7 (или выше)

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

И тогда вы можете вызвать его следующим образом:

Myclass.<String>get(key);

В приведенном выше примере я предполагал, что Myclass будет классом, содержащим метод get()

person afzalex    schedule 11.11.2014

get нуждается в Class<T>, так как из-за стирания типа приведение (T) бессмысленно.

Либо полностью включить класс; ID на класс:

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);
}

store не использует value.getClass(), чтобы разрешить получение по интерфейсу или базовому классу:

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

Или сделать только

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

(Я немного упростил исходный код для читателей.)

person Joop Eggen    schedule 11.11.2014