Effective Java 类和接口 第17条:要么为继承而设计,并提供文档说明,要么就禁止继承

第16条提醒我们,对于不是为了继承而设计,并且没有文档说明的“外来”类进行子类化是多么危险。那么对于专门为了继承而设计并具有良好文档说明的类而言,这有意味着什么呢?

该类的文档必须精确的描述覆盖每个方法所带来的影响。(java.util.AbstractCollection)

 /**
     * Removes a single instance of the specified element from this collection, 
     * if it is present (optional operation).  
     * More formally, removes an element e such that (o==null:o.equals(e)), 
     * if this collection contains one or more such elements. 
     * Returns true if this collection contained the specified element (or
     * equivalently, if this collection changed as a result of the call).
     * 
     * This implementation inerates over the collection looking for the specified element.
     * If it finds the element,it removes the element from the collection using the iterator's remove method.
     * Note that this implementation throws an UnsupportedOperationException 
     * if the iterator removed by this collection's iterator method does not implement the remove method.
     */
    boolean remove(Object o);

该文档很明确的说明了,覆盖iterator 方法将会影响remove方法的行为。而且,它确切的描述了iterator方法返回的Iterator的行为将会影响remove方法的行为。

好的API应该描述一个给定的方法做了什么工作,而不是描述他是如何做到的。上面的API违反了这句话,这正式继承破坏了封装性所带来的不幸后果。

为了继承而进行的设计不仅仅涉及自用模式的文档设计。为了是程序员能够编写出更加有效的子类,而无需承受不必要的痛苦,类必须通过某种形式提供适当的钩子,以便能够进入到它的内部工作流程中,这种形式可以是精心选择的受保护的方法,也可以是受保护的域。(java.util.AbstractList):

     /**
     * Removes from this list all of the elements whose index is between
     * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive.
     * Shifts any succeeding elements to the left (reduces their index).
     * This call shortens the list by {@code (toIndex - fromIndex)} elements.
     * (If {@code toIndex==fromIndex}, this operation has no effect.)
     *
     * <p>This method is called by the {@code clear} operation on this list
     * and its subLists.  Overriding this method to take advantage of
     * the internals of the list implementation can <i>substantially</i>
     * improve the performance of the {@code clear} operation on this list
     * and its subLists.
     *
     * <p>This implementation gets a list iterator positioned before
     * {@code fromIndex}, and repeatedly calls {@code ListIterator.next}
     * followed by {@code ListIterator.remove} until the entire range has
     * been removed.  <b>Note: if {@code ListIterator.remove} requires linear
     * time, this implementation requires quadratic time.</b>
     *
     * @param fromIndex index of first element to be removed
     * @param toIndex index after last element to be removed
     */
    protected void removeRange(int fromIndex, int toIndex) ;
参数: 
fromIndex 要移除的第一个元素的索引。
toIndex 要移除的最后一个元素的索引。

这个方法对于List实现的最终用户没有意义。提供该方法的唯一目的在于,使子类更容易提供针对子列表的快速clear方法。如果没有removeRange方法,当在子列表上调用clear方法,子类将不得不用平方级的时间来完成他的工作。否则,就得重新编写真个subList机制,这可不是件容易的事情!

因此,当你为了继承而设计的类的时候,如何决定应该暴露那些受保护的方法或者域呢?遗憾的是,并没有神奇的法则可供你使用。唯一的方法就是测试。

对于为了继承而设计的类,唯一的测试方法就是编写子类。经验表明,3个子类通常就足可以测试一个可扩展的类。

为了允许继承,类还必须遵守其他的一些约束。构造器绝不能调用可被覆盖的方法,无论是直接调用还是间接调用。
例如:

    public class Super{
        //Broken - constructor invokes an overridable method
        public Super{
            overrideMe();
        }

        public void overrideMe(){
        }
    }

下面的子类覆盖了方法overrideMe,Super唯一的构造器就错误的调用了这个方法:

public final class Sub extends Super{
        private final Date date;//Blank final,set by constructor

        Sub(){
            date=new Date();
        }

        //Overriding method invoked by superclass constructor
        @Override
        public void overrideMe(){
            Systen.out.println(date);
        }

        public static void main(String[] args){
            Sub sub=new Sub();
            sub.overrideMe();
        }
    }

你可能会期待这个程序会打印出日期俩次,但是它第一次打印出的是null,因为overrideMe方法被Super构造器调用的时候,构造器Sub还没有机会初始化Date域。

在为了继承而设计的类的时候,Cloneable和Serializable接口出现了特殊的困难。如果类是为了继承而被设计的,无论实现这其中的那个接口通常都不是一个好主意,因为他们它一下实质性的负担转嫁到扩展这个类的程序员的身上。

如果你决定在一个为了继承而设计的类中实现Cloneable或者Serializable接口,就应该意识到,因为clone和readObject方法在行为上非常类似于构造器,所以类似的限制规则也是使用的:无论是clone还是readObject,都不可以调用可覆盖的方法,不管是以直接还是间接的方式。

如果你决定在一个为了继承而设计的类中实现Serializable,并且该类有一个readResolve或者writeReplace方法,就必须使readResolve或者writeReplace成为受保护的方法,而不是私有的方法。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值