多态

一.相关定义

1-1 多态

多态是同一个行为具有多个不同表现形式或形态的能力。同一个形参类型为基类的接口,使用不同的子类的实例可以执行不同操作。

1-2 绑定

  • 绑定:将一个方法调用和一个方法体关联起来被称作绑定;
  • 前期绑定:若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现);
  • 后期绑定:在运行时根据对象的类型绑定,也叫做动态绑定或运行时绑定。

后期绑定的实现通常是在对象中安置某种”类型信息”,以便方法调用机制能知道对象是哪种类型,从而找到对应的方法体加以调用。

Java中除了static方法和final方法(private方法也属于final方法)之外,其他所有方法都是后期绑定。

1-3 总是调用最派生的方法

Java在使用基类引用调用方法时,总是调用最派生的方法。选取最派生方法的范围是:基类中的方法和基类引用指向的对象所在类中覆写的同名方法。即,如果基类引用所指向对象所在的类中未覆写基类中的方法,那么通过基类引用调用的将会是基类中的方法,否则调用的就是基类引用所指向对象所在类中覆写的方法。

package com.hutao.test.page155;

import static com.hutao.util.Print.print;

class BaseClass {
    public void method1(){
        print("Base class's method1 call method2");
        method2();
    }

    public void method2(){
        print("Base class's method2 was called");
    }
}

class DerivedClass extends BaseClass{
    @Override
    public void method2() {
        print("Derived class's method2 was called");
    }
}

public class Test{
    public static void main(String[] args) {
        BaseClass baseClass = new DerivedClass();
        //使用基类引用调用方法
        baseClass.method1();
    }
}

运行结果为:

Base class's method1 call method2
Derived class's method2 was called

Process finished with exit code 0

如上代码中的派生关系为:

多态

使用基类引用调用的方法是method1(),从BaseClass baseClass = new DerivedClass()可知选取最派生方法的范围应该是BaseClass类和DerivedClass类,但DerivedClass中未覆写BaseClass中的method1(),所以最派生的method1()方法就是BaseClass中的method1()。BaseClass中的method1()又调用了method2(),这里实际上也可理解为是通过基类引用baseClass去调用的method2(),此时,因为DerivedClass中覆写了method2(),所以选取最派生的方法就是DerivedClass类中的method2()。

二.final方法、静态方法和域的多态

结论:final方法(包括private方法)、静态方法和域没有多态特性。

2-1 final方法

final方法不可以被覆写,所以同一个行为就不能有多种表现形式,所以没有多态。注意:private方法是默认的final方法。

package com.hutao.page.page156;
import static com.hutao.util.Print.*;

public class PrivateOverride {
    private void f(){
        print("private f()");
    }

    public static void main(String[] args) {
        PrivateOverride privateOverride = new Derived();
        privateOverride.f();
    }
}

class Derived extends PrivateOverride{
    public void f(){
        print("public f()");
    }
}

运行结果为:

private f()

Process finished with exit code 0

这里还是可以根据1-3中的方式分析,从PrivateOverride privateOverride = new Derived()中可知,选取最派生f()方法的范围为PrivateOverrie类和Derived类,但是Derived类中未覆写f()方法(虽然有同名方法,但因为private方法不可能被覆写,所以Derived类中的f()只能算是另一个新方法),所以选取的范围就只剩下来基类中的f()方法,因此实际调用的方法就是基类中的f()方法。

2-2 静态方法

静态方法对应的行为不具有多态性,静态方法是与类而并非单个对象关联的。

package com.hutao.page.page157;

class StaticSuper{
    public static String staticGet(){
        return "Base staticGet()";
    }
    public String dynamicGet(){
        return "Base dynamicGet()";
    }
}

class StaticSub extends StaticSuper{
    public static String staticGet(){
        return "Derived staticGet()";
    }
    public String dynamicGet(){
        return "Derived dynamicGet()";
    }
}

public class StaticPolymorphism {
    public static void main(String[] args) {
        //静态方法是与类而并非单个对象关联的,所以这里的静态方法会与引用的类型StaticSuper类相关联
        StaticSuper staticSuper = new StaticSub();
        System.out.println(staticSuper.staticGet());
        System.out.println(staticSuper.dynamicGet());
    }
}

运行结果为:

Base staticGet()
Derived dynamicGet()

Process finished with exit code 0

2-3 域

如果直接访问某个域,这个访问在编译期就进行了解析(并非是运行时绑定),所以域不具有多态性。

package com.hutao.page.page156;

class Super{
    public int field = 0;
    public int getField(){
        return field;
    }
}

class Sub extends Super{
    public int field = 1;
    public int getField(){
        return field;
    }
    public int getSupperField(){
        return super.field;
    }
}

public class FieldAccess {
    public static void main(String[] args) {
        Super sup = new Sub();//upcast
        System.out.println("sup.field = "+sup.field +
                ", sup.getField() = "+sup.getField());

        Sub sub = new Sub();
        System.out.println("sub.field = "+sub.field+
                ", sub.getField() = "+sub.getField()+
                ", sub.getSuperField() = "+sub.getSupperField());
    }
}

运行结果为:

sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0

Process finished with exit code 0

三.构造器和多态

3-1 构造器的多态

构造器不具有多态性,因为构造器实际上是隐式声明的static方法,由2-2可知,静态方法不具有多态性。

  • 基类的构造器总是在导出类的构造过程中被调用;
  • 无论是基类还是导出类,在构造器被调用时,所有成员变量 都已经完成了初始化
package com.hutao.page.page158;
import static com.hutao.util.Print.*;

class Meal{
    Meal(){
        print("Meal()");
    }
}

class Bread{
    Bread(){
        print("Bread()");
    }
}

class Cheese{
    Cheese(){
        print("Cheese()");
    }
}

class Lettuce{
    Lettuce(){
        print("Lettuce()");
    }
}

class Lunch extends Meal{
    Lunch(){
        print("Lunch()");
    }
}

class PortableLunch extends Lunch{
    //此成员变量用来验证是先初始化成员再调用构造器的。
    private Bread bread = new Bread();

    PortableLunch(){
        print("PortableLunch()");
    }
}

public class Sandwich extends PortableLunch{
    private Bread bread = new Bread();
    private Cheese cheese = new Cheese();
    private Lettuce lettuce = new Lettuce();
    public Sandwich(){
        print("Sandwich()");
    }

    public static void main(String[] args) {
        new Sandwich();
    }
}

运行结果为:

Meal()
Lunch()
Bread()//表明成员被初始化在构造器被调用之前
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

Process finished with exit code 0

调用构造器的顺序为:

1)调用基类构造器。这个步骤会不断反复递归下去,首先是构造这个层次结构的根(根基类),然后是下一层的导出类,直到最低层的导出类(构造器执行前需保证类中的所有成员按声明顺序完成了初始化);

2)按声明顺序调用最低层导出类的初始化方法;

3)调用最低层导出类构造器的主体。

3-2 基类构造器中调用具有多态特性的方法

如果要在基类构造器中调用一个可被动态绑定的方法,实际上调用时绑定的方法体可能会是导出类中覆盖此方法的方法体。这个调用结果可能会引发错误,因为在调用基类构造器时,导出类对象还未被完全构造。(在构造导出类对象时候,会先调用基类的构造器。)

package com.hutao.page.page163;
import static com.hutao.util.Print.*;

class Glyph{
    void draw(){
        print("Glyph.draw()");
    }

    Glyph(){
        print("Glyph() before draw()");
        draw();
        print("Glyph() after draw()");
    }
}

class RoundGlyph extends Glyph{
    private int radius = 1;

    RoundGlyph(){
        this.radius = radius;
        print("RoundGlyph.RoundGlyph(), radius = "+radius);
    }

    @Override
    void draw() {
        print("RoundGlyph.draw(), radius = "+radius);
    }
}

public class PolyConstructors {
    public static void main(String[] args) {
        new RoundGlyph();
    }
}

运行结果为:

Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 1

Process finished with exit code 0

Glyph.draw()方法设计为将要被覆盖,这种覆盖是在RoundGlyph中发生的。但是Glyph构造器会调用这个方法,结果导致了对RoundGlyph.draw()的调用。但是此时RoundGlyph中的radius还未被初始化(还是二进制0)。

初始化的具体过程:(->)https://www.cnblogs.com/certainTao/p/14657469.html

编写构造器的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。

在构造器中唯一能够安全调用的方法是本类中的final方法(private方法默认是final方法),这类方法不能被覆盖(其实调用静态方法也是可以的,但这里要表明的重点不是这个)。

Original: https://www.cnblogs.com/certainTao/p/14798451.html
Author: certainTao
Title: 多态

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585279/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • spring boot实现不同生产环境下的文件配置

    spring boot项目开发时不同开发环境,打包生成不同的文件。(避免生产环境得到开发环境时的配置文件) 配置不同生产环境 本文适用于开发环境下需要打包项目至生产环境,避免开发环…

    Linux 2023年6月7日
    096
  • phpcms如何在前台文章列表前显示所属类别名称

    最近做单位网站模版遇到的问题,欲实现的效果: 但是phpcms中自带的文章列表标签没有这个功能,数据库中文章表中也只有类别id的字段,因此不能通过简单的{$r[catname]}读…

    Linux 2023年6月13日
    095
  • Redis集群原理及搭建(Twemproxy、Predixy代理搭建、Redis Cluster集群)

    1 引言 网上很多文章会把集群和主从复制混为一谈,其实这两者是存在本质差异的,各自解决的问题不同。Redis在单机/单节点/单实例存在的风险: 单点故障、 容量有限、 并发压力问题…

    Linux 2023年6月13日
    0104
  • Linux嵌套目录权限的比较探究

    在/tmp目录下新建一个嵌套目录,名字分别为test_0、test_1、test_2。在test_2目录下新建普通文件,名为tryme。设置test_0和test_2的权限为777…

    Linux 2023年6月7日
    090
  • 操作系统之虚拟内存总结

    前言 操作系统为每个进程提供了一个假象:它拥有属于自己的大量的私有内存,可以有巨大的连续地址空间放入自己的代码和数据。用户程序中访问的地址都是虚拟地址,需要经过操作系统和硬件的协同…

    Linux 2023年6月7日
    0147
  • shell脚本并发执行

    简单的并发脚本 如果shell不能执行,或者报格式错误,记得用 Original: https://www.cnblogs.com/phpdragon/p/10511256.htm…

    Linux 2023年5月28日
    0108
  • 每周一个linux命令(tar)

    基础环境 tar命令介绍 tar命令是linux非常使用频率非常高的一个命令,比如:离线软件包的解压缩、将一个目录打包备份、将一个压缩包解压到一个指定的目录。tar命令主要用来将一…

    Linux 2023年6月8日
    097
  • Java学习笔记_Lambda学习

    在Java8之前,如果想”让参数具备行为能力”,即将代码块作为参数进行传递,这是很不方便的。比较普遍的方式就是创建一个类的实例对象,让实例去调用这个方法,从…

    Linux 2023年6月7日
    0107
  • Linux 基于flock命令实现多进程并发读写文件控制

    需求描述 实际项目中,需要在Linux下通过 shell脚本并发读写同一个文件,但是希望同一时刻,只有一个进程可以在读、写目标文件。 解决方案 使用 flock命令。 flock …

    Linux 2023年5月27日
    0108
  • Android 图片设置圆角

    Android 开发中,经常需要对图片进行二次处理,比如添加圆角效果 或 显示圆形图片; 通过第三方框架 Glide 设置圆角效果; 写法1: RequestOptions opt…

    Linux 2023年6月13日
    090
  • 数字证书编码ASN.1

    任务详情 参考附件中图书p223 中13.2的实验指导,完成DER编码 序列号=1174(0x0496),证书签发者 DN=”CN=Virtual CA,C=CN&#8…

    Linux 2023年6月8日
    061
  • shell 同时执行多任务下载视频

    本文为博主原创,转载请注明出处: shell 脚本不支持多线程,但我们需要用shell 脚本同时跑多个任务时怎么让这些任务并发同时进行,可以采用在每个任务 后面 添加一个 &amp…

    Linux 2023年5月28日
    0111
  • Android进阶技术之——一文吃透Android的消息机制

    前言 为什么要老药换新汤 作为Android中 至关重要 的机制之一,十多年来,分析它的文章不断,大量的内容已经被挖掘过了。所以: 已经对这一机制熟稔于心的读者,在这篇文章中,看不…

    Linux 2023年6月13日
    0104
  • 五、用户管理

    id root查看用户uiduid 0管理员uid 1-999系统账号uid 1000-60000普通账号gid 0 管理组gid 1-999 系统组gid 1000-60000 …

    Linux 2023年6月7日
    084
  • bash初始化文件详解

    本文使用的环境: Bash 4.2.46 bash启动时会执行一系列脚本, 具体要执行哪些启动文件, 这和bash的类型有关: 是否为交互式(interactive)的shell,…

    Linux 2023年6月7日
    079
  • Spring Boot yaml配置文件解析

    1、Spring Boot 配置文件类型和作用 2、yaml 配置文件简介 3、yaml 基础语法 3.1、配置【基本】数据类型 3.2、配置【Object、Map】数据类型 3….

    Linux 2023年6月8日
    0101
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球