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