继承
1.在一开始创建Database数据库类、CD类、DVD类的时候,我们出现了大量的代码复制,这是代码质量不良的表现,那么我们应该如何改进呢?
2.代码里面的自己推测的注意点:
①子类与父类之间是如何建立联系的呢?是通过在子类的 “ public class 子类名 ”后面加上“ extends 父类名字 ”。子类/父类的概念就叫做继承。
②在Database类代码中,起初是两个ArrayList容器类对象,对应的元素类型分别是CD和DVD类。现在统一合并为一个,元素类型改为CD和DVD类的父类Item,这点是需要注意的。(理解:父类可以看作是任意子类的代表,所以这里的元素类型可以用父类代替) 下面的add函数也是把父类的对象作为形参,list()函数里for-each循环也用的是这个元素类型为父类的容器来遍历输出。(理解:可以把这些函数里的Item item 看成是CD cd/DVD dvd的代表)
③★★★子类继承了父类的所有东西,“ 父类变量.父类的成员函数 ”,如果子类有同名同参数的成员函数则理解为 “ 子类变量.子类的同名同参数成员函数 ” ,此时不调用继承过来的父类的成员函数。 如果子类没有同名同参数的成员函数,则理解为“ 子类变量.继承过来的父类的成员函数 ”。
3.修改完Database以后,剩余的问题是CD类和DVD类重复的部分太多了,那我们应该如何解决呢?
答: 我们可以把子类CD和DVD中相同的东西挪到父类Item类中。(比如成员变量,成员函数等等),比如可以把CD类和DVD类中的成员变量“ private String title ”放到了父类Item中。但是此时在CD类和DVD类中,title都是指继承自父类的title,但是凡是用到title的地方都会报错,为什么呢?
原因: 父类中声明为private的成员变量,就真的只有父类可以用(虽然子类会继承得到这个成员变量,但是不能直接用,受限于privte)
**解决方法:**① 把private类型换为protected类型,表示该成员变量,自己可以访问,同一个包内的其他类可以访问,其子类可以访问。(有时候父类和子类不一定在一个包下面)此时父类的成员变量在子类中可以常规使用,比如构造函数中类似于this.title=title的初始化呀,成员函数中直接使用呀,都是可以的 ②依然维持其private类型,我们在父类Item中利用构造函数对title进行初始化,并且还用上了一个super()函数(用处不详),如下图所示:
然后在子类中需要对title进行初始化的地方,直接写super(title)就可以了,表示先去父类中完成定义初始化,然后调用父类中形参为title的构造函数。如下图所示:
单步调试可以看出,CD类的对象的初始化步骤是,先停到super(title)前,然后先去做父类中的定义初始化,然后父类中的构造初始化(父类中构造函数的参数得是title),然后再做自己的定义初始化,然后再进自己的构造函数进行初始化。 此时父类中定义的成员变量在子类中初始化时要用到super函数,且在子类的成员函数中不能直接使用
注意: ①与super(title)类似的,super()表示去父类中先进行定义初始化,然后调用父类中参数为空的构造函数。其实在子类对象创建和初始化的过程中分为三种情况,一种就是上述例子中的这种标准情况,在子类的构造函数中有super(某参数),则初始化的顺序上面也讲过了。第二种是子类构造函数中只有super(),则和第一种类似的初始化顺序,只是调用的是父类的不含参数的构造函数。第三种是子类构造函数中没有super函数,这时系统会自动默认完成一次super()的工作,先去父类再回来。
4.★★★ 当子类中自己定义了与父类相同名字的成员变量时,该成员变量与从父类继承而来的同名成员变量是不同的,且在子类中从父类继承过来的成员变量会被自动隐藏,子类中使用该成员变量时,都是使用的自己的!父类中使用该名字的成员变量,就是指父类的!需要注意的是,父类的public成员函数也会被子类继承,可以在子类中任意使用。如果遇到子类中有同名的成员函数,产生就近问题,则在前面加super.即表示调用的是父类的public成员函数。
5.此时我们又回到了第三点的第二句标黄的问题,父类中声明为private的成员变量,不能在子类中被直接使用。则我们可以在父类中设计一个成员函数,让子类调用即可,要注意就近问题。
多态变量和向上造型
1.子类和父类的传递关系:
错误:父类 父类变量=new 父类对象=子类 子类变量=new 子类对象
正确关系:
下面三条子性质具体实例如下:
(1)
(2)
(3)
2.多态变量:
解释:java的所有对象变量都是多态的,多态(对象)变量是有多种形态的,能保存不止一种类型的对象。保存的是声明类型的对象时,也即保存的是静态类型的对象。也可以保存声明类型的子类的对象。当程序运行到该变量处,其管理的是什么类型的对象是不确定的,此时管理的就是动态类型的对象。动态类型有时候和声明类型(静态类型)是一致的,有时候是不一致的,比如保存的是声明类型的子类的对象时就是不一致的。
3.向上造型的定义:
把子类的对象交给父类变量(多态的对象变量),这时候发生的事情就是向上造型。而造型的一般含义:当一个对象变量保存的对象类型不是静态类型(声明类型)此时发生的事情就是“ 造型 ”,向上造型是特殊的“ 造型 ”。
5.我们都知道子类对象可以直接赋值给父类变量,初始化后的子类变量可以直接赋值给父类变量,这也是向上造型;
问:那么初始化后的父类变量是否可以直接赋值给子类变量呢?
答:不可以,直接赋值,这样的造型是会报错的。我们可以在初始化后的父类变量前面加上“ (造型目标的子类名) ”,进行强制造型。但是这样也是不安全的,此时必须满足一个前提条件:此父类的变量已经经过向上造型被该子类对象或者初始化后的子类变量赋过值。否则依然会报错。(父类对象直接赋值给子类变量暂时不考虑)
6.向上造型总是安全的。而其他造型可能需要加强制造型符,且需要满足一定的条件,比如上面的“ 初始化后的父类变量赋值给子类变量 ”。
多态
1.在非子类和父类的其他类中,如果用到了“ 父类的变量.父类的成员函数() ”此时完成的操作参考(#继承)中第2点的③。这时也体现出对象变量的调用函数的多态性质。
2.函数调用的绑定 以及 子类父类函数中的覆盖关系:
类型系统
注意:子类的子类,依然是父类的子类,不管跨越多少级,只是分为间接子类和直接子类而已!
1.java是单根结构,所有的类,不管是系统的还是自己定义的(这些类的子类也包括在其中),不管声明与否(其实是不需要extends的),都是一个叫作Object类的子类(分为直接子类和间接子类)。Object类就是那个根,几乎所有的op语言都是单根结构(除了C++)。所以Object类的一些public函数在这些子类中都可以直接使用,比如典型的 toString() 函数 和 equals() 函数。
2.toString()函数
(1)toString是把对象变量转换为字符串并返回(用一个字符串来表达那个对象变量),但是对于一般的类的变量,直接使用会返回一段复杂的串码,如下所示(注意:① 这里的“ .toString() ”可以省略,系统知道要调用toString()函数,把对象变量转成字符串来输出。②这里会出一复杂串码,但是对于容器类的变量输出的结果就会很好看,我们前面也做过实验):
(2)对于一般类的变量调用toString()函数,输出的都是复杂的串码,显得不是特别有意义,为了输出我们想要的,能看懂的东西,我们得自己再写一个toString()函数构成覆盖关系,使用自己的toString()。可以通过sourse—>generate toString(),快捷方式产生。
3.equals()函数
(1)equals()函数是比较两个变量管理的对象内容是否相同,这个前面我们也接触过。但是对于一般的类的两个变量比较,就算管理的对象内容相同,我们现在直接使用equals()函数去判断,结果依然是false,为什么呢?因为这个类没有自己的equals(),用的是Object父类继承过来的equals(),父类怎么可能知道子类的两个变量管理的内容是否一样呢!所以就算对象内容一样也会输出false。所以我们必须在自己的类里面写equals(),与父类Object里的equals()构成覆盖关系。(之前在String类,容器类,里面使用equals()都没有问题,是因为系统类库里面的类已经给你写好了equals()成员函数,已经与根里面的成员函数构成了覆盖关系了)
(2)因为没有直接的生成equals函数,我么可以通过,sourse—>Override/Implement Methods,改造父类Object里的成员函数equals(),来实现效果。改造实例如下:
4.@override是表明下面这个函数和,Object类里面的某个函数构成覆盖关系(名字,参数表都一样)
5.可拓展性:这里的Database媒体库,如果想要添加一种新的媒体类型是很容易的,不需要修改Database和父类,只需要给父类添加子类就可以了,这种就叫做可拓展性。甚至还可以作子类的子类,实现一种更深的继承关系。(注:可维护性是指能通过修改代码,实现新的功能)