• 《Java编程思想》学习笔记_多态


    多态

    多态指一个行为产生多种状态,针对父类类型可接收其子类类型,最终执行的状态由具体子类确定,其不同子类可呈现出不同状态。例如人[父类]都会跑步[行为],但小孩[子类]跑步、成年人[子类]跑步、运动员[子类]跑步呈现出来的状态是不一致的。

    例如:

    创建一个父类People,和对应子类Child,Adult,Athletes.

    子类都各自实现了一遍父类的run方法。

    class People {
        public void run() {
            System.out.println("run");
        }
    }
    
    
    class Adult extends People {
        public void run() {
            System.out.println("Adult run,1km 4分钟");
        }
    }
    
    class Child extends People {
        public void run() {
            System.out.println("Child run, ,1km 3分钟");
        }
    }
    
    class Athletes extends People {
        public void run() {
            System.out.println("Athletes run, ,1km 2分钟");
        }
    }
    

    创建了一个run方法,接收参数为People类型。

    public class Main {
        public static void main(String[] args) {
            Adult adult = new Adult();
            Child child = new Child();
            Athletes athletes  = new Athletes();
    
            run(adult);
            run(child);
            run(athletes);
        }
    
        private static void run(People people){
            people.run();
        }
    }
    
    
    Adult run,1km 4分钟
    Child run, ,1km 3分钟
    Athletes run, ,1km 2分钟
    

    可以看到,接收类型为父类类型,具体传递的参数类型为子类类型。执行run方法时,执行的是具体子类的run方法。上述三个子类都对父类的run方法进行了重写。所有优先执行子类重写的方法,如果子类没有重写对应方法,执行的依然是子类方法。

    class Adult extends People {
    
    }
    
    run
    Child run, ,1km 3分钟
    Athletes run, ,1km 2分钟
    

    Adult修改为不重写父类方法,执行后Adult执行的就是父类的run方法。

    既然方法类型为People可接收其所有子类类型。程序可改写为

    public class Main {
        public static void main(String[] args) {
            People adult = new Adult();
            People child = new Child();
            People athletes  = new Athletes();
    
            run(adult);
            run(child);
            run(athletes);
        }
    }
    

    使用多态可以确定一个规范,规范具体的实现可能有差异,但使用该规范时,只需要将接收参数设置为该规范,就可接收所有遵循该规范的实现。避免为每一个具体实现编写一个方法。

    避免如下情况:

        private static void run(Adult people){
            people.run();
        }
        private static void run(Child people){
            people.run();
        }
        private static void run(Athletes people){
            people.run();
        }
    
    

    绑定

    方法绑定

    将方法调用桶一个方法主体关联起来成为方法绑定。方法绑定主要有前期绑定,后期绑定(运行时绑定,动态绑定)。

    • 前期绑定

      方法在编译时绑定,程序一但编译完成,绑定就完成了,方法调用与主体已经进行了关联,运行过程中始终只调用与之绑定的主体上的方法。

    • 后期绑定

      方法绑定是在运行时,根据判断对象类型,调用合适的方法。

    上例中接收参数类型均为People,方法执行时根据传递入方法的不同子类执行具体方法。此时就是动态绑定,运行时方法调用会判断对象类型调用对应的方法。Java中private/final/static修饰的方法都是前期绑定,其余方法都是动态绑定。

    多态的问题

    如下例:

    class A {
        public int i = 0;
    }
    
    class A1 extends A {
        public int i = 1;
    }
    
    public class Main {
        public static void main(String[] args) {
            A a = new A1();
            System.out.println(a.i);
        }
    }
    
    0
    

    如果按照上面多态的的逻辑分析该代码,输出应该为子类的i即1.但实际输出的是父类的i值。

    域的访问操作,都会有编译器解析,因此不是多态的。换而言之,只有方法才具有多态性。

    A a = new A1();
    a.i;
    

    由于访问变量i不具有多态,同时此时A1向上转型为A。

    访问a.i可看做访问A1类型的父类的i,即A1的super.i。

    class A {
        public int i = 0;
    }
    
    class A1 extends A {
        public int i = 1;
    
        public int getI(){
            return i;
        }
    
        public int getAi(){
            return super.i;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            A a = new A1();
            System.out.println(a.i);
    
            A1 a1 = new A1();
            System.out.println(a1.i);
            System.out.println(a1.getI());
            System.out.println(a1.getAi());
        }
    }
    
    0 //A a = new A1(); a.i 实质是A的i
    1 //A1 a1 = new A1(); a1.i A1的i
    1 
    0 // A1 a1 = new A1(); a1.getAi(); super.i A的i
    

    继承时,A与A1的域使用到的是两块区域存储对应变量,可看做A区域中有一个i,A1区域中也有一个i。此时由于父类和子类都有对应i,此时访问a.i虽然真实类型时A1,但A1继承自A,A a = new A1() 访问 a.i,由于A1被向上转型为A,可看做访问A1的父类A的i。

    private/static

    class F {
        private void fPrivate(){
            System.out.println("F.fPrivate");
        }
    
        public static void fStatic(){
            System.out.println("F.fStatic");
        }
    
        public static void main(String[] args) {
            F f = new F1();
            f.fPrivate();
    
            f.fStatic();
        }
    }
    
    class F1 extends F{
        public void fPrivate(){
            System.out.println("F1.fPrivate");
        }
    
        public static void fStatic(){
            System.out.println("F1.fStatic");
        }
    }
    

    其中f调用private方法和static方法,访问的都是父类中的方法,并未实现多态。

    实现多态是基于动态绑定,确定具体方法调用的主体,private访问权限就相当于告诉编译器,该方法不需要动态绑定。当方法不要动态绑定时,就会以当前向上转型的类型为准。所以调用的父类的private方法。

    静态方法是与类关联,而非与对象关联。所以调用时是以具体类型信息为准。也是以向上转型后的父类类型信息为准,调用父类的静态方法。

    构造器的多态

    假设现在有这样一个问题,有两个类是继承关系。子类和父类都有f()方法,在父类初始化时调用了f()方法,此时f方法究竟是子类的还是父类的方法?。

    此处有一个两难的问题:

    1. 首先初始化子类时,父类必须先完成初始化。父类中的f()方法是动态绑定的,是运行时确定的。
    2. 运行时确定意味着需要对应子类也需要完成初始化,但在父类构造过程中调用的f()方法时,子类尚未初始化完成。
    class A {
        A() {
            System.out.println("A构造器开始执行");
            f();//此处调用的子类的f()方法,但此时子类还未完成初始化。
            System.out.println("A构造器结束执行");
        }
    
        void f() {
            System.out.println("A.f()");
        }
    }
    
    class B extends A {
        private int bi = 1;
    
        B() {
    
        }
    
        B(int bi) {
            this.bi = bi;
        }
    
        void f() {
            System.out.println("B.f() bi=" + bi);
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            B b = new B(5);
        }
    }
    
    A构造器开始执行
    B.f() bi=0
    A构造器结束执行
    

    可以看到A构造器中执行时,执行的是子类B的f方法,但此时B类并未完全初始化,所以执行输出bi时输出的默认的0. 一个动态绑定的方法调用会向外深入到继承层次结构内部,它可以调用导出类(子类)里的方法。(《Java编程思想》原话)暂时还不太理解

    协变返回类型

    协变返回类型是指,但某个方法对父类进行重写时,返回类型不一定需要和父类方法的返回类型完全一致。可以是父类方法返回类型的子类。例如,父类方法A f() ,子类重写父类方法应为A f(), 但子类重写时返回类型也可为A的子类,如A1 f().

    首先有父类A子类A1作为返回类型

    class A {
    
        public String toString() {
            return "A";
        }
    }
    
    
    class A1 extends A {
        public String toString() {
            return "A1";
        }
    }
    
    class F {
        public A f() {
            return new A();
        }
    }
    
    class F1 extends F {
        public A1 f() {// 子类重写父类方法,返回类型不一定为父类的A,也可为父类对应返回类型A的子类A1
            return new A1();
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            F f = new F();
            System.out.println(f.f());
    
            f = new F1();
            System.out.println(f.f());
        }
    }
    
    A
    A1
    
  • 相关阅读:
    Channel使用技巧
    Flask开发技巧之异常处理
    后端开发使用pycharm的技巧
    python单元测试
    Docker入门介绍
    python高阶函数的使用
    python内置模块collections介绍
    python中@property装饰器的使用
    三次握手四次挥手
    python类方法@classmethod与@staticmethod
  • 原文地址:https://www.cnblogs.com/huang-changfan/p/14471590.html
Copyright © 2020-2023  润新知