------------ 恢复内容开始 ------------
几年前你可能会遇到这样一个面试题:"重写和重载的区别", 而现在随着科技的更迭, 面试的问题越来越高级, 面试官的问题也越来越深入, 此文是上述面试题的一个延伸, 让你从简单的重写规则中更加深入的理解其软件工程与面向对象的思想.
1, 重写规则之一:
访问修饰符的限制一定要不小于被重写方法的访问修饰符
比如: Object 类有个 toString()方法, 开始重写这个方法的时候我们总容易忘记 Public 修饰符, 出错的原因就是: 没有加任何访问修饰符的方法具有包访问权限, Default 访问权限小于 Public 访问权限, 所以编译器出错.
2, 重写规则之二:
参数列表必须与被重写方法的相同.
重载的时候, 方法名要一样, 但是参数类型和个数不一样, 返回值类型可以相同也可以不相同.
3, 重写规则之三:
C-1: 返回类型必须与被重写方法的返回类型相同.
父类方法 A:void catch(){} 子类方法 B:int catch(){} 两者虽然参数相同, 返回类型不同, 所以不是重写.
父类方法 A:int catch(){} 子类方法 B:long catch(){} 返回类型虽然兼容父类, 但是不同就是不同, 所以不是重写.
C-1 補足 1: 如果在没有加注 @Override 的情况下, 方法名和参数列表完全相同, 且满足规则 A 的情况下, 返回值类型必须完全一致的情况下, 才不会出现编译错误(即为该方法为强制重写方法). 如果以上条件中参数列表不同, 且返回值类型不同这样编译并不会出现错误(这个方法为在子类的新方法, 且不是重写方法).// 2016/11/21 19:44 bluetata 追記标注補足 1(下接博文后追記)
C-1 補足 2: 当子类的方法重写或实现父类的方法时, 方法的后置条件 (即方法的返回值) 要比父类更严格.[参照 2]// 2016/11/22 追記
即: 如果重写方法的参数列表和方法名相同, 且其他条件满足的情况下, 方法的返回值为父类的子类, 那么该方法也为重写方法
- // C-1 補足 2 の例を挙げります:
- package com.ibm.dietime1943.test;
- public class Computer {
- public Computer sale() {
- return new Computer();
- }
- public HP make() {
- return new HP();
- }
- }
- class IBM extends Computer {
- @Override
- public IBM sale() {
- return new IBM();
- }
- }
- class HP extends Computer {
- @Override
- public Computer make() {
- return new Computer();
- } // compilation error
- }
4, 重写规则之四:
重写方法不能抛出新的异常或者比被重写方法声明的检查异常更广的检查异常. 但是可以抛出更少, 更有限或者不抛出异常.
举个简单的例子: 父类异常好比父亲偷盗抢掠, 那么儿子不能比父亲更坏, 要学好, 自然异常就要少. 虽然举得例子与社会主义核心价值观有冲突, 但是比较形象.// 2016/12/10 10:55 bluetata 追記 add
5, 重写规则之五:
如果一个方法不能被继承, 则不能重写它.
比较典型的就是父类的 private 方法. 因为 private 说明该方法对子类是不可见的, 子类再写一个同名的方法并不是对父类方法进行复写(Override), 而是重新生成一个新的方法, 也就不存在多态的问题了. 同理也可以解释 final, 因为方法同样是不可覆盖的.
6, 重写规则之六:
不能重写被标识为 final 的方法.
// 2016/12/01 17:05 bluetata 追記 add Start
final 方法可以被继承, 但是不能被重写, 一个方法如果被 final 修饰, 那么也就意味着, 这个方法不会被改动(声明一个 final 方法的主要目的是防止方法的内容被修改).
// 2016/12/01 17:12 bluetata 追記 add End
7, 重写规则之七:
静态方法不能被重写.
《JAVA 编程思想》中多次的提到: 方法是静态的, 他的行为就不具有多态性. 静态方法是与类, 而非单个对象相关联的.
父类的普通方法可以被继承和重写, 不多作解释, 如果子类继承父类, 而且子类没有重写父类的方法, 但是子类会有从父类继承过来的方法. 静态的方法可以被继承, 但是不能重写. 如果父类中有一个静态的方法, 子类也有一个与其方法名, 参数类型, 参数个数都一样的方法, 并且也有 static 关键字修饰, 那么该子类的方法会把原来继承过来的父类的方法隐藏, 而不是重写. 通俗的讲就是父类的方法和子类的方法是两个没有关系的方法, 具体调用哪一个方法是看是哪个对象的引用; 这种父子类方法也不在存在多态的性质.《JAVA 编程思想》: 只有普通的方法调用可以是多态的, 静态方法是与类而不是与某个对象相关联.
// 2016/11/22 16:45 bluetata 追記 add Start
補足 1: 父类的静态方法不能被子类覆盖为非静态方法.
子类可以定义于父类的静态方法同名的静态方法, 以便在子类中隐藏父类的静态方法(满足覆盖约束), 而且 Java 虚拟机把静态方法和所属的类绑定, 而把实例方法和所属的实例绑定.
如果在上记的方法上追记 @Override 注解的话, 该方法会出编译错误. 应为该方法实际不是重写方法.
補足 2: 父类的非静态方法不能被子类覆盖为静态方法.
// 2016/11/22 16:45 bluetata 追記 add End
補足 3: 面试可能会遇到的此处相关问题(与静态相关)
1,abstract 方法能否被 static 修饰?
不能被 static 修饰, 因为抽象方法要被重写, 而 static 和子类占不到边, 即上述.// 2016/12/06 20:59 bluetata 追記
反过来也一样 static 方法一定不能被 abstract 方法修饰, static 不属于对象而属于类, static 方法可以被类直接调用(抽象方法需要被实例才能被调用, 这里说的实例是实现的意思, 也就是重写后实现其方法), 这样注定了 static 方法一定有方法体, 不能是没有方法体的抽象方法(被 abstract 修饰) // 2018/07/10 18:17 bluetata 追記
2, 为什么静态方法不能被覆盖? // 2016/12/15 午后 追記
可以参看上面从 java 编程思想摘出的话, 另外在总结下: 覆盖依赖于类的实例, 而静态方法和类实例并没有什么关系. 而且静态方法在编译时就已经确定, 而方法覆盖是在运行时确定的(动态绑定)(也可以说是 java 多态体现在运行时, 而 static 在编译时, 与之相悖).
3, 构造方法能否被重写, 为什么? // 2016/12/15 晚 追記
不能, 构造方法是隐式的 static 方法, 同问题 2. 其实这个问题回答切入点很多, 首先构造方法无返回值, 方法名必须和所在类名相同, 这一点就必杀了子类无法重写父类构造方法. 另外多态方面, 重写是多态的一种提现方式, 假设在子类重写了构造方法是成立的, 那么子类何谈实例成父类. 另外重要得一点, 子类可以使用 super()调用父类的构造方法, 且必须放在子类构造方法内的第一行. 请参看另一篇博文: <<Super 和 this 用法, 对象的加载顺序>>
4, 静态方法为什么不能访问非静态变量或方法? // 2018/07/10 午后追记
对于前面 123 问题理解后, 问题 4 也不难理解, 还是引用下《JAVA 编程思想》: 静态方法是与类而不是与某个对象相关联 用 static 修饰的成员属于类, 非 static 修饰的成员属于实例对象, 也就是类可以直接调用静态成员, 这样假设如果类直接调用了静态成员, 而静态成员调用了非静态变量或方法, 这样在内存中是找不到该非静态变量方法的, 因为静态方法需要创建对象后才可调用.
另外通过类加载说明: 类的加载全过程: 加载 ->验证 ->准备 ->解析 ->初始化 在这里加载到解析阶段都是 JVM 进行主导, 而在初始化阶段才是真正 java 执行代码的阶段. static 成员在初始化阶段之前会被加载到方法区中, 并且进行初始化赋值等操作, 并且分配内存, 而非 static 成员确是在加载后解析后的初始化阶段才会被 "加载" 分配内存, 也就是代码中使用 new 进行创建实例的时候, 这样也就验证了 类可以直接调用 static 成员没有问题, 而直接调用非 static 的成员就会出问题, 因为违背了 java 加载初始化的逻辑.
注意: 如果 static 调用非 static 成员 编译器会出现 No enclosing instance of type * is accessible 异常错误.
XX01, 重写规则補足:
補足 1: 父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖).
補足 2: 父类的非抽象方法可以被覆盖为抽象方法[2].
[2]子类必须为抽象类.
補足 2 の例を挙げります:
- package com.ibm.dietime1943.test;
- public class Computer {
- public Computer send() {
- return new Computer();
- }
- }
- abstract class Lenovo extends Computer {
- @Override
- public abstract Computer send();
- }
以上规则更加详细的说明请参看另一篇博文: <<JAVA 中 @Override 的作用>>
// 2016/11/21 20:27 bluetata 追記标注補足 1(上接博文后追記)
举例(来源于 OCJP 题库):
- Given:
- 1. public class Blip {
- 2. protected int blipvert(int x) {
- return 0;
- }
- 3.
- }
- 4. class Vert extends Blip {
- 5. // insert code here
- 6.
- }
- Which five methods, inserted independently at line 5, will compile? (Choose five.)
- A. public int blipvert(int x) {
- return 0;
- }
- B. private int blipvert(int x) {
- return 0;
- }
- C. private int blipvert(long x) {
- return 0;
- }
- D. protected long blipvert(int x) {
- return 0;
- }
- E. protected int blipvert(long x) {
- return 0;
- }
- F. protected long blipvert(long x) {
- return 0;
- }
- G. protected long blipvert(int x, int y) {
- return 0;
- }
- Answer: A,C,E,F,G
Explanation: 继承关系后, 子类重写父类的方法时, 修饰符作用域不能小于父类被重写方法, 所以 A 正确, B 不正确. 选项 CEFG 均不满足重写规则, 不是重写方法(在子类的普通方法). 选项 D 即为不满足 C-1 补足.
// 2016/11/22 11:08 bluetata 追記補足 2
里氏替换原则(リスコフの置換原則(りすこふのちかんげんそく), 英: Liskov Substitution Principle)
这项原则最早是在 1987 年, 由麻省理工学院的由芭芭拉. 利斯科夫 (Barbara Liskov) 在一次会议上名为 "数据的抽象与层次" 的演说中首先提出.
里氏替换原则的内容可以描述为: "派生类 (子类) 对象能够替换其基类 (超类) 对象被使用." 以上内容并非利斯科夫的原文, 而是译自罗伯特. 马丁 (Robert Martin) 对原文的解读. 其原文为:
Let q(x) be a property provable about objectsx of type T. Thenq(y) should be true for objectsy of typeS where S is a subtype ofT.
严格的定义: 如果对每一个类型为 T1 的对象 o1, 都有类型为 T2 的对象 o2, 使得以 T1 定义的所有程序 P 在所有的对象 o1 都换成 o2 时, 程序 P 的行为没有变化, 那么类型 T2 是类型 T1 的子类型.
通俗的定义: 所有引用基类的地方必须能透明地使用其子类的对象.
更通俗的定义: 子类可以扩展父类的功能, 但不能改变父类原有的功能.
里氏替换原则包含以下 4 层含义:
1, 子类可以实现父类的抽象方法, 但是不能 [1] 覆盖父类的非抽象方法.(核心)[参照 1]
在我们做系统设计时, 经常会设计接口或抽象类, 然后由子类来实现抽象方法, 这里使用的其实就是里氏替换原则. 子类可以实现父类的抽象方法很好理解, 事实上子类也必须完全实现父类的抽象方法, 哪怕写一个空方法, 否则会编译报错.
里氏替换原则的关键点在于不能覆盖父类的非抽象方法. 父类中凡是已经实现好的方法, 实际上是在设定一系列的规范和契约, 虽然它不强制要求所有的子类必须遵从这些规范, 但是如果子类对这些非抽象方法任意修改, 就会对整个继承体系造成破坏. 而里氏替换原则就是表达了这一层含义.
[1]处的说明: 该处的不建议原则, 并不是硬性规定无法不能的含义. 增加新功能时, 尽量添加新方法实现, 而不是 (不建议) 去重写父类的方法, 也不建议重载父类的方法.// 2016/11/22 15:33 bluetata 追記
2, 子类中可以增加自己特有的方法.
3, 当子类重写或实现父类的方法时, 方法的前置条件 (即方法的形参) 要比父类方法的输入参数更宽松.
4, 当子类的方法重写或实现父类的方法时, 方法的后置条件 (即方法的返回值) 要比父类更严格.[参照 2]
// 2016/11/22 18:54 bluetata 追記 add Start - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
来源: http://www.bubuko.com/infodetail-3289763.html