继承 -- Inherit
继承的起源
在很多问题域当中,类与类之间除了之前所讲过的has-a\use-a这两种关系之外,还有一种“is-a”。
在我们举的场景中,普通员工、小时工、年薪员工 都是 员工。如果我们不考虑这层关系,那么设计的时候就会出现以下的问题:
-
三个员工类各自独立,表现不出它们具有is-a关系;
-
三个员工类当中的共有属性和行为,我们需要书写三次,在每个类当中都要写一次。
站在“分离与复用”的角度去看待,我们不希望三个员工类混在一起(希望各自分离);但同时共有的东西,包括属性和行为能够“复用”,至少我们的工作量。那么在这种情况下,我们提出“继承”的概念。
从“继承”在生活中的概念,我们能够轻易得到继承是发生在具有上下关系(或者叫父子关系)当中的,子不需要做任何努力直接就可以得到父所拥有的内容。
在面向对象编程当中,这个概念同样如此。我们可以把场景中的三种员工类进行再次分析,把它们共有的属性和行为提取出来,然后定义在一个叫做“员工”的类当中,这个类就是父类。
这样我们就可以得到一个基本的层次结构
第一层类 Employee 属性:name,num,salary 行为:打卡
第二层 小时工 属性: hour 行为 computePay
正式员工 属性 ssn 行为 mailCheck
第三层 普通员工 属性 行为 computePay
年薪员工 属性 行为 computePay
在这里我需要强调一点,千万不要因为两个类具有相同属性和行为就去做继承设计;而是要正确找到两个类具有is-a关系,那么它们的相同属性和行为进行提取才是有意义的,才去设计继承!!
Java中做继承的语法
Java里面继承的语法很简单,就是 “extends” 关键字
public class 子类 extends 父类 {
//子类的内容
}
属性在继承中的表现
结论:
父类中的属性 在继承的情况,是肯定被放入到了子类对象当中。只是由于访问修饰符的限制,导致某些特殊访问修饰符的属性在子类中不能被直接访问。
访问修饰符
在Java当中一共提供了3个关键字,4种情况来表现访问修饰;
| 本类(MySelf) | 同包非子类(MyWife) | 同包子类(MySon) | 非同包子类(MyDaughter) | 非同包非子类(Stranger) | |
|---|---|---|---|---|---|
| public--公共 | 能 | 能 | 能 | 能 | 能 |
| protected--受保护 | 能 | 能 | 能 | 能 | |
| 不写(默认)--同包 | 能 | 能 | 能 | ||
| private--私有 | 能 |
在这里看到,对于父类当中的私有属性,子类是看不见,但不代表它没有被继承。 父类当中的同包属性,非同包子类是看不见,同样不代表它没有被继承。
父类 与 子类 当中定义了重名属性
1、这在语法上是可行,但是我们应该避免这种设计情况,因为它会给我们造成认知上的麻烦。 正常的设计不应该出现这种情况; 2、这个时候在子类中由于拥有了两个重名属性,而不是用子类的属性 覆盖了 父类的同名属性。只是说,当我们直接用属性名在子类中操作的时候,它会根据就近原则默认为本类声明的属性;如果我们能加了this,那么在子类中写的this,仍然是子类的当前对象,所以仍然认为是子类定义的那个属性。 如果在这种情况下,我们一定要去访问父类中定义的同名属性,那么用把"this."修改为"super."。 这里我们引出了super这个关键字,如果说“this”代表的是当前对象,那么“super”代表的是当前对象的来自于父类的部分。 关于super,需要和this对比学习,明天详细讲。
构造方法 在继承中的表现
结论:父类的构造方法是不会被子类继承的。 原因: 1、从语法层面上,构造方法的名字必须和类名保持一致。如果子类继承了父类的构造方法,那么相当于在子类里面有一个构造方法,但是该方法的名字是父类的名字。这样语法体系出现了冲突。
2、从场景来说,构造方法的作用是用来产生对象的。父类的构造方法是产生父类对象,子类构造方法是产生子类对象。如果子类继承了父类的构造方法,就相当于子类有方法用来产生父类对象了,这明显不合理吧。
虽然没有继承,但是父类的构造方法在继承的实现机制中起了不可替代的作用的
Java当中实现继承的本质 --- 内存叠加
当我们new一个子类对象的时候,它会首先调用父类的构造方法,产生父类对象部分,然后再调用子类的构造方法,在内存中父类对象的下面叠加上子类对象部分,从而形成一个完整的子类对象。
有可能的面试问题: 当我们new一个子类对象是不是产生了两个对象(一个父一个子)? 答案:不是。我们只产生了一个对象,就是子类对象。只是由于这个子类对象是由两部分组成的,一个是来自于父类的共有部分,然后叠加了来自子类的特有部分。
普通方法 在继承当中的表现
结论:父类当中的所有方法都会被子类继承,当然子类能不能调用到这个方法,仍然是受访问修饰符的影响。
方法的重写
如果子类中重新定义一个与父类一模一样的方法,书写上子类自己的实现,那么子类的方法就会覆盖父类的方法。 重写方法的规范要求: 1、方法名保持一致; 2、参数列表保持一致; 3、返回类型保持一致; 4、重写后的该方法访问修饰符不能比重写前小;
方法重写很重要,是面向对象当中构建下一个特征”多态“的基本手段。
重载 与 重写
相同的地方: 1、重载与重写都是指的方法,没有属性重载属性重写的说法; 2、他们体现的思想是一样,”相同的行为不同的实现“。
不同的地方: 1、方法重载是在一个类里面有多个重名方法,各有各的实现; 方法重写是体现在继承关系当中,父类有一个方法,子类继承后也有这个方法,但是子类的实现和父类不一样;
2、方法重载的语法要求: 方法名必须一样,参数列表必须不一样,与返回类型,访问修饰符无关; 方法重写的要求见上面,比重载要高得多。
在场景设计当中去区分它们,比如: 1、程序员都有写代码的行为,Java程序员书写Java代码,前端程序员书写JS代码。 这是重写! 程序员、Java程序员、前端程序员,都有写代码的行为,但是不同的程序员子类各有各的实现
class 程序员{
public void codeing(){
}
}
class Java程序员 extends 程序员{
@Override
public void codeing(){
用Java代码实现
}
}
class 前端程序员 extends 程序员{
@Override
public void codeing(){
用JS代码实现
}
}
2、程序员都有书写代码的功能,给他笔记本就在笔记本上写,给他纸就在纸上写,什么都不给就在沙地上画 这是重载!只有一个类型程序员,行为都是写代码,根据外部传入的参数类型或个数的不同,各有各的实现
class 程序员{
public void codeing(){
沙地上画
}
public void codeing(纸 ){
纸上写
}
public void codeing(电脑){
电脑上写
}
}
3、人都有吃饭的行为,中国人用筷子,美国人用刀叉,印度人用手 4、人都有吃饭的行为,给我筷子我夹着吃,给我勺子我挖着吃
class 学生{
public void goToSchool(int money){
if(money == 20){
}else if(money == 0){
}else{
}
}
}
继承的层次结构
继承可以从上往下顺序继承,这个层次结构理论上可以有无限层。 直接继承关系,上层的叫父类,下层的叫子类; 非直接的继承关系,上层的叫“基类” 或 “超类”; 下层的叫“派生类”。 这是“纵向”的结构。
生活中,每个人除了有父,还有母,我们是从他们两个同时继承而来的。这种情况,我们把它叫做“多继承”。 但是,Java语言在设计的时候没有采纳“多继承”,而是使用的“单继承”,也就是说每个类能且只能有一个父类。不存在:
//报错
class 类 extends 父类, 母类{
}
在面向对象的设计思想当中,其实是没有限制只能做“单继承”的,有些编程语言(C++)选择了“多继承”,有些编程(Java)选择了“单继承”。因此,常常能看到面试官要求对“单继承”还是“多继承”做比较。
“多继承”的好处:具有更好的丰富度! 不好: 类结构会出现网状结构,增加了复杂度。 “单继承”的好处:类继承层次结构简单,都是树形结构 不好:灵活性不够,丰富度不够。 不同编程语言有不同的设计出发点,Java只不过是选择了“单继承”。然后,我们后面会学习Java里面设计的另外一个类型---接口,来弥补没有“多继承”缺失掉的丰富度。
\