Java - расширение HashMap - поведение объекта и дженериков

Я пишу простой кэш HashMap, который работает следующим образом:

  • Если запрошенный key в кеше, верните его value.
  • Если запрошенный key не, запустите метод, который создает value на основе key, сохраните оба, верните value.

Код:

import java.util.HashMap;

abstract class Cache<K, V> extends HashMap<K, V> {  
    @Override
    public V get(Object key) {
        if (containsKey(key)) {
            return super.get(key);
        } else {
            V val = getData(key);
            put((K)key, val);    // this is the line I'm discussing below
            return val;
        }
    }

    public abstract V getData(Object key);
}

Это довольно просто и хорошо работает. Тем не менее, я ненавижу решение Sun для get() взять Object в качестве своего аргумента, а не K. Я достаточно читал об этом, чтобы знать, что у него есть некоторые соображения позади этого (с которым я не согласен, но это другая история).

Моя проблема заключается в прокомментированной строке, потому что кажется, что актер должен быть снят. Из-за стирания стилей я не могу проверить, имеет ли тип key тип K (который необходим для правильной put() функциональности), поэтому метод, следовательно, подвержен ошибкам.

Одним из решений было бы перейти от отношения "есть" к "имеет" HashMap, которое намного лучше и чище, но тогда Cache не может реализовать Map, что было бы неплохо по нескольким причинам. Код:

import java.util.HashMap;
import java.util.Map;

abstract class Cache<K, V> {
    private final Map<K, V> map = new HashMap<K, V>();

    public V get(K key) {
        if (map.containsKey(key)) {
            return map.get(key);
        } else {
            V val = getData(key);
            map.put(key, val);
            return val;
        }
    }

    public abstract V getData(K key);
}

Может ли кто-нибудь придумать любое другое (даже хакерское) решение, чтобы я мог поддерживать Cache как Map и по-прежнему быть безопасным в терминах get(Object key) и put(K key, V val)?

Единственное, о чем я могу думать, это сделать другой метод с именем ie getValue(Key k), который делегировал бы get(Object key), но тогда я не могу заставить кого-либо использовать новый метод вместо обычного.

Ответы

Ответ 1

Неа. Вы нашли правильное решение при переключении на отношения "has-a". (Честно говоря, если метод get вычислить новое значение, если он еще не существует, является неожиданным, нарушает договор Map и может привести к чрезвычайно странному поведению для ряда других методов. Это была большая часть почему Guava отошел от MapMaker, который предлагал почти это точное поведение, потому что это было просто с проблемами.)

Тем не менее, что, например, Guava Cache делает ли он представление Map<K, V> asMap(), которое вы можете сделать. Это дает вам большинство преимуществ Map без ущерба для безопасности типов.

Ответ 2

Определенно отношение has-a является правильной реализацией. бизнес-логика того, как генерируется значение, должна быть удалена из класса кэша.