面向对象开发技术06_0 继承

06_0 继承

1.继承

继承是允许重用现有类来构造新类的特性

在这里插入图片描述

继承是向下传递的
  • 子类所具有的数据和行为总是作为与其相关的父类的属性的扩展(extension)(即更大的集合)。子类具有父类的所有属性以及其他属性。
  • 继承总是向下传递的,因此一个类可以从它上面的多个超类中继承各种属性 。

如果Dog是Mammal的派生类,而Mammal又是Animal的派生类,则Dog不仅继承了Mammal的属性,同时也继承了Animal的属性。

  • 派生类可以覆盖从基类继承来的行为。
继承的作用
  • 使代码具有可重用性——通过继承类库拥有类库的能力

用别人的类为基础创建自己需要的类,可以省时省力

  • 可以重定义基类的成员函数,实现新的功能

对别人的类中的不足重新实现,但是又不改变别人的类

  • 向派生类添加新成员,实现功能发展和扩展

基于对别人的类的基础上,实现新的类

  • 不需要了解核心技术的细节,就能拥有别人的能力

简单地继承别人的类,可以不需要了解其功能的实现细节,就可以直接使用

继承关系的判断
  • 当类与类之间存在着所属关系时,才具备了继承的前提。a是b中的一种。a继承b。狼是犬科中的一种。
  • 所属关系:" is a "
  • 注意:不要仅仅为了获取其他类中的已有成员进行继承。
  • 判断所属关系:如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。

2.基类和派生类

派生类的声明必须指定基类的名称

class Manager extends Employee

基类分为两种类型:

直接基类
间接基类

直接基类和间接基类

直接基类:

class B extends class A 
    	//A是B的直接基类

间接基类:

class B extends class A 
class C extends class B 
	      //A是C的间接基类

3.继承的层次

在这里插入图片描述

单一继承

  • 只拥有一个父类的新类的创建过程
  • 从一个现有基类创建新类的过程
    在这里插入图片描述
  • 使用一个箭头从派生类指向基类
  • 表示派生类引用基类的函数和数据,而基类没有访问派生类的权限

JAVA为什么不支持多重继承

因为当一个类同时继承两个父类时,两个父类中有相同的功能,则子类对象调用该功能时,无法确定运行的是哪一个?

  • 但是java支持多级继承。A继承B B继承C C继承D。
  • 多级继承的出现,就有了继承体系。
  • 体系中的顶层父类是通过不断向上抽取而来的。它里面定义的该体系最基本最共性内容的功能。

多级继承

在这里插入图片描述

层次继承

在这里插入图片描述

多重继承

在这里插入图片描述

4.访问控制

派生类的函数

能够访问基类的保护和公有成员

派生类的对象

通过派生类的对象能够访问基类的公有成员
通过派生类的对象不能访问基类的私有和保护成员

5.继承的形式

  • 特殊化(specialization)继承
  • 规范化(specification)继承
  • 构造(Construction)继承
  • 泛化继承
  • 扩展继承
  • 限制继承
  • 变体继承
  • 合并继承 (多重继承)

6.特殊化继承

  • 通过**(is-a)检验规则**检验两个概念是否为继承关系
  • 如果检验概念 A 与概念 B 是否为继承关系,那么就尝试着套用这个英语语句:“A(n) A is a(n) B”,如果这个语句“听起来是对的”,那么这个继承关系很可能就是正确的。

在静态类型语言中:

  1. 子类实例必须拥有父类的所有数据成员。
  2. 子类的实例必须至少通过继承实现父类所定义的所有功能
  3. 这样,在某种条件下,如果用子类实例来替换父类实例,那么将会发现子类实例可以完全模拟父类的行为,二者毫无差异。

可替换原则(重要)

指如果类B是类A的子类,那么在任何情况下都可以用类B来替换类A,而外界毫无察觉。

子类型
  • 指符合替换原则的子类关系。
  • 区别于一般的可能不符合替换原则的子类关系
  • 子类说明了新类是继承自父类,而子类型强调的是新类具有父类一样的行为

特殊化继承

  • 很多情况下,都是为了特殊化才使用继承。
  • 在这种形式下,新类是基类的一种特定类型,它能满足基类的所有规范。 用这种方式创建的总是子类型,并明显符合可替换性原则。
  • 与规范化继承一起,这两种方式构成了继承最理想的方式,也是一个好的设计所应追求的目标。

(里氏)替换(代换)原则

  • 类B继承类A时,除添加新的方法完成新增功能外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法
  • 含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏

替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类的方法重写父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

改写(重写)

  • 子类有时为了避免继承父类的行为,需要对其进行改写
  • 语法上:子类定义一个与父类有着相同名称且类型签名相同的方法。
  • 运行时:变量声明为一个父类,它所包含的值来自于子类,与给定消息相对应的方法同时出现于父类和子类。

改写(重写)机制

  • Java、Smalltalk等面向对象语言,只要子类通过同一类型签名改写父类的方法,自然便会发生所期望的行为。
  • C++中,需要父类中使用关键字Virtual来表明这一含义。

方法(函数)改写

  • 为了能使基类中过时的方法能被修改,我们可以在派生类中定义与基类中完全相同的函数,这叫做方法(函数)改写(重写、重置、覆盖)

派生类的函数覆盖基类的同名函数

  • 通过派生类的对象调用时,执行派生类的函数
  • 用基类的对象调用时,执行基类的函数

子类型与改写

子类型比子类有更严格的要求,它不仅要求有继承的语法,同时要求如果存在子类对父类方法的改写(override),那么改写的内容必须符合父类原本的语义,其被调用后的作用应该和父类实现的效果方向一致

7.规范化继承

  • 规范化继承用于保证派生类和基类具有某个共同的接口,即所有的派生类实现了具有相同方法接口的方法
  • 基类中既有已实现的方法,也有只定义了方法接口、留待派生类去实现的方法。
  • 派生类只是实现了那些定义在基类却又没有实现的方法
  • 派生类并没有重新定义已有的类型,而是去实现一个未完成的抽象规范。 也就是说,基类定义了某些操作,但并没有去实现它。只有派生类才能实现这些操作。
  • 在这种情况下,基类有时也被称为抽象规范类
  • 在Java中,关键字abstract确保了必须要构建派生类。声明为abstract的类必须被派生类化,不可能用new运算符创建这种类的实例。除此之外,方法也能被声明为abstract,同样在创建实例之前,必须覆盖类中所有的抽象方法。
  • 规范化继承可以通过以下方式辨认:基类中只是提供了方法接口,并没有实现具体的行为,具体的行为必须在派生类中实现。

8.构造继承

  • 子类使用父类提供的行为,但并不是父类的子类型
  • 子类通过继承父类就可以实现其需要的行为,只需要对方法名称、方法参数等进行修改

9.泛化继承

  • 子类修改或改写父类的某些方法

10.拓展继承

  • 子类只是往父类中添加新行为,并不修改从父类继承来的任何属性
  • 由于父类的功能仍然可以使用,而且没有被修改,因此扩展继承并不违反可替换性原则,用这种方式构建的子类还是子类型

11.限制继承

  • 如果子类的行为比父类的少或更严格,就出现了限制继承

12.变体继承

子类和父类之间都是对方的变体,可以任意选择两个之间的父子关系

例如,用来控制鼠标的代码与用来控制图形输入板的代码几乎完全相同。在概念上没有理由让一个类作为另外一个类的父类,因此可以选择任何一个类作为另外一个类的父类

13.合并继承

  • 可以通过合并两个或者更多的抽象特性来形成新的抽象。
  • 一个类可以继承自多个基类的能力被称为多重继承

14.继承的形式

  • 特殊化:子类是父类的一个特例,即子类是父类的一个子类型
  • 规范化:父类中定义的行为在子类中实现,而父类本身没有实现这些行为
  • 构造:子类使用父类提供的行为,但是并不是父类的子类型。
  • 泛化:子类修改或改写父类的某些方法
  • 扩展:子类添加一些新功能,但没有改变继承来的行为
  • 限制:子类限制了一些来自父类的方法的使用
  • 变体:子类和父类之间都是对方的变体,可以任意选择两个之间的父子关系
  • 合并:子类从多个父类中继承特性

15.构造函数的继承

子类构造方法

  • 构造器不能被继承

子类不能继承父类的构造器

  • 构造器只能通过两种方法获得:

自己编写;
使用系统默认构造器。

子类构造方法

  • 调用父类构造器
  1. 调用父类构造器必须在第一行使用super();
  2. 如果没有使用this ()或super(),系统将自动调用super();
  3. 如果系统调用super(),而父类没有super(),则出错。
  • 如果一个类没有定义构造函数,编译器默认会为该类插入一个默认的无参构造函数。
  • 子类中,父类构造函数必须由程序员显式调用或由编译器隐式地调用。

例子:

class SuperClass{
    private int n;
    SuperClass(){
        System.out.println("SuperClass()");
    }
    SuperClass(int n){
        System.out.println("SuperClass(int n)");
        this.n = n;
    }
}
class SubClass extends SuperClass{
    private int n;

    SubClass(){
        super(300);
        System.out.println("SuperClass");

    }
    SubClass(int n){
        System.out.println("SubClass(int n):"+n);
        this.n = n;
    }
}
public class TestSuperSub{
    public static void main (String args[]){
        SubClass sc = new SubClass();
        SubClass sc2 = new SubClass(200);
    }
}

结果:
在这里插入图片描述

如果把SuperClass()删除,会发现报错了
如果一个类有了构造函数,系统是不会默认为它生成一个空的构造函数的

多级继承的调用次序

class A { 
	A() { 
	System.out.println("A");
	 } } 
class B extends A {
	 B() {
	 System.out.println("B"); } }
class C extends B { 
	  C() { System.out.println("C"); } }
public class hrt { 
	  public static void main(String args[]) {
	     new C(); 
	 } 
}

结果:

A
B
C

基类构造器总是在导出类的构造过程中被调用,而且按照继承层级逐渐向上链接调用顺序则是从基类开始向下)。可以理解为,这么做的逻辑关系是在一个类构建时可能会用到其父类的成员、方法。在清理时顺序相反。

END

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值