Java - 扩展HashMap - Object与泛型行为

15

我正在写一个基于 HashMap 的缓存,它的工作方式如下:

  1. 如果请求的 key 在缓存中,则返回其对应的 value
  2. 如果请求的 key 不在缓存中,则运行一个根据 key 生成 value 的方法,将键值对存储起来,并返回 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()功能是必需的),因此该方法存在错误风险。

一个解决方案是将“is a”切换为“has a”的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);
}

有没有人能提出其他(即使是hackish的)解决方案,这样我就可以将 Cache 保持为一个 Map 并在 get(Object key)put(K key, V val) 的类型安全方面保持不变?

我唯一想到的办法是创建另一个方法,比如 getValue(Key k),并将其委托给 get(Object key),但是我不能强制任何人使用新的方法而不是通常用的那个。


1
你可以实现 getAmyget,但是你不能改变 Map.get() 的行为,因为你无法强制使用接口而不是直接使用你的类的代码重新编译。 - Peter Lawrey
2个回答

15

不对。你在转向“拥有”关系方面找到了正确的解决方案。(坦白说,如果get方法计算一个不存在的新值是令人惊讶的,违反了Map合同,并且可能导致许多其他方法产生极其奇怪的行为。这就是Guava移动离开MapMaker的重要原因之一,它几乎提供了完全相同的行为--因为它只是太riddled问题了。)

话虽如此,例如Guava的Cache所做的是公开一个 Map<K, V> asMap() 视图,这是你可以做的事情。这给你了大部分Map的优点,而不会影响类型安全性。


看起来 MapMaker 确实做了我所做的事情 :).我部分地意识到这种奇怪的行为。方法 V getData(K key)abstract 的,因此只有在缓存实例化时才会被指定(实现)。我甚至尝试将类设置为 final abstract(即使我知道它不会起作用),或者只是 final(方法可以被匿名内部类重写 - 当然也不起作用),以确保没有人会尝试使用一些业务逻辑来子类化这个危险的代码片段。无论如何,非常感谢,“has-a”关系与 asMap() 一起提供了一个视图。 - Petr Janeček
顺便说一句,我简直不敢相信我错过了Guava的缓存。虽然我现在有自己的实现,但出于骄傲,我不会使用它,但如果我昨天知道的话... :) - Petr Janeček
5
考虑使用Guava的Cache吧。它被广泛应用于谷歌的生产环境中,已经经过了大量的测试和优化,并且还具备许多便利特性。 - Louis Wasserman
转换为“HAS-A”关系(对于其他Java程序通常)真的可以吗? - Keenan Gebze
@KeenanGebze,组合(composition)通常更受青睐。继承(inheritance)通常被错误地使用,就像这种情况一样,因此只有在确实需要并且确定它在架构上是合理的时候才应该使用。 - Petr Janeček
也许这是组合优于继承的另一个例子? - flow2k

0

明显地,has-a关系是正确的实现。产生值的业务逻辑应该从缓存类中移除出去。


这就是其中有趣的部分 - 没有逻辑!该方法是“抽象的”,因此应仅在缓存实例化时指定(实现、覆盖 - 无论您最喜欢哪个)。我甚至尝试将类设置为“final abstract”(即使我知道它不会起作用),或者只是“final”(使用匿名内部类重写该方法 - 当然也不起作用),以确保没有人会尝试使用一些业务逻辑来子类化这段危险的代码。 - Petr Janeček

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接