接口的骨架实现中的抽象方法

时间:2008-10-25 22:43:12

标签: java interface abstract-class

我正在重读有效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
}

这个例子有两个问题:

  1. 为什么getKey和getValue在这里显式声明为抽象方法?它们是Map.Entry接口的一部分,所以我没有在抽象类中看到冗余声明的原因。
  2. 为什么使用留下这些原语方法的成语,正如布洛赫先生所说的那样,是抽象的?为什么不这样做:

    //骨骼实施 公共抽象类AbstractMapEntry             实现Map.Entry {         私人K钥匙;         私人V值;

        // Primitive operations
        public K getKey() {return key;}
        public V getValue() {return value;}
    
     // ... remainder omitted
    

    }

  3. 这样做的好处是每个子类不必定义自己的字段集,并且仍然可以通过其访问器访问键和值。如果子类确实需要为访问器定义自己的行为,它可以直接实现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项),因为它使超类容易受到子类所做的更改的影响。 也许这是一个意见问题,但我希望确定是否还有更多的故事,因为布洛赫在书中没有详细说明这一点。

4 个答案:

答案 0 :(得分:1)

  1. 我想说它有助于强调具体类要处理的内容,而不是仅仅将它留给编译器告诉你(或者你必须比较两者以查看缺少的内容)。一种自我记录的代码。但它肯定没有必要,就我所见,它更像是一种风格的东西。
  2. 返回这些值比使用简单的getter和设置有更重要的逻辑。我在标准JDK(1.5)中检查的每个类在至少一个方法上做了一些不简单的事情,所以我猜他认为这样的实现太天真了,它会鼓励子类使用它而不是通过思考问题本身。
  3. 关于equals的问题,如果抽象类实现它们,则不会改变任何内容,因为问题是覆盖能力。在这种情况下,我会说等于试图仔细实施以预期实现。通常情况下,由于协方差问题(超类会认为它等于子类,但子类不会认为它等于超类),因此通常不应该实现equals在它自己和它的子类之间返回true(尽管有很多)所以无论你做什么,这种类型的equals实现都很棘手。

答案 1 :(得分:1)

  Bloch警告不要打电话   来自的可覆盖方法(第17项)   为继承而设计的类   让超类容易受到攻击   子类所做的更改

他警告在构造函数中调用可覆盖的方法,而不是在其他方法中。

答案 2 :(得分:0)

AbstractMapEntry#getKeygetValue是抽象的(即未实现的)的一个原因是Map.EntryMap的内部接口。使用嵌套类/接口是Java实现组合的方式。构图中的想法是组成的部分不是一流的概念。相反,如果组成部分包含在整体中,则它才有意义。在这种情况下,组合部分为Map.Entry,复合的根对象为Map。显然,表达的概念是Map有许多Map.Entry s。

因此AbstractMapEntry#getKeygetValue的语义将主要取决于我们所讨论的Map的实现。您编写的普通旧getter实现对HashMap工作正常。它不适用于需要线程安全的ConcurrentHashMap之类的东西。 ConcurrentHashMapgetKey getValue的实施可能会产生防御性副本。 (建议您自己检查源代码)。

不实施getKeygetValue的另一个原因是,实现Map的字符完全不同,应该从不属于(Properties)的字符到完全不同的宇宙与Map的直观冲动(例如ProviderTabularDataSupport)。

总之,实施AbstractMapEntry#getKeygetValue,因为这个API设计的黄金法则:

  

如有疑问,请将其保留(请参阅here

答案 3 :(得分:0)

  1. 我认为没有任何理由

  2. 允许实现定义密钥和值的存储方式。