Java编程 多态

目录
  • 一、向上转型
  • 二、转机
    • 1、绑定
    • 2、扩展性
    • 3、缺陷
  • 三、构造器与多态
    • 1、构造器的调用顺序
    • 2、构造器内部的多态方法的行为
  • 四、协变返回类型
  • 五、继承进行设计

前言:

封装,是合并属性和行为创建一种新的数据类型,继承是建立数据类型之间的某种关系(is-a),而多态就是这种关系在实际场景的运用。

多态就是把做什么和怎么做分开了;其中,做什么是指调用的哪个方法[play 乐器],怎么做是指实现方案[使用A乐器 使用B乐器],''分开了''指两件事不在同一时间确定。

一、向上转型

对象既可以作为它本身的类型使用,也可以作为它的基类型使用,而这种把对某个对象的引用视为对其基类型的引用的做法就是向上转型。

example:

public enum Note {
 // 演奏乐符
    MIDDLE_C, C_SHARP, B_FLAT;

}
public class Instrument {

 // 乐器基类
    public void play(Note n) {
        print("Instrument.play()");
    }

}
public class Wind extends Instrument{

 // Wind是一个具体的乐器
    // Redefine interface method:
    public void play(Note n) {
        System.out.println("Wind.play() " + n);
    }

}
public class Music {

 // 乐器进行演奏
    public static void tune(Instrument i) {
        i.play(Note.MIDDLE_C);
    }

    public static void main(String[] args) {
        Wind flute = new Wind();
        tune(flute); // 向上转型
    }
}

好处:

在上面例子中,如果让tune方法接受一个Wind引用作为自己的参数,似乎看起来更为直观,但是会引发一个问题:这个时候你就需要为系统中Instrument的每种类型都编写一个新的tune方法。所以我们只写一个简单的方法,仅仅接收基类作为参数,而不是特殊的导出类,这么做情况不是变得更好吗。

example:

class Stringed extends Instrument {
    public void play(Note n) {
        print("Stringed.play() " + n);
    }
}

class Brass extends Instrument {
    public void play(Note n) {
        print("Brass.play() " + n);
    }
}

public class Music2 {
    public static void tune(Wind i) {
        i.play(Note.MIDDLE_C);
    }

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

    public static void tune(Brass i) {
        i.play(Note.MIDDLE_C);

    }
    public static void main(String[] args) {
        Wind flute = new Wind();
        Stringed violin = new Stringed();
        Brass frenchHorn = new Brass();
        tune(flute); // No upcasting
        tune(violin);
        tune(frenchHorn);
    }
}

二、转机

public static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);

}

在上面这个方法中,它接收一个Instrument引用,那么在这种情况下,编译器怎么样才能知道这个instrument引用指向的是Wind对象呢? ——通过后期绑定

1、绑定

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

在程序执行前进行绑定,就是前期绑定,比如C语言就只有一种方法调用,就是前期绑定。

在运行时根据对象的类型进行绑定就是后期绑定,也叫做动态绑定或者运行时绑定。

Java中除了static方法和final方法之外,其它所有方法都是后期绑定,这意味着通常情况下,我们不必判定是否应该进行后期绑定——它会自动发生。

2、扩展性

由于有多态机制,所以可根据自己的需要向系统里加入任意多的新类型,同时毋需更改 true()方法。在一个设计良好的 OOP 程序中,我们的大多数或者所有方法都会遵从 tune()的模型,而且只与基础类接口通信。我们说这样的程序具有“扩展性”,因为可以从通用的基础类继承新的数据类型,从而新添一些功能。如果是为了适应新类的要求,那么对基础类接口进行操纵的方法根本不需要改变,
对于乐器例子,假设我们在基础类里加入更多的方法[what/adjust],以及一系列新类[Woodwind/Brass],

例子:

class Instrument {
    void play(Note n) { print("Instrument.play() " + n); }
    String what() { return "Instrument"; }
    void adjust() { print("Adjusting Instrument"); }
}

class Wind extends Instrument {
    void play(Note n) { print("Wind.play() " + n); }
    String what() { return "Wind"; }
    void adjust() { print("Adjusting Wind"); }
}

class Percussion extends Instrument {
    void play(Note n) { print("Percussion.play() " + n); }
    String what() { return "Percussion"; }
    void adjust() { print("Adjusting Percussion"); }
}

class Stringed extends Instrument {
    void play(Note n) { print("Stringed.play() " + n); }
    String what() { return "Stringed"; }
    void adjust() { print("Adjusting Stringed"); }
}

class Brass extends Wind {
    void play(Note n) { print("Brass.play() " + n); }
    void adjust() { print("Adjusting Brass"); }
}

class Woodwind extends Wind {
    void play(Note n) { print("Woodwind.play() " + n); }
    String what() { return "Woodwind"; }
}

public class Music3 {
    // Doesn't care about type, so new types
    // added to the system still work right:
    public static void tune(Instrument i) {
        // ...
        i.play(Note.MIDDLE_C);
    }

    public static void tuneAll(Instrument[] e) {
        for(Instrument i : e)
            tune(i);
    }
    public static void main(String[] args) {
        // Upcasting during addition to the array:
        Instrument[] orchestra = {
            new Wind(),
            new Percussion(),
            new Stringed(),
            new Brass(),
            new Woodwind()
        };
        tuneAll(orchestra);
    }
}

​ 为乐器系统添加更多的类型,而不用改动tune方法。tune方法完全可以忽略它周围代码所发生的全部变化,依旧正常运行。

3、缺陷

私有方法

private方法被自动修饰为final,而且对导出类是屏蔽的,所以在子类Derived类中的f方法是一个全新的方法。既然基类中的f方法在在子类Derived中不可见,那么也不能被重载。

域与静态方法

任何域(field)的访问操作都是由编译器解析的,因此不是多态的。

如果某个方法是静态的,那么它就不具有多态性

三、构造器与多态

通常,构造器不同于其它方法,涉及到多态时也是如此。构造器是不具有多态性的

1、构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接。使得每个基类的构造器都能得到调用。

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

构造器调用的层次结构带来一个问题:如果在一个构造器内部调用正在构造的对象的某个动态绑定方法,会发生什么?

public class Glyph {

    void draw() { print("Glyph.draw()"); }
    Glyph() {
        print("Glyph() before draw()");
        draw(); // 调用正在构造的对象的某个动态绑定方法,对象的字段radius被初始化为0
        print("Glyph() after draw()");
    }

}

public class RoundGlyph extends Glyph{

    private int radius = 1;

    RoundGlyph(int r) {
        radius = r;
        print("RoundGlyph.RoundGlyph(), radius = " + radius);
    }
    void draw() {
        print("RoundGlyph.draw(), radius = " + radius);
    }

}

public class PolyConstructors {

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

}

/* Output:
    Glyph() before draw()
    RoundGlyph.draw(), radius = 0
    Glyph() after draw()
    RoundGlyph.RoundGlyph(), radius = 5
*///:~

Glyph的构造器中,我们调用了draw方法,因为这个是动态绑定方法的缘故,我们就会调用导出类RoundGlyph中的draw方法,但是这个方法操纵的成员radius还没初始化,所以就体现出问题了,结果中第一次输出radius为0。

所以初始化的实际过程是:

  • 1 在其他任何事物之前,将分配给对象的存储空间初始化成二进制的零
  • 2 如前所述调用基类构造器
  • 3 按照声明的顺序调用成员的初始化方法
  • 4 调用导出类的构造器主体

四、协变返回类型

在面向对象程序设计中,协变返回类型指的是子类中的成员函数的返回值类型不必严格等同于父类中被重写的成员函数的返回值类型,而可以是更 "狭窄" 的类型。

​ Java 5.0添加了对协变返回类型的支持,即子类覆盖(即重写)基类方法时,返回的类型可以是基类方法返回类型的子类。协变返回类型允许返回更为具体的类型。

例子:

import java.io.ByteArrayInputStream;
import java.io.InputStream;

class Base
{
    //子类Derive将重写此方法,将返回类型设置为InputStream的子类
   public InputStream getInput()
   {
      return System.in;
   }
}
public  class Derive extends Base
{

    @Override
    public ByteArrayInputStream getInput()
    {

        return new ByteArrayInputStream(new byte[1024]);
    }
    public static void main(String[] args)
    {
        Derive d=new Derive();
        System.out.println(d.getInput().getClass());
    }
}
/*程序输出:
class java.io.ByteArrayInputStream
*/

五、继承进行设计

class Actor {
 public void act() {
 }
}

class HappyActor extends Actor {
 public void act() {
  System.out.println("HappyActor");
 }
}

class SadActor extends Actor {
 public void act() {
  System.out.println("SadActor");
 }
}

class Stage {
 private Actor actor = new HappyActor();

 public void change() {
  actor = new SadActor();
 }

 public void performPlay() {
  actor.act();
 }
}

public class Transmogrify {
 public static void main(String[] args) {
  Stage stage = new Stage();
  stage.performPlay();
  stage.change();
  stage.performPlay();
 }

}

输出:

HappyActor
    SadActor

一条通用的准则是:“用继承表达行为间的差异,并用字段表达状态上的变化”。在上述例子中,两者都用到了:通过继承得到了两个不同的类,用于表达 act()方法的差异:而 Stage通过运用组合使自己的状态发生了变化。在这种情况下,这种状态的改变也就产生了行为的改变。

总结:

多态意味着 不同的形式。在面向对象的设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式不同版本的多态绑定方法。 运用数据的抽象和继承,能更好的类型和创造多态的例子。

到此这篇关于Java编程 多态的文章就介绍到这了,更多相关Java多态内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java面向对象编程(封装/继承/多态)实例解析

    本文主要介绍了面向对象的三大特征实例解析,下面看看具体内容. 封装 封装一个Teacher和Student类 package com.hz.test; public class Teacher { private String name; private String majorDirection; private String teachCourse; private int teachAge; public Teacher() { super(); } public Teacher(Stri

  • Java编程—在测试中考虑多态

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承是为了重用父类代码.两个类若存在IS-A的关系就可以使用继承.,同时继承也为实现多态做了铺垫.那么什么是多态呢?多态的实现机制又是什么?请看我一一为你揭开: 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底

  • Java编程 多态

    目录 一.向上转型 二.转机 1.绑定 2.扩展性 3.缺陷 三.构造器与多态 1.构造器的调用顺序 2.构造器内部的多态方法的行为 四.协变返回类型 五.继承进行设计 前言: 封装,是合并属性和行为创建一种新的数据类型,继承是建立数据类型之间的某种关系(is-a),而多态就是这种关系在实际场景的运用. 多态就是把做什么和怎么做分开了:其中,做什么是指调用的哪个方法[play 乐器],怎么做是指实现方案[使用A乐器 使用B乐器],''分开了''指两件事不在同一时间确定. 一.向上转型 对象既可以

  • Java编程Iterator迭代器设计原理及实现代码示例

    我们知道迭代器(Iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素.那么Iterator迭代器的设计原理是什么呢?迭代器问什么定义了一个借口,而不是一个类呢? 我们假设迭代器迭代数据的功能定义为了一个类,那么,会有这样的问题.不同的集合,由于数据结构不一样,所以他们的存储方式也是不一样的.也就是说,迭代器获取的时候,获取的方式是变化的,也就是不固定的.所以把这种方式定义为具体的实现是不合理的. 无论何种集合,他们肯定都有获取的功能,而且不知道什么时候就没有数据了.所有他

  • Java编程复用类代码详解

    本文研究的主要是Java编程中的复用类,那么到底复用类是什么东西,又有什么用法,下面具体介绍. 看了老罗罗升阳的专访,情不自禁地佩服,很年轻,我之前以为和罗永浩一个级别的年龄,也是见过的不是初高中编程的一位大牛之一,专访之后,发现老罗也是一步一个脚印的人.别说什么难做,做不了,你根本就没去尝试,也没有去坚持. If you can't fly then run,if you can't run then walk, if you can't walk then crawl,but whateve

  • Java编程细节重构之为什么if-else不是好代码详析

    前言 面向过程设计和面向对象设计的主要区别是:是否在业务逻辑层使用冗长的if else判断.如果你还在大量使用if else,当然,界面表现层除外,即使你使用Java/C#这样完全面向对象的语言,也只能说明你的思维停留在传统的面向过程语言上.本文将通过示例代码给大家介绍关于Java编程细节重构之if-else的相关内容,下面来一起看看详细的介绍吧 平时开发中if-else用的多吗? 其实这是个再正常不过的coding习惯,当我们代码量小的时候用来做条件判断是再简单不过的了. 但对于优秀程序员来说

  • Java编程删除链表中重复的节点问题解决思路及源码分享

    一. 题目 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 二. 例子 输入链表:1->2->3->3->4->4->5 处理后为:1->2->5 三. 思路 个人感觉这题关键是注意指针的指向,可以定义一个first对象(值为-1,主要用于返回操作后的链表),first.next指向head,定义一个last同样指向first(主要用于操作记录要删除节点的前一个节点),定义一个p指向head,指向当前节点.

  • Java编程实现从尾到头打印链表代码实例

    问题描述:输入一个链表的头结点,从尾巴到头反过来打印出每个结点的值. 首先定义链表结点 public class ListNode { int val; ListNode next = null; ListNode(int val){ this.val = val; } } 思路1:此题明显想到是利用栈的思想,后进先出,先遍历链表,依次将结点值进栈.最后在遍历栈出栈. public static Stack<Integer> printListReverse_Stack(ListNode li

  • Java编程基础测试题分享

    单选题:(每道题目2分) 1. 下列哪个声明是错误的?(B) A.  int i=10; B.  float f=1.1;     //float f=1.1f C.  double d=34.4; D.  byte b=127; long类型的数据加后缀L或者l float类型的数据加后缀F或者f 整数默认是int类型 浮点数默认是double类型 2. 下面哪个不是java中的关键字?(C) A. public B.  true C.  main D.  class 3. 下面程序哪个语句是

  • Java编程之多线程死锁与线程间通信简单实现代码

    死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源:我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁:具体其死锁代码如下:

  • Java编程获取文件列表及子文件目录的方法(非递归)

    废话不谈,直接进入正题,理解见代码注释. // 非递归 public List<String> scanFiles(String path) { List<String>filePaths = new ArrayList<String>(); LinkedList<File> list = new LinkedList<File>(); File dir = new File(path); File[] file = dir.listFiles(

随机推荐