zoukankan      html  css  js  c++  java
  • 第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
    
    
  • 相关阅读:
    STM32 HAL库 UART 串口读写功能笔记
    c语言数组越界的避免方法
    MOS管做开关之初理解
    keil mdk 菜单 “project” 崩溃问题解决
    51 arm x86 的大小端记录
    (C99)复合字面量
    如何的keil试试调试中,看逻辑分析仪Logic viwer
    c语言之——整型的隐式转换与溢出检测
    C语言入坑指南-缓冲区溢出
    C语言入坑指南-被遗忘的初始化
  • 原文地址:https://www.cnblogs.com/Pikzas/p/11273412.html
Copyright © 2011-2022 走看看