JavaSE笔记——多态

news2024/11/16 12:05:13

文章目录

  • 前言
  • 一、向上转型回顾
    • 1.忘掉对象类型
  • 二、转机
    • 1.方法调用绑定
    • 2.产生正确的行为
    • 3.可扩展性
  • 三、构造器和多态
    • 1.构造器调用顺序
    • 2.构造器内部多态方法的行为
  • 四、协变返回类型
  • 总结


前言

本文是学习Java编程思想记录的笔记,主要内容介绍在 Java 中多态的概念。

多态是面向对象编程语言中,继数据抽象和继承之外的第三个重要特性。

多态提供了另一个维度的接口与实现分离,以解耦做什么和怎么做。多态不仅能改善代码的组织,提高代码的可读性,而且能创建有扩展性的程序——无论在最初创建项目时还是在添加新特性时都可以 “生长” 的程序。

封装通过合并特征和行为来创建新的数据类型。隐藏实现通过将细节私有化把接口与实现分离。而多态是消除类型之间的耦合。继承允许把一个对象视为它本身的类型或它的基类类型。这样就能把很多派生自一个基类的类型当作同一类型处理,因而一段代码就可以无差别地运行在所有不同的类型上了。多态方法调用允许一种类型表现出与相似类型的区别,只要这些类型派生自一个基类。这种区别是当你通过基类调用时,由方法的不同行为表现出来的。


一、向上转型回顾

前面我们知道了如何把一个对象视作它的自身类型或它的基类类型。这种把一个对象引用当作它的基类引用的做法称为向上转型,因为继承图中基类一般都位于最上方。

public class Instrument {
    public void play() {
        System.out.println("Instrument.play()");
    }
}

public class Wind extends Instrument{
    @Override
    public void play() {
        System.out.println("Wind.play()");
    }
}

public class Music {
    public static void tune(Instrument i) {
        i.play();
    }

    public static void main(String[] args) {
        Wind wind = new Wind();
        Music.tune(wind);
    }
}

在 main() 中你看到了 tune() 方法传入了一个 Wind 引用,而没有做类型转换。这样做是允许的—— Instrument 的接口一定存在于 Wind 中,因此 Wind 继承了Instrument。从 Wind 向上转型为 Instrument 可能 “缩小” 接口,但不会比 Instrument 的全部接口更少。

1.忘掉对象类型

如果 tune() 接受的参数是一个 Wind 引用会更为直观。这会带来一个重要问题:如果你那么做,就要为系统内 Instrument 的每种类型都编写一个新的 tune() 方法。

public class Music {
    public static void tune(Wind i) {
        i.play();
    }

    public static void tune(Stringed i) {
        i.play();
    }

    public static void main(String[] args) {
        Wind wind = new Wind();
        Music.tune(wind);
    }
}

有一个主要缺点:必须为添加的每个新 Instrument 类编写特定的方法。这意味着开始时就需要更多的编程,而且以后如果添加类似 tune() 的新方法或 Instrument 的新类型时,还有大量的工作要做。

只写一个方法以基类作为参数,而不用管是哪个具体派生类,这正是多态所允许的。

二、转机

运行程序后,Wind.play() 的输出结果正是我们期望的,然而它看起来似乎不应该得出这样的结果。
观察 tune() 方法,它接受一个 Instrument 引用。那么编译器是如何知道这里的 Instrument 引用指向的是 Wind,而不是 Stringed 呢

1.方法调用绑定

将一个方法调用和一个方法主体关联起来称作绑定。若绑定发生在程序运行前(如果有的话,由编译器和链接器实现),叫做前期绑定。它是面向过程语言不需选择默认的绑定方式。

上述程序让人困惑的地方就在于前期绑定,因为编译器只知道一个 Instrument 引用,它无法得知究竟会调用哪个方法。

解决方法就是后期绑定,意味着在运行时根据对象的类型进行绑定。后期绑定也称为动态绑定或运行时绑定。当一种语言实现了后期绑定,就必须具有某种机制在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器仍然不知道对象的类型,但是方法调用机制能找到正确的方法体并调用。每种语言的后期绑定机制都不同,但是可以想到,对象中一定存在某种类型信息。

Java 中除了 static 和 final 方法(private 方法也是隐式的 final)外,其他所有方法都是后期绑定。这意味着通常情况下,我们不需要判断后期绑定是否会发生——它自动发生。

2.产生正确的行为

面向对象编程中的经典例子是形状 Shape。形状的例子中,有一个基类称为 Shape ,多个不同的派生类型分别是:Circle,Square,Triangle 等等。继承图展示了它们之间的关系:
在这里插入图片描述
向上转型就像下面这么简单:

Shape s = new Circle();

由于后期绑定(多态)被调用的是 Circle的 draw() 方法,这是正确的。

3.可扩展性

让我们回头看音乐乐器的例子。由于多态机制,你可以向系统中添加任意多的新类型,而不需要修改 tune() 方法。在一个设计良好的面向对象程序中,许多方法将会遵循 tune() 的模型,只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类派生出新的数据类型,从而添加新的功能。那些操纵基类接口的方法不需要改动就可以应用于新类。

三、构造器和多态

通常,构造器不同于其他类型的方法。在涉及多态时也是如此。尽管构造器不具有多态性(事实上人们会把它看作是隐式声明的静态方法),但是理解构造器在复杂层次结构中运作多态还是非常重要的。

1.构造器调用顺序

在派生类的构造过程中总会调用基类的构造器。初始化会自动按继承层次结构上移,因此每个基类的构造器都会被调用到。这么做是有意义的,因为构造器有着特殊的任务:检查对象是否被正确地构造。由于属性通常声明为 private,你必须假定派生类只能访问自己的成员而不能访问基类的成员。只有基类的构造器能初始化自身的元素。因此,必须得调用所有构造器;否则就不能构造完整的对象。这就是为什么编译器会强制调用每个派生类中的构造器的原因。如果在派生类的构造器主体中没有显式地调用基类构造器,编译器就会默默地调用无参构造器。如果没有无参构造器,编译器就会报错(当类中不含构造器时,编译器会自动合成一个无参构造器)。

public class Instrument {
    public Instrument() {
        System.out.println("我是父类构造方法");
    }
}

public class Wind extends Instrument{
    public Wind() {
        System.out.println("我是子类Wind构造方法");
    }
}

public class Music {
    public static void main(String[] args) {
        Wind wind = new Wind();
    }
}

在这里插入图片描述

2.构造器内部多态方法的行为

在普通的方法中,动态绑定的调用是在运行时解析的,因为对象不知道它属于方法所在的类还是类的派生类。

如果在构造器中调用了动态绑定方法,就会用到那个方法的重写定义。然而,调用的结果难以预料因为被重写的方法在对象被完全构造出来之前已经被调用,这使得一些bug 很隐蔽,难以发现。

public class Glyph {
    void draw() {
        System.out.println("Glyph.draw()");
    }

    Glyph() {
        System.out.println("Glyph() before draw()");
        draw();
        System.out.println("Glyph() after draw()");
    }

}

public class RoundGlyph extends Glyph {
    private int radius = 1;

    RoundGlyph(int r) {
        radius = r;
        System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
    }

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

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

在这里插入图片描述
初始化的过程是:

  1. 在所有事发生前,分配给对象的存储空间会被初始化为二进制 0。
  2. 如前所述调用基类构造器。此时调用重写后的 draw() 方法(是的,在调用 RoundGraph 构造器之前调用),由步骤 1 可知,radius 的值为 0。
  3. 按声明顺序初始化成员。
  4. 最终调用派生类的构造器。

因此,编写构造器有一条良好规范:做尽量少的事让对象进入良好状态。如果有可能的话,尽量不要调用类中的任何方法。在基类的构造器中能安全调用的只有基类的final 方法(这也适用于可被看作是 final 的 private 方法)。这些方法不能被重写,因此不会产生意想不到的结果。

四、协变返回类型

Java 5 中引入了协变返回类型,这表示派生类的被重写方法可以返回基类方法返回类型的派生类型:

public class Grain {
    @Override
    public String toString() {
        return "Grain";
    }
}
public class Wheat extends Grain{
    @Override
    public String toString() {
        return "Wheat";
    }
}
public class Mill {
    Grain process() {
        return new Grain();
    }
}

public class WheatMill extends Mill {

    @Override
    Wheat process() {
        return new Wheat();
    }

    public static void main(String[] args) {
        Grain grain = new Mill().process();
        System.out.println(grain.toString());
        grain = new WheatMill().process();
        System.out.println(grain.toString());
    }
}

在这里插入图片描述


总结

多态意味着 “不同的形式”。在面向对象编程中,我们持有从基类继承而来的相同接口和使用该接口的不同形式:不同版本的动态绑定方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/30539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Servlet API(HttpSerrvlet+HttpServletRequest+HttpServletResponse)

目录 🐲 1. HttpServlet 🐲 2. HttpServletRequest HTTP请求 🦄 2.1 打印请求信息(创建 ShowRequest 类) 🦄 2.2 获取 GET 请求中的参数(创建 GetParameter 类) 🦄 2.3 获取 POST 请求中的参数(创建 PostParame…

java学习day57(Spring Cloud)Spring Cloud 微服务

主要课程内容 第⼀部分:微服务架构 互联网应用架构演进 微服务架构的体现思想及优缺点 微服务架构的核心概念 第⼆部分:SpringCloud概述 Sping Cloud 是什么 Sping Cloud 解决什么问题 Sping Cloud 架构 第三部分:案例准备 第四部分&#xff…

2022华为杯研究生数学建模竞赛DS数模选题建议

2022华为杯研究生数学建模竞赛DS数模选题建议 开放性:F>E>AD>BC. 难度:AD>BC>EF (仅C君个人看法) A题 移动场景超分辨定位问题 此题是物理cv类题目,属于比较新颖的超分辨率图像检测类任务&#xff0c…

1、Java的json得到我们想要的数据结构

Java的json得到我们想要的数据结构 第一步:首先我们要知道json就两种数据结构。 !!!第一种数据结构:对象用{ }表示 !!!第二种数据结构:数组用[ ]表示 我们用这个案例来…

在智能家居领域产品中常用芯片

芯片是当前“电子科技设备的灵魂”所在,几乎决定了所有电子设备的综合性能,现如今智能家居带来了全新的使用场景与交互方式,从扫地机器人、智能洗碗机、智能冰箱等智能机器,到智能照明、智能感知、网络通讯、家庭影音等智能系统&a…

H264基础知识入门

之前视频基础,有讲到视频的原始数据YUV,相比RBG,数据确实减少了,但还是一个非常大数据量,会占用很大空间以及在给网络传输带来很大压力。所以必须要对视频进行压缩,减少占用空间。这里主要分享H264编码技术…

数字IC设计之——低功耗设计

目录 概述 背景 为什么需要低功耗设计 CMOS IC功耗分析 基本概念 功耗的分类 功耗相关构成 不同层次低功耗设计方法 芯片中的功耗分布以及对应的低功耗方案 低功耗方案 系统算法级的低功耗技术 编码阶段的低功耗技术 门控时钟 Clock Gating 物理实施的低功耗技术 操作数分离&am…

【第六部分 | JavaScript高级】1:面向对象

目录 【第一章】面向对象 | Class创建、构造函数、方法 | Class继承 | 三个注意点 | 静态成员 | 原型对象 __ _proto___ | 类的本质 【第一章】面向对象 | Class创建、构造函数、方法 创建类 class name {// class body }var xx new name() 构造函数 class Person {co…

【Godot】数据响应的方式执行功能

Godot Engine 版本:4.0 beta 6 下载地址:Index of /godotengine/4.0/beta6/ (downloads.tuxfamily.org) 在这个教程中,学会理解以数据为主的进行处理执行逻辑的代码编写方式,虽然看似简单,但是确是方便又好用。 以及下…

Git使用教程

Git项目的三个工作区域的概念: 1、Git仓库Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。 2、工作目录工作目录是对项目的某个版本独立提取出来的内容…

Ansible之 AWX 创建管理项目的一些笔记

写在前面 分享一些 AWX 创建管理项目的笔记博文内容涉及: 容器化 AWX 手工创建项目Demo通过 SCM 创建项目 Demo项目角色,更新策略介绍,SCM 凭据的创建 食用方式: 需要了解 Ansible理解不足小伙伴帮忙指正 傍晚时分,你坐…

ssm项目改造spring boot项目

快速创建 Spring Boot 项目 添加依赖 如果是普通 Maven 项目&#xff0c;需要手动添加。 <!-- 打包方式 jar 包 --> <packaging>jar</packaging><!-- 指定父工程 --> <parent><groupId>org.springframework.boot</groupId><ar…

操作系统学习笔记(Ⅰ):概述

目录 1 操作系统概念 1.1 定义 1.2 功能 1.系统资源的管理者 2.用户和计算机硬件间接口 3.最接近硬件的层次 2 操作系统的特征 2.1 并发 2.2 共享 2.3 虚拟 2.4 异步 3 发展和分类 3.1 手工操作阶段 3.2 批处理阶段 1.单道批处理阶段 2.多道批处理系统 3.3 分…

启明欣欣STM32开发板闪烁LED实验

最近在咸鱼上买了一块启明欣欣的STM32板子&#xff0c;准备在上面测试open62541和CANopen&#xff0c;到货后如下图&#xff0c; 找商家要了资料&#xff0c;然后运行一个LED灯的实验来简单测试下板子&#xff0c;本文记录一下这个过程。 一 准备 安装Keil 5.35&#xff0c;安…

【selection】 学习光标API并实现编辑区插入表情图片的功能

目录场景介绍selection介绍selection APIrange 介绍range API实现编辑区插入表情图片参考资料场景介绍 在写web版聊天器时&#xff0c;遇到一个需求&#xff1a; 聊天时用户可以在编辑区加入表情图片&#xff0c;并且表情图片要插入在光标位置。// *web版聊天器地址&#xff…

useMemo 使用误区

文章の目录问题背景useMemo 使用前后组件性能对比结论问题背景 在某一个h5项目中&#xff0c;使用了 useMemo 对项目中的组件进行优化&#xff0c;减少组件不必要的re-render, 优化后的结果&#xff1a; 在组件的props和状态未改变时&#xff0c;组件不再进行 re-render 表面上…

生意不好如何逆风翻盘 | 多门店经营必读技巧(1):导购管理 连锁店管理的技巧 连锁店生意经 如何做导购管理

很多连锁店老板反馈&#xff0c;为了优化门店的销售业绩&#xff0c;什么方法都试过了&#xff0c;改店铺陈列、搞优惠活动、做会员管理.......为什么分店的业绩还是看不到明显的提升&#xff1f; 方法试过了&#xff0c;结果没变化&#xff0c;那只能是执行这块出了问题&#…

量子计算(八):观测量和计算基下的测量

文章目录 观测量和计算基下的测量 一、观测量 二、计算基下的测量 三、投影测量 观测量和计算基下的测量 一、观测量 量子比特&#xff08;qubit&#xff09;不同于经典的比特&#xff08;bit&#xff09;&#xff0c;一个量子比特|>可以同时处于|0>和|1>两个状态…

Linux从入门到精通(八)——Linux磁盘管理

文章篇幅较长&#xff0c;建议先收藏&#xff0c;防止迷路 文章跳转Linux从入门到精通&#xff08;八&#xff09;——Linux磁盘管理goLinux从入门到精通&#xff08;九&#xff09;——Linux编程goLinux从入门到精通&#xff08;十&#xff09;——进程管理goLinux从入门到精…

C++ 结合mysql写一个服务端

1 libhv和mysql libhv是一个跨平台的C网络库。 mysql是一个关系型数据库。 2 下载MySQL&#xff0c; 最好不要下载高版本的&#xff0c;容易出错&#xff01;&#xff01;&#xff01; 下载地址MySQL 下载好后目录是这样的&#xff1a; 然后在环境变量里配置&#xff1a;…