我正在重新阅读 Effective Java (2nd edition) item 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
}
这个例子有两个问题:
- 为什么这里将 getKey 和 getValue 显式声明为抽象方法?它们是Map.Entry接口的一部分,所以我看不出抽象类中冗余声明的原因。
为什么要使用将这些原始方法(正如 Bloch 先生所说的那样)保留为抽象的习语?为什么不这样做:
// 骨架实现 public abstract class AbstractMapEntry implements Map.Entry { private K key; 私人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 项),因为它会使超类容易受到子类所做更改的影响。也许这是一个见仁见智的问题,但我希望确定这个故事是否还有更多内容,因为布洛赫在书中并没有真正详细说明这一点。