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

发布于 2024-07-08 10:23:28 字数 1634 浏览 11 评论 0原文

我正在重读《Effective Java》(第二版)第 18 项,更喜欢接口而不是抽象类。 在该项目中,Josh Bloch 提供了 Map.Entry接口的骨架实现示例:

// 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 先生所说)保留为抽象的习惯用法? 为什么不这样做:

    // 骨架实现 公共抽象类 AbstractMapEntry 实现 Map.Entry { 私人K密钥; 私有V值;

     // 原始操作 
          公共 K getKey() {返回键;} 
          public V getValue() {返回值;} 
    
       // ... 省略余数 
      

    }

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

I was re-reading Effective Java (2nd edition) item 18, prefer interfaces to abstract classes. In that item Josh Bloch provides an example of a skeletal implementation of the Map.Entry<K,V> interface:

// 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
}

Two questions stem from this example:

  1. Why are getKey and getValue explicitly declared here as abstract methods? They are part of the Map.Entry interface, so I don't see a reason for the redundant declaration in the abstract class.
  2. Why use the idiom of leaving these primitives methods, as Mr. Bloch refers to them, as abstract? Why not just do this:

    // Skeletal Implementation
    public abstract class AbstractMapEntry
    implements Map.Entry {
    private K key;
    private V value;

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

    }

The benefits of this are that each subclass doesn't have to define its own set of fields, and can still access the key and value by their accessors. If a subclass truly needs to define its own behavior for the accessors, it can implement the Map.Entry interface directly. The other downside is that in the equals method provided by the skeletal implementation, the abstract accessors are called:

// 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 warns against calling overridable methods (item 17) from classes designed for inheritance as it leaves the superclass vulnerable to changes made by subclasses.
Maybe this is a matter of opinion, but I was hoping to determine whether there's more to the story, as Bloch doesn't really elaborate on this in the book.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

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

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

  1. I would say it helps emphasize what the concrete class is intended to deal with, instead of just leaving it up to the compiler to tell you (or you having to compare both to see what is missing). Kind of self-documenting code. But it certainly isn't necessary, it is more of a style thing, as far as I can see.
  2. There is more significant logic in returning these values than simple getter and setting. Every class I spot checked in the standard JDK(1.5) did something non-simple on at least one of the methods, so I would guess that he views such an implementation as too naive and it would encourage subclasses to use it instead of thinking through the problem on their own.

Regarding the issue with equals, nothing would change if the abstract class implemented them because the issue is overridable. In this case I would say that the equals is attempting to be carefully implemented to anticipate implementations. Normally equals in general should not be implemented to return true between itself and its subclass (although there are plenty that do) due to covariance issues (the superclass will think it equals the subclass, but the subclass won't think it equals the superclass), so this type of implementation of equals is tricky no matter what you do.

北音执念 2024-07-15 10:23:28

布洛赫警告不要致电
可重写的方法(第 17 项)来自
为继承而设计的类
让超类容易受到
子类所做的更改

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

Bloch warns against calling
overridable methods (item 17) from
classes designed for inheritance as it
leaves the superclass vulnerable to
changes made by subclasses

He warns about calling overridable methods in the constructor, not in other methods.

酸甜透明夹心 2024-07-15 10:23:28

AbstractMapEntry#getKeygetValue 是抽象的(即未实现)的原因之一是 Map.EntryMap< 的内部接口/代码>。 使用嵌套类/接口是 Java 实现组合的方式。 组合的想法是,组合的部分不是一流的概念。 相反,组成部分只有包含在整体中才有意义。 在本例中,组合部分是 Map.Entry,组合的根对象是 Map。 显然,表达的概念是一个Map有许多Map.Entry

因此,AbstractMapEntry#getKeygetValue 的语义本质上取决于我们正在讨论的 Map 的实现。 您编写的普通旧 getter 实现对于 HashMap 来说效果很好。 它不适用于像 ConcurrentHashMap 这样需要线程安全的东西。 ConcurrentHashMapgetKeygetValue 实现很可能会创建防御性副本。 (建议自己查看源代码)。

不实现 getKeygetValue 的另一个原因是,实现 Map 的字符与那些本来不应该属于的字符(即 < code>Property)到完全不同的宇宙,从Map的直观实现(例如ProviderTabularDataSupport)。

总之,由于 API 设计的黄金法则,实现 AbstractMapEntry#getKeygetValue

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

One reason that AbstractMapEntry#getKey and getValue are abstract (i.e. unimplemented) is that Map.Entry is an inner interface to Map. Using nested classes/interfaces is how Java implements composition. The idea in composition is that the composed part is not a first-class concept. Rather, the composed part only make sense if it is contained in the whole. In this case, the composed part is Map.Entry and the root object of the composite is Map. Obviously the concept expressed is that a Map has many Map.Entrys.

Therefore the semantics of AbstractMapEntry#getKey and getValue will depend essentially on the implementation of Map that we're talking about. A plain old getter implementation as you've written will work just fine for HashMap. It won't work for something like ConcurrentHashMap which demands thread-safety. It's likely that ConcurrentHashMap's implementation of getKey and getValue make defensive copies. (Recommend checking the source code for yourself).

Another reason not to implement getKey and getValue is that the characters that implement Map are radically different ranging from ones that should have never belonged (i.e. Properties) to completely different universes from an intuitive impls of Map (e.g. Provider, TabularDataSupport).

In conclusion, not implementing AbstractMapEntry#getKey and getValue, because of this golden rule of API design:

When in doubt, leave it out (see here)

筱武穆 2024-07-15 10:23:28
  1. 我没有看到任何原因

  2. 允许实现定义如何存储键和值。

  1. I don't see any reason

  2. Allows the implementation to define how the key and value are stored.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文