当前位置: 萬仟网 > IT编程>软件设计>面向对象 > 多态的独特见解,多态学习一篇必懂。

多态的独特见解,多态学习一篇必懂。

2020年12月31日  | 萬仟网IT编程  | 我要评论
多态的独特见解一级目录二级目录构造器和多态多态的定义是:同一操作作用于不同的类的实例,将产生不同的执行结果,即不同类的对象收到相同的消息时,得到不同的结果。多态是面向对象程序设计的重要特征之一,是扩展性在“继承”之后的又一重大表现 。对象根据所接受的消息而做出动作,同样的消息被不同的对象接受时可能导致完全不同的行为,这种现象称为多态性。一级目录二级目录构造器和多态构造器不同于其他方法,构造器隐式的声明了static(static声明的方法或对象不具备多态性)那么构造器怎么通过多态在复杂的程序结构中

什么是多态

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三中基本特征。多态通过分离做什么和怎么做,从另一角度将接口和实现分离出来。多态不但能改善代码的组织结构和可读性,还能够创建可扩展的程序。
对象即可以作为它自己本身的类型使用,也可以作为它的基类型使用。而这种把对某个对象的引用视为对其基类型的引用的做法被称作向上转型(因为在继承树的画法中,基类是放置在上方的)。

public class test{
    public static void main(String[] args){
		BaseClass A = new ExportClass();
		System.out.println(A);
    }
}
class BaseClass{
    public String toString(){
    	return "BaseClass";
    }
}
class ExportClass extends BaseClass{
    public String toString(){
    	return "ExportClass";
    }
}
/* 执行结果:
 * ExportClass
 */

静态绑定与动态绑定

将一个方法调用同一个方法主体关联起来被称作绑定,若在程序执行前进行绑定叫做前期绑定。运行时根据对象的类型进行绑定称为后期绑定。后期绑定也称为动态绑定和运行时绑定。Java中除了static和final方法(private方法属于final方法)之外,其它的所有方法都是后期绑定(后期绑定的方法具有多态性)。通常情况下,不必判定后期绑定——它会自动生成。

了解多态机制,可能就会开始认为所有的事物都可以多态的发生。然而,只有普通的方法调用可以是多态的。列如下面的示例所演示:

//ing.java
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 getSuperField(){
    	return super.field;
    }
}

public class ing{
    public static void main(String[] args){
		Super sup = new Sub();
		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.getSuperField());
    }
}
/*
 * 执行结果
 * sup.field = 0, sup.getField() = 1
 * sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
 */

在上面的例子中,为Super.field和Sub.field分配了不同的存储空间。然而,在调用sup在调用field的时候,调用的是基类中的field。由此可见只有普通方法才具有多态性。平常我们见不到这种情况,通常情况我们会将所有的对象设置成private,因此不能直接访问它们。只能调用方法来访问。

此外某个方法是静态的也不具备多态性(如下例子):

//test.java
 public class test{
    public static void main(String[] args){
		BaseClass b = new ExportClass();
		b.fun();
    }
}
class BaseClass{
    public static void fun(){
		System.out.println("BaseClass");
    }
}
class ExportClass extends BaseClass{
    public static void fun(){
		System.out.println("ExportClass");
    }
}
/*
 * 执行结果:
 * BaseClass
 */

构造器和多态

构造器不同于其他方法,构造器隐式的声明了static(被static声明不具备多态性),那么构造器怎么通过多态在复杂的程序结构中运行?
注意下面这个程序

//PolyConstructors.java
class Glyph{
    void draw(){
    	System.out.println("Glyph.draw()");
    }
    Glyph(){
		System.out.println("Glyph() before draw()");
		draw();
		System.out.println("Glyph() after draw()");
    }
}
class RoundGlyph extends Glyph{
    private int radius = 1;
    RoundGlyph(int r){
		radius = r;
		System.out.println("RoundGlyph.draw().radius = " + radius);
    }
    void draw() {
    	System.out.println("RoundGlyph.draw().radius = " + radius);
    }
}

public class PolyConstructors{
    public static void main(String[] args){
		new RoundGlyph(5);    
    }
}
/*
 * 
 * 调试结果:
 * Glyph() before draw()
 * RoundGlyph.draw().radius = 0
 * Glyph() after draw()
 * RoundGlyph.draw().radius = 5 
 */

仔细看运行结果,基类的构造器总是在导出类的构造过程中先被调用,而且按照继承层次逐渐向上链接,使每个基类的构造器都能够调用。
在执行中先执行基类的构造方法。那么问题来了为什么没有执行基类中的draw()方法。当基类的构造器调用被覆盖后的draw()方法时,radius并不是默认值1。我们的执行结果不应该是下面这样的吗?

  • 调试结果:(这只是设想的执行结果)
  • Glyph() before draw()
  • Glyph.draw()
  • Glyph() after draw()
  • RoundGlyph.draw().radius = 5

其实初始化的实际过程是:
1)在其他任何事物发生之前,将分配给对象的储存空间初始化成二进制的零
2)如前所述那样调用基类构造器,此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于会给对象储存空间初始化成二进制零的缘故,我们此时会发现radius的值是0。
3)按照声明的顺序调用成员的初始化方法。
4)调用导出类的构造器主体

这样做有一个好处,那就是所有东西都至少初始化成零(或者一些特殊数据类型中与“零”等价的值),而不是留作垃圾。
另一方面,我们应该对这个程序的结果相当震惊。在逻辑方面,做的已经非常完美,而它的行为却不可思议地错了,而且它并没有报错。这时就是构造器在捣乱,此类错误很容易让人忽略,会让浪费很多时间才能发现。因此,编写构造器时有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法(避免调用没有被static、final和private关键字声明的方法)”。在构造器内唯一能够安全调用的那些方法是基类中的static和final方法(也适用与private方法,它们自动属于final方法)。如下例子:

class Glyph{
    private void draw(){
    	System.out.println("Glyph.draw()");
    }
    //这里把draw()方法声明成了private,也就不会出现基类中draw()调用导出类中的draw()
    Glyph(){
		System.out.println("Glyph() before draw()");
		draw();
		System.out.println("Glyph() after draw()");
    }
}
class RoundGlyph extends Glyph{
    private int radius = 1;
    RoundGlyph(int r){
		radius = r;
		System.out.println("RoundGlyph.draw().radius = " + radius);
    }
    void draw() {
    	System.out.println("RoundGlyph.draw().radius = " + radius);
    }
}

public class PolyConstructors{
    public static void main(String[] args){
		new RoundGlyph(5);    
    }
}
/*
 * 
 * 调试结果:
 * Glyph() before draw()
 * Glyph.draw()
 * Glyph() after draw()
 * RoundGlyph.draw().radius = 5 
 */

这些方法不能被覆盖,因此也就不会出现上述问题

谈向上(下)转型

由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,如下例子:

class BaseClass{
	public int number = 0;
    public String f(){
    	return "BaseClass:f()";
    }
    public String g(){
    	return "BaseClass:g()";
    }
}
class ExportClass extends BaseClass{
	public int number = 1;
    public String f(){
    	return "ExportClass:f()";
    }
    public String g(){
    	return "ExportClass:g()";
    }
    public String u(){
    	return "ExportClass:u()";
    }
    public String v(){
    	return "ExportClass:v()";
    }
    public String w(){
    	return "ExportClass:w()";
    }
}
public class Test{
    public static void main(String[] args){
		BaseClass[] x = {
		    new BaseClass(),
		    new ExportClass()
		};
		System.out.println(x[0].f());
		System.out.println(x[1].u());
    }
}

运行结果

Test.java:20: 错误: 找不到符号
System.out.println((x[1]).u());
^
符号: 方法 u()
位置: 类 BaseClass
1 个错误

可以看到x[1]中没有u()方法,这就是向上转型所带来的损失。向上转型时只保留导出类继承基类的普通方法。因此我们想,通过向下转型——也就是在继承层次中向下移动。然而,我们知道向上转型是安全的,因为基类不会具有大于导出类的接口(基类中的普通方法,导出类中都会有)。
在这里插入图片描述

class BaseClass{
    public int number = 0;
    public String f(){return "BaseClass:f()";}
    public String g(){return "BaseClass:g()";}
}
class ExportClass extends BaseClass{
    public int number = 1;
    public String f(){return "ExportClass:f()";}
    public String g(){return "ExportClass:g()";}
    public String u(){return "ExportClass:u()";}
    public String v(){return "ExportClass:v()";}
    public String w(){return "ExportClass:w()";}
}
public class ooo{
    public static void main(String[] args){
	BaseClass[] x = {
	    new ExportClass(),
	    new ExportClass()
	};
	System.out.println(x[0].number);
	System.out.println(((ExportClass)x[1]).number);
    }
}
/*
 * 运行结果:
 * 0
 * 1
 * /

在这里可以看到x[1]被向下转型了,调用了ExportClass中的number。然而并不建议向下转型,这个操作相当于你把风险转移到了运行时,并不可取。如果非要向下转型不可,一定要做好检验和异常处理

本文地址:https://blog.csdn.net/hello_ing/article/details/111831067

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

  • OO第四单元作业小结

    一、本单元两次作业的架构设计 1、第一次作业 整体思路:以class为核心,建立MyInterface、MyAttribute、MyOperatio... [阅读全文]
  • 面向对象电梯系列总结

    面向对象电梯系列总结

    一. 设计策略 1. 架构设计 三个线程:电梯,调度器,主线程(输入线程), 采用worker thread,生产者消费者模式。和同学讨论,发现有的... [阅读全文]
  • GPON学习笔记-01 GPON网络架构

    GPON学习笔记-01 GPON网络架构

    GPON网络主要由三部分组成,ONU + ODN + OLTONU在指定的端口ID上面取数据 GEMPORT I... [阅读全文]
  • 什么是控制反转(IOC)?什么是依赖注入?

    控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的。在传统编程方式中,业务逻辑... [阅读全文]
  • 面向对象OO第三单元总结

    面向对象OO第三单元总结

    第三单元OO总结博客 1 梳理JML语言的理论基础、应用工具链情况 由于篇幅原因,这里只梳理几个在本单元常用的 注释结构 行注释://@annota... [阅读全文]
  • 软件工程(2018)结对编程第二次作业

    [toc] 一.题目要求 我们在刚开始上课的时候介绍过一个小学四则运算自动生成程序的例子,请实现它,要求: 能够自动生成四则运算练习题 可以定制题目... [阅读全文]
  • Java面试题,Java三大特性之一——多态的理解

    首先我们知道Java是一门面向对象的语言 面向对象三大特性,封装、继承、多态。 封装、继承、多态 ↓ 无论是学习路线,还是众人的口语习惯,都是按照这... [阅读全文]
  • 对象关系映射(ORM)

    1、什么是 对象-关系映射 对象-关系映射(Object Relational Mapping,简称ORM,对象关系映射)是一种为了解决面向对象与关... [阅读全文]
  • 理解面向对象思想

    面向对象程序设计的基本认识 0.前言: 接触面向对象程序设计这门课程已经有四周有余了,经过四周的网课,直播课,PTA作业题等的练习,我对面向对象程序设计... [阅读全文]
  • 21. 合并两个有序链表

    知乎ID: 码蹄疾 码蹄疾,毕业于哈尔滨工业大学。 小米广告第三代广告引擎的设计者、开发者; 负责小米应用商店、日历、开屏广告业务线研发;主导小米广... [阅读全文]
验证码:
Copyright © 2017-2021  萬仟网 保留所有权利. 粤ICP备17035492号-1
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com