视频

从类型体系的角度理解继承和多态

  • 继承是一个类的子类,自动具备了父类的属性和方法,除非被父类声明为私有的
  • 多态是同一个类的不同子类,在调用同一个方法时会执行不同的动作 ```javascript // 面向对象继承和多态示例 class Mammal{ int weight = 20;
    boolean canSpeak(){

    1. return true;

    }

    void speak(){

    1. println("mammal speaking...");

    } }

class Cow extends Mammal{ void speak(){ println(“moo~~ moo~~”); } }

class Sheep extends Mammal{ void speak(){ println(“mee~~ mee~~”); println(“My weight is: “ + weight); //weight的作用域覆盖子类 } }

//将子类的实例赋给父类的变量 Mammal a = Cow(); Mammal b = Sheep();

//canSpeak()方法是继承的 println(“a.canSpeak() : “ + a.canSpeak()); println(“b.canSpeak() : “ + b.canSpeak());

//下面两个的叫声会不同,在运行期动态绑定方法 a.speak(); //打印牛叫 b.speak(); //打印羊叫

  1. <a name="mo8Ru"></a>
  2. ### 子类型(subtype)
  3. 面向对象编程时,我们可以给某个类创建不同的子类,实现一些个性化的功能;写程序时,我们可以站在抽象度更高的层次上,不去管具体的差异。
  4. 子类型的核心是提供了 is-a 的操作。也就是对某个类型所做的所有操作都可以用子类型替代。它可以放宽对类型的检查,从而导致多态。
  5. <a name="V0xwe"></a>
  6. ### 子类型两种实现方式
  7. - 名义子类型(Nominal Subtyping):`Java` 和 `C++` 语言
  8. - 显式声明继承了什么类,或者实现了什么接口
  9. - 结构化子类型(Structural Subtyping)
  10. - 一个类不需要显式地说自己是什么类型,只要它实现了某个类型的所有方法
  11. <a name="RGYJS"></a>
  12. ## 语义分析
  13. 1. 从类型处理的角度出发,我们要识别出新的类型:`Mammal`、`Cow` 和 `Sheep`
  14. 1. 设置正确的作用域![image.png](https://cdn.nlark.com/yuque/0/2022/png/155952/1645238238762-0214dd60-7e69-453f-9b88-0ae500533efe.png#clientId=u0ea26bac-379f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=445&id=u046ca3e0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=445&originWidth=1092&originalType=binary&ratio=1&rotation=0&showTitle=false&size=133113&status=done&style=none&taskId=uf701ae7f-66e2-4145-b289-f2347fb1cd3&title=&width=1092)
  15. 1. 对变量和函数做类型的引用消解
  16. 用 `Mammal` 来声明这两个变量 a,b。按照类型推导的算法,a 和 b 都是 `Mammal`,这是个 I 属性计算的过程。也就是说,在编译期,我们无法知道变量被赋值的对象确切是哪个子类型,只知道声明变量时,它们是哺乳动物类型,至于是牛还是羊,就不清楚了。<br />正确的消解,是要指向 Cow 和 Sheep 的 speak 方法,只能到运行期再解决。
  17. <a name="UTPAW"></a>
  18. ## 在运行期实现方法的动态绑定
  19. 在运行期,我们能知道 a 和 b 这两个变量具体指向的是哪个对象,对象里是保存了真实类型信息的。ClassObject 的 type 属性会指向一个正确的 Class,这个类型信息是在创建对象的时候被正确赋值的。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/155952/1645238808324-77855d36-4003-4962-88d4-d413eeff261b.png#clientId=u0ea26bac-379f-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=339&id=u8ffd7bbc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=339&originWidth=1079&originalType=binary&ratio=1&rotation=0&showTitle=false&size=137158&status=done&style=none&taskId=ue8a602a1-e926-4ff4-8493-36375235bc8&title=&width=1079)
  20. <a name="i79eI"></a>
  21. ### 实现多态
  22. 在调用类的属性和方法时,我们可以根据运行时获得的,确定的类型信息进行动态绑定。下面这段代码是从本级开始,逐级查找某个方法的实现,如果本级和父类都有这个方法,那么本级的就会覆盖掉父类的。
  23. ```javascript
  24. protected Function getFunction(String name, List<Type> paramTypes){
  25. //在本级查找这个这个方法
  26. Function rtn = super.getFunction(name, paramTypes);
  27. //如果在本级找不到,那么递归的从父类中查找
  28. if (rtn == null && parentClass != null){
  29. rtn = parentClass.getFunction(name,paramTypes);
  30. }
  31. return rtn;
  32. }

获取类型信息,这种机制就叫做运行时类型信息(Run Time Type Information, RTTI)。C++、Java 等都有这种机制,比如 Java 的 instanceof 操作,就能检测某个对象是不是某个类或者其子类的实例。

继承情况下对象的实例化

不仅要初始化自己这一级的属性变量,还要把各级父类的属性变量也都初始化。

  1. //从父类到子类层层执行缺省的初始化方法,即不带参数的初始化方法
  2. protected ClassObject createAndInitClassObject(Class theClass) {
  3. ClassObject obj = new ClassObject();
  4. obj.type = theClass;
  5. Stack<Class> ancestorChain = new Stack<Class>();
  6. // 从上到下执行缺省的初始化方法
  7. ancestorChain.push(theClass);
  8. while (theClass.getParentClass() != null) {
  9. ancestorChain.push(theClass.getParentClass());
  10. theClass = theClass.getParentClass();
  11. }
  12. // 执行缺省的初始化方法
  13. StackFrame frame = new StackFrame(obj);
  14. pushStack(frame);
  15. while (ancestorChain.size() > 0) {
  16. Class c = ancestorChain.pop();
  17. defaultObjectInit(c, obj);
  18. }
  19. popStack();
  20. return obj;
  21. }

this 和 super

  1. public class ThisSuperTest {
  2. public static void main(String args[]){
  3. //创建Cow对象的时候,会在Mammal的构造方法里调用this.reportWeight(),这里会显示什么
  4. Cow cow = new Cow();
  5. System.out.println();
  6. //这里调用,会显示什么
  7. cow.speak();
  8. }
  9. }
  10. class Mammal{
  11. int weight;
  12. Mammal(){
  13. System.out.println("Mammal() called");
  14. this.weight = 100;
  15. }
  16. Mammal(int weight){
  17. this(); //调用自己的另一个构造函数
  18. System.out.println("Mammal(int weight) called");
  19. this.weight = weight;
  20. //这里访问属性,是自己的weight
  21. System.out.println("this.weight in Mammal : " + this.weight);
  22. //这里的speak()调用的是谁,会显示什么数值
  23. this.speak();
  24. }
  25. void speak(){
  26. System.out.println("Mammal's weight is : " + this.weight);
  27. }
  28. }
  29. class Cow extends Mammal{
  30. int weight = 300;
  31. Cow(){
  32. super(200); //调用父类的构造函数
  33. }
  34. void speak(){
  35. System.out.println("Cow's weight is : " + this.weight);
  36. System.out.println("super.weight is : " + super.weight);
  37. }
  38. }
  39. // result
  40. Mammal() called
  41. Mammal(int weight) called
  42. this.weight in Mammal : 200
  43. Cow's weight is : 0
  44. super.weight is : 200
  45. Cow's weight is : 300
  46. super.weight is : 200