2

我正在重新阅读 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
}

这个例子有两个问题:

  1. 为什么这里将 getKey 和 getValue 显式声明为抽象方法?它们是Map.Entry接口的一部分,所以我看不出抽象类中冗余声明的原因。
  2. 为什么要使用将这些原始方法(正如 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 项),因为它会使超类容易受到子类所做更改的影响。也许这是一个见仁见智的问题,但我希望确定这个故事是否还有更多内容,因为布洛赫在书中并没有真正详细说明这一点。

4

4 回答 4

1
  1. 我想说它有助于强调具体类要处理的内容,而不是让编译器告诉你(或者你必须比较两者以查看缺少的内容)。一种自我记录的代码。但这当然不是必需的,据我所知,它更像是一种风格。
  2. 返回这些值比简单的 getter 和设置更重要的逻辑。我在标准 JDK(1.5) 中发现的每个类都至少在其中一种方法上做了一些不简单的事情,所以我猜他认为这样的实现太天真了,它会鼓励子类使用它而不是仔细考虑自己的问题。

关于 equals 的问题,如果抽象类实现它们,什么都不会改变,因为这个问题是 overridable。在这种情况下,我会说 equals 正在尝试仔细实施以预期实施。由于协方差问题(超类会认为它等于子类,但子类不会认为它等于超类),通常不应实现 equals 以在其自身与其子类之间返回 true(尽管有很多这样做) ,所以无论你做什么,这种类型的 equals 实现都很棘手。

于 2009-06-04T00:01:08.157 回答
1

Bloch 警告不要从为继承而设计的类中调用可覆盖的方法(第 17 项),因为它会使超类容易受到子类所做更改的影响

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

于 2010-12-13T07:18:31.690 回答
0
  1. 我看不出任何理由

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

于 2009-06-03T23:38:39.420 回答
0

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

AbstractMapEntry#getKey因此, and的语义getValue本质上将取决于Map我们正在讨论的实现。您编写的一个普通的旧 getter 实现将适用于HashMap. 它不适用于ConcurrentHashMap需要线程安全的东西。这很可能是ConcurrentHashMap实施getKeygetValue制作防御性副本。(建议自己检查源代码)。

另一个不实现的原因getKey是实现getValue的字符Map完全不同,从不应该属于的字符(即Properties)到完全不同的宇宙Map(例如ProviderTabularDataSupport)。

总之,没有实现AbstractMapEntry#getKeyand getValue,因为 API 设计的黄金法则:

如有疑问,请忽略(请参阅此处

于 2008-10-25T23:26:21.283 回答