JavaSE基础知识(十七)--Java复用代码之向上转型(为多态章节预热准备)

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

本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
向上转型实际上指的是继承关系!
"为新的类提供方法"并不是继承技术中最重要的方面(虽然继承一个类能获得它所有的方法,但是这个不是继承技术的主要目的),继承最重要的方面是用来表现子类和父类之间的关系,这种关系可以用"子类是父类的一种特殊类型"来概括(比如前面博文涉及到的继承:餐叉,小刀,勺子都是餐具类型的一种特殊类型)。
以上描述并不仅仅只是一种描述,这种思想完全落实在了Java语言上(Java语言对它是完全支撑的!)。
例如,假设有一个称为Instrument的代表乐器的父类和一个称为Wind的子类。由于继承可以确保父类中所有的方法在子类中也同样有效,所以能够向父类发送的所有消息同样也可以向子类发送
如果父类Instrument有一个play()方法,那么子类Wind肯定也有这个方法,这意味着我们可以准确地说Wind对象也是一种Instrument类型(Wind也是一种乐器!类Instrument就是代表乐器。)。
下面通过代码示例来说明编译器是如何支持这一概念的:
代码示例:

// 继承关系
   //父类Instrument,代表乐器类,但是不代表某种具体的乐器。
   class Instrument{
      //方法play()
      public void play(){}
      //方法tune(Instrument i),你需要特别关注的是这个方法的形式参数类型
      //是Instrument类型!
      static void tune(Instrument i){
         //...
         //i代表的是乐器类对象,调用了方法play()
         i.play();
      }
   }
   //类Wind继承类Instrument
   public class Wind extends Instrument{
      //程序执行入口main方法
      public static void main(String[] args) {
         //创建类Wind的对象
         Wind flute = new Wind();
         //将Wind对象做为实际参数传入类Instrument的方法tune(Instrument i)中,
         //这里你需要注意下,类Instrument的方法tune(Instrument i)声明的
         //形式参数类型是Instrument类型的,但是实际传入的类型是Wind类型
         Instrument.tune(flute);
         //程序没有任何问题,可以正常运行得出结果。
      }
   }

在上面的代码示例中,类Instrument的方法tune(Instrument i)声明接受Instrument类型的引用,但是在Wind.main()方法中实际传入的参数却是Wind类型的(传递给方法tune(Instrument i)的是一个Wind对象的引用),鉴于Java对类型的检查十分严格(基本类型都有八种,每种的存储空间都有严格的限制),接受某种类型的方法同样可以接受另外一种类型就会显得很奇怪,除非你认识到Wind对象同样也是一种Instrument类型(因为不存在任何方法tune(Instrument i)是可以通过类Instrument调用,但是又不存在他的子类(Wind)中的)。
在方法tune(Instrument i)中,方法体代码可以对类Instrument以及所有它的子类都起作用!也就是Instrument及其子类都能调用方法play()。因为它们都有这个方法!
这种将Wind引用转换为Instrument引用的动作,我们称之为向上转型,在Java中,向上转型是自动发生的。
所以,在一段代码,或者是一个框架中,你可以通过一个或多个根父类(父类)搭建起来,落实到具体功能的时候,再通过继承根父类(父类)的子类去实现,最后使用子类的功能实现!
这里再提一点,在后期,Java的开发者们发现,类的限制性还是太强了,类变量可能已经被初始化了,方法已经有了部分或者全部实现,所以,干脆都不要了,于是,就创造了一种叫接口的东西,接口就没什么限制性了,因为它只强调了类型,剩下的什么都要靠程序员自己了!
所以,前几年你在学习某某框架的时候,会要求你去实现框架固定的接口,因为在这个框架核心的逻辑功能代码中使用的都是根接口(父接口),具体的实现效果需要使用框架的人按照自己的需求去编辑代码(implements接口),但是现在由于反射的熟练使用,框架代码的耦合度在使用接口的基础上又大大降低了,比如Spring,不会再要求你去实现什么接口了。

那么,多态有什么用?

上面已经解释清楚了类,继承,接口,实现接口之间的关系,也粗略谈到了框架的问题,现在再来浅谈一下多态。
回到上面的示例代码:
方法没有任何的实现!
在运行的时候,传入的是子类的对象!
运行的时候传入的是子类的对象!
之前的博文提到过,子类继承父类,可以将父类的方法进行重写,也就是覆盖,重新实现,那么,现在既然类Wind继承了类Instrument,那么可以在类Wind里面重新实现方法play()

// 在子类Wind中重新实现父类的方法play()
   //类Wind继承类Instrument
   public class Wind extends Instrument{
      //重新实现父类方法play()
      public void play(){
         System.out.println("Wind");
      }
      //程序执行入口main方法
      public static void main(String[] args) {
         //创建类Wind的对象
         Wind flute = new Wind();
         //将Wind对象做为实际参数传入类Instrument的方法tune(Instrument i)中,
         //这里你需要注意下,类Instrument的方法tune(Instrument i)声明的
         //形式参数类型是Instrument类型的,但是实际传入的类型是Wind类型
         Instrument.tune(flute);
         //程序没有任何问题,可以正常运行得出结果。
      }
   }

好了,现在在子类Wind中重新实现了父类Instrument的方法play(),然后在运行时传入子类Wind的对象,那么我们是否可以这么猜想:程序运行的结果会是子类Wind中方法play()的结果?
结果示例:
多态的体现!
在搭建框架的时候,我们使用的是父类/父接口(Instrument),但是进入运行的时候,也就是需要具体实现的时候,传入子类(Wind)的对象,得出的也是子类的实现结果,这就给我们传达了一种意识:
将不变的东西,不变的逻辑,不变的框架体系用父类/父接口去搭建,将变的,需要有创造性的东西用继承父类/implements父接口的子类去实现,Java中的多态会给出你想要的结果!如果你想升级,或者想换一种实现,需要做的就是再实现另外一个子类,然后将原先的子类替换掉,但是主体框架你不再需要做任何的改动,提供这种扩展,以不变应万变的可能的机制就是继承父类/implements父接口+多态!
正是因为多态的存在,你才能在各个子类之间切换实现。
具体的内容,可以关注后续的博文!
现在,我们回到之前提到的向上转型:
为什么称为向上转型?
这个术语使用有其历史原因,并且是以传统的类继承图的绘制方法为基础的,将根类/根接口置于页面的顶端,然后逐渐向下,于是,Wind.java的继承图就是:
向上转型的继承图!
由子类转型成父类,在继承图中是向上移动的,因此,一般称为向上转型,由于向上转型是从一个较专用的类型向较通用的类型转换,所以总是很安全的,也就是说,子类是父类的一个超集,它可能比父类含有更多的方法,但它必须至少具备父类中所有的方法。
在向上转型的过程中,类接口中唯一可能发生的事情就是丢失方法,而不是获取它们。
这就是为什么编译器在"未曾明确表示类型"或"未曾指定特殊标记"的情况下,仍然允许向上转型的原因。
Java中有向上转型,那么对应的,就有向下转型,但是向下转型有些复杂,它需要通过强制转换操作符实现!
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新类,而继承技术其实是不太常用的。
因此,尽管在教授OOP的过程中,我们多次强调继承,但这并不意味着要尽可能使用它,相反,应当慎用这一技术,其使用场合仅限于你确信使用该技术确实有效的情况。
到底是该用组合还是继承,一个最清晰的办法就是问一问自己是否需要从子类向父类进行向上转型,如果必须向上转型,则继承是必要的的,但是如果不需要,则应该考虑是否需要继承。

复用代码章节总结

继承和组合都能从现有类型生成新类型,组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口(你需要深刻理解接口到底是什么)
在使用继承时,由于子类具有父类的接口,因此它可以向上转型成父类,这对多态来讲至关重要。
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般应优先选择使用组合(或者可能是代理),只有确实必要时,才使用继承。
因为组合更具灵活性。此外,通过对成员类型使用继承技术的添加技巧,可以在运行时改变那些成员对象的类型和行为。因此,还可以在运行时改变组合而成的对象的行为。
在设计一个系统时,目标应该是找到或者创建某些类,其中每个类都有具体的用途,而且既不会太大(包含太多功能而难以复用),也不会太小(不添加其它功能就无法使用),如果你的设计变得过于复杂,通过将现有类拆分为更小的部分而添加更过的对象,通常会有所帮助。
当你开始设计一个系统时,应该认识到程序开发是一种增量的过程,犹如人类的学习一样,这一点很重要,程序开发依赖于试验,你可以尽自己所能去分析,但当你开始执行一个项目时,你仍然无法知道所有的答案,如果将项目视为一种有机的,进化着的生命体而去培养,而不是打算像盖摩天大楼一样快速见效,就会获得更多的成功和更迅速的回馈,继承与组合正是在面向对象程序设计中使得你可以执行这种实验的最基本的两个工具。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值