我正在重读有效Java(第2版)第18项prefer interfaces to abstract classes。在该项目中,Josh Bloch提供了Map.Entry<K,V>
接口的骨架实现的示例:
// Skeletal Implementation
public abstract class AbstractMapEntry<K,V>
implements Map.Entry<K,V> {
// Primitive operations
public abstract K getKey();
public abstract V getValue();
// ... remainder omitted
}
这个例子有两个问题:
为什么使用留下这些原语方法的成语,正如布洛赫先生所说的那样,是抽象的?为什么不这样做:
//骨骼实施 公共抽象类AbstractMapEntry 实现Map.Entry { 私人K钥匙; 私人V值;
// Primitive operations
public K getKey() {return key;}
public V getValue() {return value;}
// ... remainder omitted
}
这样做的好处是每个子类不必定义自己的字段集,并且仍然可以通过其访问器访问键和值。如果子类确实需要为访问器定义自己的行为,它可以直接实现Map.Entry接口。另一个缺点是在骨架实现提供的equals方法中,调用抽象访问器:
// Implements the general contract of Map.Entry.equals
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (! (o instanceof Map.Entry))
return false;
Map.Entry<?,?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey()) &&
equals(getValue(), arg.getValue());
}
Bloch警告不要从为继承设计的类中调用可覆盖的方法(第17项),因为它使超类容易受到子类所做的更改的影响。 也许这是一个意见问题,但我希望确定是否还有更多的故事,因为布洛赫在书中没有详细说明这一点。
答案 0 :(得分:1)
关于equals的问题,如果抽象类实现它们,则不会改变任何内容,因为问题是覆盖能力。在这种情况下,我会说等于试图仔细实施以预期实现。通常情况下,由于协方差问题(超类会认为它等于子类,但子类不会认为它等于超类),因此通常不应该实现equals在它自己和它的子类之间返回true(尽管有很多)所以无论你做什么,这种类型的equals实现都很棘手。
答案 1 :(得分:1)
Bloch警告不要打电话 来自的可覆盖方法(第17项) 为继承而设计的类 让超类容易受到攻击 子类所做的更改
他警告在构造函数中调用可覆盖的方法,而不是在其他方法中。
答案 2 :(得分:0)
AbstractMapEntry#getKey
和getValue
是抽象的(即未实现的)的一个原因是Map.Entry
是Map
的内部接口。使用嵌套类/接口是Java实现组合的方式。构图中的想法是组成的部分不是一流的概念。相反,如果组成部分包含在整体中,则它才有意义。在这种情况下,组合部分为Map.Entry
,复合的根对象为Map
。显然,表达的概念是Map
有许多Map.Entry
s。
因此AbstractMapEntry#getKey
和getValue
的语义将主要取决于我们所讨论的Map
的实现。您编写的普通旧getter实现对HashMap
工作正常。它不适用于需要线程安全的ConcurrentHashMap
之类的东西。 ConcurrentHashMap
和getKey
getValue
的实施可能会产生防御性副本。 (建议您自己检查源代码)。
不实施getKey
和getValue
的另一个原因是,实现Map
的字符完全不同,应该从不属于(Properties
)的字符到完全不同的宇宙与Map
的直观冲动(例如Provider
,TabularDataSupport
)。
总之,不实施AbstractMapEntry#getKey
和getValue
,因为这个API设计的黄金法则:
如有疑问,请将其保留(请参阅here)
答案 3 :(得分:0)
我认为没有任何理由
允许实现定义密钥和值的存储方式。