第8章-多态


Think in java 读书笔记

pikzas

2019.07.25

第八章 多态

知识点

1.什么是多态

继承自同一个父类的两个子类对同一方法的不同实现

2.多态的实现基础

JAVA实现多态的基础依赖于后期绑定(动态绑定,运行时绑定),就是一个方法的调用者,只有在运行的时候才知道具体回去调用哪个方法。如果在编译时就知道,那就是前期绑定。

JAVA中除了static,final,private修饰的方法都是动态绑定的。

3.多态的应用

接口处使用父类,实际使用的时候是具体的类。也就是如果要求一个对象执行一个方法,不是单单看这个对象是用什么引用来持有的,而应该看该对象具体是怎么new 出来的。

例如 输出的是man run。

class Human{
    public void run(){
        System.out.println("human run");
    }
}

class Man extends Human{
    public void run(){
        System.out.println("man run");
    }
}

class Demo{
    public static void main(String[] args){
      Human human = new Man();
      human.run(); // 输出的结果是human run
    }
}

4.覆盖(override)的正确姿势

示例执行结果是Sup f(),并不是期待中的Sub f().仔细看会发现,父类中的f()是private修饰的,
而子类的是public修饰的。而private方法是不存在override的,也就是说,
当前这种写法中的f()方法并没有发生override,也就不可能有多态的效果。
但是编译器不会报错。应为这时候,编译器认为父子类中的f()是两个不相干的方法。
此时可以在子类f()上加入@override,来表明我们就是想要覆盖父类方法。
但是介于父类方法是私有的,此时会提示编译错误。

public class Sup {
    private void f(){
        System.out.println("Sup f()");
    }

    public static void main(String[] args) {
        Sup sup = new Sub();
        sup.f();  // 输出Sup f(),因为此时并没有发生重写,所以调用的时候。不会调用子类方法。所以建议此时子父类不要用一个方法名,最好区别开。
    }

}

public class Sub extends Sup {
    public void f(){
        System.out.println("Sub f()");
    }

    public static void main(String[] args) {
        Sup sup = new Sub();
        sup.f();  // 此时会提示编译错误,因为Sup里的f()方法是私有的,这里没有访问权限,除非用Sub su = new Sub();的方式创建Sub
    }
}

5.多态对于属性的影响

多态针对的是方法的调用,所以其对于属性是不存在多态性的

public class Test {
    public static void main(String[] args) {
        Parent parent = new Child();
        System.out.println("parent's field "+ parent.field +" parent's getField " + parent.getField() );
        Child child = new Child();
        System.out.println("child's field "+ child.field +" child's getField " + child.getField() + " child's parent's field "+ child.getParentField() );

    }
}
public class Parent {
    public int field = 0;
    public int getField(){
        return field;
    }
}
public class Child extends Parent {
    public int field = 100;
    public int getField(){
        return field;
    }
    public int getParentField(){
        return super.field;
    }
}
输出 
parent's field 0 parent's getField 100  
child's field 100 child's getField 100 child's parent's field 0
// 方法的调用遵循多态,但是属性的获取,只看当前是用什么引用去持有该对象。因为域的访问是由编译器解析的,不存在多态性。

6.多态对于静态方法的影响

静态方法是属于类的方法,与具体类无关,不存在动态绑定。

public class Test {
    public static void main(String[] args) {
        StaticSuper sup = new StaticSub();
        sup.method();
        sup.staticGet();
    }

}

public class StaticSuper {
    public static void staticGet(){
        System.out.println("Static Super");
    }

    public void method(){
        System.out.println("super method");
    }
}

public class StaticSub extends StaticSuper {
    @Override
    public void method() {
        System.out.println("Sub method");
    }

    public static void staticGet(){
        System.out.println("Static Sub");
    }
}

输出: 
Sub method //父类方法被子类覆盖,此处为多态  
Static Super //对象的持有引用为父类,就会调用父类的静态方法

7.多态对构造器的影响

构造器是隐式的static方法,所以不存在多态

7.1.多继承结构中初始化的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐层向上链接,保证每个基类的构造器都能得到调用。

初始化顺序为

  1. 具体类中的静态属性和方法
  2. 调用基类的构造器,自上而下。
  3. 初始化类的属性和方法
  4. 调用导出类的构造方法

所以可简单的总结一下,以Parent p = new Child();为例

  • p.xxMethod(); 如果Child有对Parent做正确的Override,那么遵循多态的规则,调用子类的xxMethod,如果不存在多态重写的情况,调用Parent的xxMethod。
  • p.xxMethod(); 中如果xxMethod为静态方法,那么也是调用Parent中的xxMethod。
  • 而对p.yyParam,则是看等号前面是用哪个对象来装载新创建的对象的,则取调用它的yyParam。

8.手动销毁对象的原则

对于有多层次的复杂对象,应该遵循,销毁对象的顺序和初始化的相反,对于字段则和声明顺序相反

8.1.如果多个对象实例共享一个对象,并要求手动销毁对象的时候,这时候就应该引入引用计数

例子


public class Share {
    private int refcount = 0;
    private static long counter = 0 ;
    private final long id = counter++;
    public Share(){
        System.out.println("creating " + this);
    }
    public void addRef(){
        refcount++;
    }
    protected void dispose(){
        if(--refcount==0){
            System.out.println("disposing "+ this);
        }
    }
    public String toString(){
        return "Share " + id;
    }
}
public class Composing {
    private Share share;
    private static long counter = 0;
    private final long id = counter++;
    public Composing(Share share){
        System.out.println("Creating "+this);
        this.share = share;
        this.share.addRef();
    }
    protected void dispose(){
        System.out.println("disposing "+this);
        share.dispose();
    }
    public String toString(){
        return "Composing "+id;
    }

}
public class Test {
    public static void main(String[] args) {
        Share share = new Share();
        Composing[] cs = new Composing[10];
        for(int i = 0; i < 10 ;i++){
            cs[i] = new Composing(share);
        }
        for (Composing c:cs ) {
            c.dispose();
        }
    }
}
输出为
creating Share 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
Creating Composing 5
Creating Composing 6
Creating Composing 7
Creating Composing 8
Creating Composing 9
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
disposing Composing 5
disposing Composing 6
disposing Composing 7
disposing Composing 8
disposing Composing 9
disposing Share 0

9.构造器内部调用多态方法

如过父类中存在一个方法在子类中被覆盖,但是此方法同时也在基类构造器中被调用,那么就会产生不恰当的调用。
所以编写构造器的时候,尽力避免书写复杂的调用,尽力调用那些不能被覆盖的方法(private final static的方法)

实例

public class Test {
    public static void main(String[] args) {
        new Extra(333);
    }
}
public class Base {
    public void f(){
        System.out.println("base f");
    }

    public Base(){
        System.out.println("Base start");
        f();
        System.out.println("Base end");
    }
}

public class Extra extends Base {
    private int x = 123;
    public void f(){
        System.out.println("Extra f "+x);
    }
    public Extra(int i){
        System.out.println("Extra start");
        this.x = i;
        System.out.println("Extra end");
    }
}

输出
Base start
Extra f 0
Base end
Extra start
Extra end

Base构造器调用的居然是子类的方法,而且变量i既不是123,也不是输入的333.这是因为这时候子类只对属性进行了初始化为0的动作,然后就被父类调用了。所以会出现这个问题。

10.协变返回类型

被覆盖的子类中的方法的返回值可以是父类中的子类型

11.继承的使用原则

用继承表达行为间的差异,用字段表达状态上的变化

public class Actor {
    public void act(){}
}

public class HappyActor extends Actor {
    @Override
    public void act() {
        System.out.println("i am happy");
    }
}

public class SadActor extends Actor {
    @Override
    public void act() {
        System.out.println("i am sad");
    }
}

public class Stage {
    private Actor actor = new HappyActor();
    public void change(){
        actor = new SadActor();
    }
    public void show(){
        actor.act();
    }
}

public class Test {
    public static void main(String[] args) {
        Stage stage = new Stage();
        stage.show();
        stage.change();
        stage.show();
    }
}

输出
i am happy
i am sad

相关