[Java编程思想] 第八章 多态

“我曾经被问到’求教,Babbage先生,如果你向机器中输入错误的数字,可以得到正确的答案吗?’我无法恰当地理解产生这种问题的概念上地混淆” ——Charles Babbage(1791-1871)

再面向对象地程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。

“封装”通过合并特征和行为来创建新的数据类型。”实现隐藏”则通过讲细节”私有化”把接口和实现分离开来。继承允许将对象视为它自己本身的类型或其基类类型来处理。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要它们都是从同一基类导出来的。这种区别是根据方法行为的不同表现出来的,虽然这些方法都可以通过同一个基类来调用。

继承与多态:
继承:类与类之间的关系
多态:方法行为的不同表现

8.1 再论向上转型

对象既可以作为它自身的类型使用,也可以作为它的基类使用。

public enum Note{}
class Obj{
    public void play(Note n){
        System.out.println("Obj.play()");
    }
}

class A extends Obj{
    public void play(Note n){
        System.out.println("A.play()");
    }
}
class M{
    public static void tune(Obj o){         // 提供向上转型方法
        o.play(Note);
    }
    public static void main(String[] args){
        A a = new A();
        tune(a);        // 向上转型,将A转为Obj
    }
}

M.tune()可以接受Obj类型,也可以接受任何导出自Obj的类。

忘记对象的类型,只记住它们的基类。

class B extends Obj{
    public void play(Note n){
        System.out.println("B.play()");
    }
}
class C extends Obj{
    public void play(Note n){
        System.out.println("C.play()");
    }
}
class M{
    public static void tune(A o){       // 为每一个新Obj导出类编写方法
        o.play(Note);
    }
    public static void tune(B o){
        o.play(Note);
    }
    public static void tune(C o){
        o.play(Note);
    }
    public static void main(String[] args){
        A a = new A();
        B b = new B();
        C c = new C();
        tune(a);
        tune(b);
        tune(c);
    }
}

可以这样做,但有一个主要缺点:必须为每一个新Obj类编写特定代码。

8.2 转机

观察tune()方法:

    public static void tune(Obj o){
        o.play(Note);
    }

接受一个Obj引用,编译器怎么知道这个Obj是A还是B?

将一个方法调用同一个方法主体关联起来被称作绑定。

上述程序疑问,主要是因为前期绑定。解决的办法就是后期绑定,它的含义就是在运行时根据对象的类型进行绑定。就是说,编译器一直不知道类型,但是方法调用机制能找到正确的方法体,并加以调用。

Java中除了static方法和final方法(private属于final)之外,其他所有的方法都是后期绑定。通常我们不必判断,因为它会自动发生。

了解Java所有方法都是通过动态绑定实现多态之后,就可以只编写与基类打交道的程序代码了。

在编译时,编译器不需要获得任何特殊信息就能进行正确调用。

只有非private方法才能被覆盖,但是还需要注意覆盖private方法的现象,重写的private是全新的方法。

多态只适用于普通方法,不适用于类成员变量。

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

8.3 构造器和多态

尽管构造器并不具有多态性(实际上是隐式static方法),但了解构造器的运作有助于避免一些困扰。

基类构造器总是在导出类构造过程中被调用,而且按照继承层次逐渐向上,因为构造器有一项特殊任务:检查对象是否被正确构造。导出类只能访问自己的成员,基类成员通常是private,只有基类构造器才能恰当的对自己的元素进行初始化。因此必须另所有构造器都得到调用,否则就不可能正确构建完整对象。

构造器调用顺序:

如果在一个构造器的内部调用正在构造的对象的某个动态绑定(重写)方法,那会发生什么情况?

当在基类构造器中调用导出类重写过的方法,实际运行的是导出类的方法,并且这个导出类的成员可能还未初始化。

构造器中唯一安全调用的方法是基类中final(final包括private)方法,因为这些方法不能被覆盖。

class A{
    void draw(){System.out.println("A.draw()");}
    A(){
        System.out.println("A() bdfore draw()");
        draw();                     // 基类中多态调用,实际执行B.draw()方法
        System.out.println("A() after draw()");
    }
}

class B extends A{
    private int radius = 1;
    B(int r){
        radius = r;
        System.out.println("B.B(), radius = " + radius);    //初始化完成,构造器赋值radius=5
    }
    void draw(){
        System.out.println("B.draw(), radius = " + radius);
    }
}

public class M{
    public static void main(String[] args){
        new B(5);
    }
}
A() bdfore draw()
B.draw(), radius = 0        // 基类调用的实际是导出类多态方法,此时导出类成员还未初始化完成
A() after draw()
B.B(), radius = 5           // 此时整个初始化完成

前一节讲述的初始化顺序并不十分完整,初始化实际过程是:

8.4 用继承进行设计

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

更好的方式是首先选择”组合”,尤其是不能十分确定应该使用哪一种方式时,组合不会强制我们的程序设计进入继承的层次结构。

class O{
    public void act(){}
}
class A extends O{
    public void act(){System.out.println("A...");}
}
class B extends O{
    public void act(){System.out.println("B...");}
}
class S{
    // 组合
    private O o = new A();
    public void change(){o = new B();}
    public void performPlay(){o.act();}
}

public class M{
    public static void main(String[] args){
        S s = new S();      // S 成员初始化是 A
        s.performPlay();    // S 调用默认是 A
        s.change();     // S 修改状态为 B
        s.performPlay();    // S 成员变成 B
    }
}
A...

B...

通过继承得到两个不同的类,用于表达act()方法的差异;而S通过运用组合是自己的状态发生变化,这种情况下,这种状态的改变也产生了应为的改变

8.5 向下转型和运行时类型识别

Java中所有转型都会得到检查!

8.6 总结

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

Original: https://www.cnblogs.com/wei-ran/p/16059361.html
Author: 蔚然丶丶
Title: [Java编程思想] 第八章 多态

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

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

(0)

大家都在看

  • centos安装MySQL问题

    使用sudo yum install mysql-server出现没有可用软件包 mysql-server。 先 执行 wget http://repo.mysql.com/mys…

    Java 2023年6月7日
    084
  • java内存模型(java memory model,JMM)与java虚拟机内存模型(java virtual machine)

    cpu从主存读写数据的速度远慢于cpu执行指令速度,故引入高速缓存,cpu执行指令后,将更新后的数据读写直接作用于多级缓存,缓存再与主存数据交换。 当多核cpu处理多线程任务时,可…

    Java 2023年6月8日
    078
  • Java中方法的定义和使用

    方法的定义和使用 注意事项: 1.方法与方法之间是 平级关系 不可以嵌套定义 2.方法的位置 可以在类{}中任意位置 3.方法定义之后 之后被调用 才能被执行 4.return 关…

    Java 2023年6月8日
    074
  • Reactor模型

    要无障碍阅读本文,需要对NIO有一个大概的了解,起码要可以写一个NIO的Hello World。 说到NIO、Netty,Reactor模型一定是绕不开的,因为这种模式架构太经典了…

    Java 2023年6月5日
    085
  • Maven:新建自定义的依赖并发布到本地仓库

    在项目中,有些代码需要被各个模块调用。为了解耦,可以把这些公共部分的代码整合到一个子项目中,并发布到本地,实现多个项目共享代码。 遇到的问题: Java类文件左下角出现J标志,说明…

    Java 2023年6月6日
    070
  • 搭载Dubbo+Zookeeper踩了这么多坑,我终于决定写下这篇!

    大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦。这篇文章我们不谈数据结构了,来谈谈入门分布式踩过的坑。感觉到了分布式这一层…

    Java 2023年6月5日
    0118
  • MySQL九:MVCC能否解决幻读问题

    转载~ 幻读【前后多次读取,数据总量不一致】 同一个事务里面连续执行两次同样的sql语句,可能导致不同结果的问题,第二次sql语句可能会返回之前不存在的行。 事务A执行多次读取操作…

    Java 2023年6月8日
    0102
  • SpringBoot-多线程

    一、配置 @Configuration @EnableAsync //启用异步任务 public class ThreadPoolConfig { @Bean("task…

    Java 2023年6月9日
    070
  • CAS学习笔记五:SpringBoot自动/手动配置方式集成CAS单点登出

    本文目标 基于SpringBoot + Maven 分别使用自动配置与手动配置过滤器方式实现CAS客户端登出及单点登出。 本文基于《CAS学习笔记三:SpringBoot自动/手动…

    Java 2023年5月29日
    099
  • Java注释Override、Deprecated、SuppressWarnings详解

    一、什么是注释 说起注释,得先提一提什么是元数据(metadata)。所谓元数据就是数据的数据。也就是说,元数据是描述数据的。就象数据表中的字段一样,每个字段描述了这个字段下的数据…

    Java 2023年5月29日
    084
  • Spring的JDK动态代理如何实现的(源码解析)

    前言 上一篇文章中提到了SpringAOP是如何决断使用哪种动态代理方式的,本文接上文讲解SpringAOP的JDK动态代理是如何实现的。SpringAOP的实现其实也是使用了 P…

    Java 2023年6月7日
    091
  • RuoYi(若依)前后端分离版本,windows下部署(nginx)

    上一篇用了tomcat部署(https://blog.csdn.net/yueyekkx/article/details/105491363),还是觉得nginx是王道话不多说开始…

    Java 2023年5月30日
    072
  • Java基础 | Stream流原理与用法总结

    Stream简化元素计算; 一、接口设计 从Java1.8开始提出了Stream流的概念,侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式;依旧先看核心接口的设计: B…

    Java 2023年6月15日
    095
  • 推荐几款最好用的MySQL开源客户端,建议收藏!

    一、摘要 众所周知,MYSQL 是目前使得最广泛、最流行的数据库技术之一,为了更方便的管理数据库,市场上出现了大量软件公司和个人开发者研发的客户端工具,比如我们所熟知的比较知名的客…

    Java 2023年6月9日
    089
  • Gitlab Runner的安装与配置

    Runner Runner就像一个个的工人,而Gitlab-CI就是这些工人的一个管理中心,所有工人都要在Gitlab-CI里面登记注册,并且表明自己是为哪个工程服务的。当相应的工…

    Java 2023年6月8日
    097
  • Java理解mian方法

    public static void mian (Sring[ ] args) public :提供给JVM调用的 static:jvm在调用这个方法是,不需要创建对象 void:…

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