JavaSE基础知识(十八)--Java多态之向上转型(多态初步)

本文介绍了Java中的多态性,强调了多态作为面向对象三大特性之一的重要性。通过实例解释了向上转型(子类对象作为父类类型处理)和动态绑定(运行时确定对象类型调用相应方法)。文章探讨了多态如何提高代码的可扩展性和可维护性,并通过乐器类和几何形状类的例子说明了多态的应用。
摘要由CSDN通过智能技术生成

Java SE 是什么,包括哪些内容(十八)?

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!

开篇一句很重要的话:在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征

Java面向对象程序设计的三大要素:封装,继承,多态(顺序不能乱)
⑴、封装:对应数据抽象,如何将现实中的事物抽象成一种数据类型(用代码表述)
"封装"通过合并特征(类变量)和行为(方法)来创建新的数据类型。其中"实现隐藏"则通过将细节"私有化"把接口和实现分离开来(这种分离的好处重点不在于应用,而是方便代码的维护)。
⑵、继承:对应类的继承extends、接口的实现implements(接口本质上也是一种类)
继承允许将对象视为它自己本身的类型或其父类型来加以处理。这种能力极为重要,因为它允许将多种类型(从同一个父类导出)视为同一类型来处理,而同一份代码也可以毫无差别地运行在这些不同类型之上了(框架代码)。
⑶、多态:对应多态
多态的作用则是消除类型之间的耦合关系(比如,框架代码只与餐具类型耦合,而不会与具体的餐叉、勺子、小刀耦合),它必须建立在继承关系之上。
"多态方法调用允许一种类型表现出与其它相似类型之间的区别,只要它们都是从同一父类导出而来的,这种区别是根据方法行为的不同而表现出来的,虽然这些方法都可以通过同一个父类来调用。"这几句话是Java编程思想中文第四版原书的翻译,个人觉得翻译的实在是有点蹩脚(可能翻译的人本身不是很懂吧),实际上这几句话要表达的意思是:多态允许同一个父类/父接口的方法在不同的子类中有不同的实现,根据你使用的子类不同,多态将表现出不同的行为(因为每个子类对这个方法的实现都不同)
只有将封装继承完全弄明白了,才能领悟到多态带来的好处。
多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来。
多态不但能够改善代码的组织结构和可读性,还能够创建可扩展的程序—即无论项目在最初创建时还是在需要添加新功能时都可以"生长"的程序
多态也称为"动态绑定"、“后期绑定”、"运行时绑定"
在前一篇博文中,我已经提到:对象既可以作为它自己本身的类型使用,也可以作为它的父类型(或根类型)使用,这种把某个对象的引用视为其父类型的引用的做法被称作向上转型(因为在继承树的画法中,父类是放置在上方的)
下面通过一个有关乐器的代码例子说明(因为乐器都要演奏乐符,所以单独创建一个乐符类):
代码示例:

// 向上转型
   //枚举类Note,代表乐符
   public enum Note{
   
      //包括了三种乐符
      MIDDLE_C,C_SHARP,B_FLAT;
   }
   //类Instrument,代表乐器类,但是不代表具体的乐器类型
   //它是所有乐器类的父类
   class Instrument{
   
      //方法play(),带一个Note类型的形式参数n
      public void play(Note n){
   
         //打印字符串"Instrument.play()"
         System.out.println("Instrument.play()");
      }
   }
   //类Wind,代表具体的乐器(具体是什么乐器我百度了没查到),从父类Instrument继承
   class Wind extends Instrument{
   
      //重新实现了父类Instrument中的方法play()
      public void play(Note n){
   
         //打印字符串"Instrument.play()"
         System.out.println("Wind.play() "+n);
      }
   }
   //类Music,代表的是乐器的演奏
   public class Music{
   
      //方法tune(Instrument i),带一个Instrument类型的形式参数i
      //类似框架了,固定接受Instrument类型的参数(当然,我们已经知道,除了Instrument
      //类型本身,还接受它所有的子类对象)
      public static void tune(Instrument i){
   
         //调用Instrument类的方法play(Note n),传入了一个实际参数,
         //也就是一个乐符Note.MIDDLE_C。
         i.play(Note.MIDDLE_C);
      }
      //程序执行入口main方法
      public static void main(String[] args) {
   
         //创建Instrument类的子类Wind的对象.
         Wind flute = new Wind();
         //将对象引用传入方法tune(Instrument i)中
         tune(flute);
      }
   }

结果示例:
父类搭框架,子类去实现!
在上面的代码示例中,类Music的方法tune(Instrument i)接受一个类Instrument的引用(实际上,它还接受任何类Instrument子类对象的引用)。
在main()方法中,当将一个类Wind(类Instrument的子类)的对象引用传递到
tune(Instrument i)方法时,虽然未经任何的转换,但是程序未报任何错误,也没有任何的警告,运行后还能得出满意的结果----这样做是允许的,因为Wind从Instrument继承而来,所以Instrument的接口(也就是方法play(Note n))必定存在于类Wind中。也就是说类Wind的对象也能调用方法play(Note n)
(从Wind向上转型到Instrument可能会"缩小"接口,但不会比Instrument的全部接口更少)。

现在,我们发现,将子类对象传入了"框架"中,似乎"框架"代码故意忘记了它的具体类型(忘记它是具体的Wind类型,而是将它当成Instrument类型处理)。

上面代码示例中的Music.java看起来似乎有些奇怪,为什么它故意忘记对象(Wind对象)的类型呢?
实际上,在进行向上转型的时候,就会发生这种类似"忘记对象类型"的情况。
试想一下,如果让**tune(Instrument i)**方法直接接受一个Wind引用作为自己的参数(也就是将它的形式参数换成Wind i),似乎更加直观。但是这样引发的一个重要问题是:
如果那样做了,就需要为系统内Instrument的每种类型都编写一个新的tune()方法,如果现在加入Stringed(弦乐)和Brass(管乐)这两种Instrument(乐器):
那么,代码需要做出如下的改变:
代码示例:

// 增加几种类Instrument的子类
   //类Stringed继承类Instrument,代表具体的弦乐
   class Stringed extends Instrument{
   
      //重新实现了父类方法play(Note n)
      public void play(Note n){
   
         //打印字符串"Stringed.play()"
         System.out.println("Stringed.play() "+n);
      }
   }
   //类Brass继承类Instrument,代表具体的管乐
   class Brass extends Instrument{
   
      //重新实现了父类方法play(Note n)
      public void play(Note n){
   
         //打印字符串"Brass.play()"
         System.out.println("Brass.play() "+n);
      }
   }
   //类Music2,需要与类Music做对比
   public class Music2{
   
      //方法tune(Wind i),带一个Wind类型的形式参数i,此方法与具体的子类型Wind
      //高度耦合.调用此方法时,只能传入类Wind的对象。
      public static 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值