接口骨架实现中的抽象方法
我正在重读《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
}
此示例产生了两个问题:
- 为什么 getKey 和 getValue 在此显式声明为抽象方法? 它们是 Map.Entry 的一部分 接口,所以我看不出抽象类中有多余声明的原因。
为什么使用将这些原始方法(正如 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:
- 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.
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
关于 equals 的问题,如果抽象类实现了它们,则不会有任何改变,因为该问题是可重写的。 在这种情况下,我想说 equals 正在尝试仔细实现以预测实现。 由于协方差问题(超类会认为它等于子类,但子类不会认为它等于超类),通常不应实现 equals 在其自身与其子类之间返回 true(尽管有很多这样做) ,所以无论你做什么,这种类型的 equals 实现都是棘手的。
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.
他警告在构造函数中调用可重写的方法,而不是在其他方法中。
He warns about calling overridable methods in the constructor, not in other methods.
AbstractMapEntry#getKey
和getValue
是抽象的(即未实现)的原因之一是Map.Entry
是Map< 的内部接口/代码>。 使用嵌套类/接口是 Java 实现组合的方式。 组合的想法是,组合的部分不是一流的概念。 相反,组成部分只有包含在整体中才有意义。 在本例中,组合部分是
Map.Entry
,组合的根对象是Map
。 显然,表达的概念是一个Map
有许多Map.Entry
。因此,
AbstractMapEntry#getKey
和getValue
的语义本质上取决于我们正在讨论的Map
的实现。 您编写的普通旧 getter 实现对于 HashMap 来说效果很好。 它不适用于像 ConcurrentHashMap 这样需要线程安全的东西。ConcurrentHashMap
的getKey
和getValue
实现很可能会创建防御性副本。 (建议自己查看源代码)。不实现
getKey
和getValue
的另一个原因是,实现Map
的字符与那些本来不应该属于的字符(即 < code>Property)到完全不同的宇宙,从Map
的直观实现(例如Provider
、TabularDataSupport
)。总之,由于 API 设计的黄金法则,不实现
AbstractMapEntry#getKey
和getValue
:One reason that
AbstractMapEntry#getKey
andgetValue
are abstract (i.e. unimplemented) is thatMap.Entry
is an inner interface toMap
. 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 isMap.Entry
and the root object of the composite isMap
. Obviously the concept expressed is that aMap
has manyMap.Entry
s.Therefore the semantics of
AbstractMapEntry#getKey
andgetValue
will depend essentially on the implementation ofMap
that we're talking about. A plain old getter implementation as you've written will work just fine forHashMap
. It won't work for something likeConcurrentHashMap
which demands thread-safety. It's likely thatConcurrentHashMap
's implementation ofgetKey
andgetValue
make defensive copies. (Recommend checking the source code for yourself).Another reason not to implement
getKey
andgetValue
is that the characters that implementMap
are radically different ranging from ones that should have never belonged (i.e.Properties
) to completely different universes from an intuitive impls ofMap
(e.g.Provider
,TabularDataSupport
).In conclusion, not implementing
AbstractMapEntry#getKey
andgetValue
, because of this golden rule of API design:我没有看到任何原因
允许实现定义如何存储键和值。
I don't see any reason
Allows the implementation to define how the key and value are stored.