如果一个类不是为了继承而设计的超类,那么如果对其继承,必须要有这个子类的说明文档,对其所有覆盖的每个方法进行精确 描述,文档必须记录该方法调用了哪些可覆盖方法,是什么顺序调用的,每个调的结果又是如何影响后续处理过程的。更广义的说,类的说明文档必须说明,哪些情况下会调用可覆盖方法。例如后台线程或者静态初始化器可能会调用这样的方法。
    调用可重写方法的方法在文档注释结束时包含对这些调用的描述。 这些描述在规范中特定部分,标记为“Implementation Requirements,”,由Javadoc标签@implSpec生成。 本节介绍该方法的内部工作原理。 下面是从java.util.AbstractCollection类的规范中拷贝的例子:

    1. public boolean remove(Object o)
    2. Removes a single instance of the specified element from this collection,
    3. if it is present (optional operation). More formally, removes an element e such that Objects.equals(o, 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).
    4. Implementation Requirements: This implementation iterates over the collection looking for the specified element. If it finds the element, it removes the element from the collection using the iterators remove method. Note that this implementation throws an UnsupportedOperationException if the iterator returned by this collections iterator method does not implement the remove method and this collection contains the specified object.

    这个文档毫无疑问地说明,重写iterator方法会影响remove方法的行为。 它还描述了iterator方法返回的Iterator行为将如何影响remove方法的行为。 与条目 18中的情况相反,在这种情况下,程序员继承HashSet并不能说明重写add方法是否会影响addAll方法的行为。
    @implSpec标签是在Java 8中添加的,并且在Java 9中被大量使用。这个标签应该默认启用,但是从Java 9开始,除非通过命令行开关-tag "apiNote:a:API Note:”,否则Javadoc实用工具仍然会忽略它。

    设计继承涉及的不仅仅是文档说明自用的模式。 为了让程序员能够写出有效的子类而不会带来不适当的痛苦,一个类可能以明智选择的受保护方法的形式提供内部工作,或者在罕见的情况下,提供受保护的属性。 例如,考虑java.util.AbstractList中的removeRange方法:

    1. protected void removeRange(int fromIndex, int toIndex)
    2. Removes from this list all of the elements whose index is between fromIndex, inclusive, and toIndex, exclusive. Shifts any succeeding elements to the left (reduces their index). This call shortens the list by (toIndex - fromIndex) elements. (If toIndex == fromIndex, this operation has no effect.)
    3. This method is called by the clear operation on this list and its sublists. Overriding this method to take advantage of the internals of the list implementation can substantially improve the performance of the clear operation on this list and its sublists.
    4. Implementation Requirements: This implementation gets a list iterator positioned before fromIndex and repeatedly calls ListIterator.nextfollowed by ListIterator.remove, until the entire range has been removed. Note: If ListIterator.remove requires linear time, this implementation requires quadratic time.
    5. Parameters:
    6. fromIndex index of first element to be removed.
    7. toIndex index after last element to be removed.

    这个方法的内部会调用ListIterator.next和ListIterator.remove来删除整个range范围内的数据,这个类设计的目的就是为了快速删除某范围。
    那么当你设计一个继承类的时候,你如何决定暴露哪些的受保护的成员呢? 没有更好的办法, 所能做的最好的就是努力思考,做出最好的测试,然后通过编写子类来进行测试。 应该尽可能少地暴露受保护的成员,因为每个成员都表示对实现细节的承诺。 另一方面,你不能暴露太少,因为失去了保护的成员会导致一个类几乎不能用于继承。
    测试为继承而设计的类的唯一方法是编写子类。 如果你忽略了一个关键的受保护的成员,试图编写一个子类将会使得遗漏痛苦地变得明显。 相反,如果编写的几个子类,而且没有一个使用受保护的成员,那么应该将其设为私有。 经验表明,三个子类通常足以测试一个可继承的类。 这些子类应该由父类作者以外的人编写。

    还有一些类必须遵守允许继承的限制。 构造方法绝不能直接或间接调用可重写的方法。 如果违反这个规则,将导致程序失败。 父类构造方法在子类构造方法之前运行,所以在子类构造方法运行之前,子类中的重写方法被调用。 如果重写方法依赖于子类构造方法执行的任何初始化,则此方法将不会按预期运行。 为了具体说明,这是一个违反这个规则的类:

    1. package item19;
    2. /**
    3. * @author: qujundong
    4. * @date: 2020/11/28 上午7:32
    5. * @description:
    6. */
    7. public class Super {
    8. public Super(){
    9. overrideMe();
    10. }
    11. public void overrideMe(){
    12. }
    13. }
    14. package item19;
    15. import java.time.Instant;
    16. /**
    17. * @author: qujundong
    18. * @date: 2020/11/28 上午7:33
    19. * @description:
    20. */
    21. public class Sub extends Super {
    22. private final Instant instant;
    23. Sub(){
    24. instant = Instant.now();
    25. }
    26. @Override
    27. public void overrideMe(){
    28. System.out.println(instant);
    29. }
    30. public static void main(String[] args) {
    31. Sub sub = new Sub();
    32. sub.overrideMe();
    33. }
    34. }
    35. //null
    36. //2020-11-27T23:35:04.351

    超类构造器中调用了overrideMe函数,我们期望超类构造函数执行一次,main函数执行一次,但是因为超类构造器先执行,这个时候Sub构造器还未执行,因此instanct还没有实例化,超类执行overrideMe不得不输出null。
    构造器中如果调用私有方法,final方法,静态方法是安全的,因为这些都是不可覆盖的。
    如果在为继承而设计的类中实现了Cloneable和Serializable要注意,因为这两个接口中的clone和readObject方法都有一个作用就是充当构造器,生成实例,那也会出现和构造器一样的问题,因此也要满足构造器的要求:无论是clone还是readObject方法,都不可以调用可覆盖函数,无论是直接还是间接的。
    readObject的情况下,重写方法将在子类的状态被反序列化之前运行。 在clone的情况下,重写方法将在子类的clone方法有机会修复克隆的状态之前运行。 在任何一种情况下,都可能会出现程序故障。 在clone的情况下,故障可能会损坏原始对象以及被克隆对象本身。 例如,如果重写方法假定它正在修改对象的深层结构的拷贝,但是尚未创建拷贝,则可能发生这种情况。如下例子:

    1. package item19;
    2. import java.io.Closeable;
    3. import java.sql.Time;
    4. import java.time.Instant;
    5. import java.util.Date;
    6. /**
    7. * @author: qujundong
    8. * @date: 2020/11/28 上午9:30
    9. * @description:
    10. */
    11. public class CloneSuper implements Cloneable{
    12. private int id;
    13. private Time time;
    14. private String name;
    15. CloneSuper(int id, Time date, String name){
    16. this.id = id;
    17. this.time = date;
    18. this.name = name;
    19. }
    20. public void print(){
    21. }
    22. @Override
    23. public String toString() {
    24. return "CloneSuper{" +
    25. "id=" + id +
    26. ", date=" + time +
    27. ", name='" + name + '\'' +
    28. '}';
    29. }
    30. public CloneSuper clone() throws CloneNotSupportedException{
    31. CloneSuper obj = (CloneSuper)super.clone();
    32. obj.time = (Time)time.clone();
    33. print();
    34. return obj;
    35. }
    36. }
    37. package item19;
    38. import java.sql.Time;
    39. import java.time.Instant;
    40. import java.util.Date;
    41. /**
    42. * @author: qujundong
    43. * @date: 2020/11/28 上午9:36
    44. * @description:
    45. */
    46. public class CloneSub extends CloneSuper {
    47. private Time time2 ;
    48. private String subName;
    49. private Instant instant;
    50. CloneSub(int id, Time time1, String name, Time time2, String subName) {
    51. super(id, time1, name);
    52. this.time2 = time2;
    53. this.subName = subName;
    54. }
    55. public void print(){
    56. System.out.println(instant);
    57. }
    58. public CloneSub clone() throws CloneNotSupportedException {
    59. CloneSub obj = (CloneSub)super.clone();
    60. obj.time2 = (Time)time2.clone();
    61. instant = Instant.now();
    62. print();
    63. return obj;
    64. }
    65. @Override
    66. public String toString() {
    67. return super.toString() + " CloneSub{" +
    68. "date2=" + time2 +
    69. ", subName='" + subName + '\'' +
    70. '}';
    71. }
    72. public static void main(String[] args) throws CloneNotSupportedException, InterruptedException {
    73. Time time1 = new Time(System.currentTimeMillis());
    74. Thread.sleep(1000);
    75. Time time2 = new Time(System.currentTimeMillis());
    76. CloneSub obj1 = new CloneSub(1, time1, "CloneSuper", time2, "subname");
    77. CloneSub obj2 = obj1.clone();
    78. System.out.println(obj1 == obj2);
    79. System.out.println(obj1);
    80. System.out.println(obj2);
    81. }
    82. }

    这段代码类似于上文构造器所写的代码,clone函数充当了构造器,本来想打印的是两次时间instant,但是因为先调用的超类构造器,这个instant还没有构造好,因此先打印的null,然后子类clone正常打印,这和构造器的输出相似。
    总结:对于那些专门为了继承而设计的类,要建立文档,说明其中所有的自用模式,并且一旦建立文档,这个类的整个声明周期都要遵守。 构造方法绝不能直接或间接调用可重写的方法,如果并且如果实现cloneable和Serializable,其中的clone和readObject也不能调用可重写方法。对于那些不是用于继承而设计的类,尽量将其设置为final或者私有化构造器,不让其他类继承。