面向对象有三大特征:封装、继承、多态。今天就来谈谈继承。
继承概述
继承的概念:
何为继承?
当多个类存在相同的属性和行为时,重复去写相同的代码其实是没有必要的,我们可以将这些相同的属性和行为都写在一个类中,然后需要用到这些属性和行为的类去继承该类即可。
继承的思想:可以理解为将多个子类的共性,向上抽取到父类当中,以实现代码的复用性和维护性。
继承的实现:
怎样实现类与类之间的继承关系呢?
可以通过extends关键字来实现类与类的继承。具体格式为:
class 子类名 extends 父类名{
成员变量;
成员方法;
}
父类又可以称为基类或者超类;子类又可以称为派生类。
继承的特点:
不同于C++,Java中只支持单继承即一个子类只能继承一个父类。但是Java中支持多层继承。
一个事物总是有其优点和缺点,继承也不例外。下面就来谈谈继承的好处和弊端。
继承的好处:
- 提高了代码的复用性
- 提高了代码的可维护性
- 让类和类之间产生了关系(多态的前提条件)
继承的弊端:
我们在设计软件时,模块之间遵循的一个原则是高内聚、低耦合。但是继承确实增强了类和类之间的耦合性,这是继承的一个弊端。
在使用继承的时候我们需要注意的是:
- 子类只能够继承父类的非私有成员(包括成员变量和成员方法)
- 子类不能继承父类的构造方法,但是可以通过super关键字来去访问父类的构造方法
- 我们不要为了部分功能的实现而去继承
那么我们何时使用继承是比较合适的呢?
事实上,继承体现的是一种“is a”关系即什么是什么。
当我们发现一个类是另一个类的一种的时候,这个时候就可以使用继承啦。
像上述例子,Cat是Animal的一种,那么我们就可以使用继承。
继承中成员变量之间的关系
在使用继承的时候,父类和子类中都有成员变量,那么在访问成员变量的时候可能会存在一些问题,下面分情况来探讨一下:
当子类和父类的成员变量名称不一样时,我们就正常进行访问,不会出现问题。
当子类和父类的成员变量名称一样时,在子类中访问变量的顺序如下(遵循就近原则):
- 在子类的局部范围找,如果有就使用
结果:
- 在子类的成员范围找,如果有就使用
结果:
- 在父类的成员范围找,如果有就使用
结果:
- 如果没有找到,则报错
继承中成员方法的关系:
继承中成员方法的关系同继承中成员变量的关系。
子类和父类的成员方法名不一样时,通过子类调用方法。
子类和父类的成员方法名一样时:
通过子类调用方法,顺序如下:
- 先查找子类中有没有该方法,如果有就使用
- 再看父类中有没有该方法,如果有就使用
- 如果没有就报错
继承中的构造方法
上面谈到子类不能继承父类的构造方法,但是可以通过super关键字来访问父类的构造方法,那么在谈继承中的构造方法之前,我们需要了解一个关键字super。
super关键字一般是用在子类局部范围访问父类的成员变量或者访问父类的构造方法的情况。那么super和this有什么区别呢?
- this代表的是本类对象的引用
- super则代表的是父类存储空间的标识,可以理解为父类的引用,可操作父类的成员
那么在什么样的情况下使用this和super呢?
- 调用成员变量
this.成员变量:表示调用的是本类的成员变量
super.成员变量:表示调用的是父类的成员变量 - 调用成员方法
this.成员方法:表示调用的是本类的成员方法
super.成员方法:表示调用的是父类的成员方法 - 调用构造方法
this(. . .):表示调用的是本类的构造方法
super(. . .):表示调用的是父类的构造方法
聊完了super关键字,下面来谈谈构造方法吧。
在创建子类的一个对象时,会调用子类的构造方法,但是子类的构造方法都会默认去访问父类的空参构造。
原因:由于子类会继承父类的一些数据,可能还会使用到父类的数据。在创建子类的对象时需要对子类的数据进行初始化,而在对子类的数据进行初始化之前,需要先对父类的数据进行初始化才可以。
在子类的构造方法里,第一条语句默认都是 super()。
不知道大家有没有这样的疑问,子类继承父类,那么父类又继承谁呢?其实,父类默认继承的是Object类,而Object类没有父类。每个类都是直接或者间接地继承Object类。
在继承中调用构造方法需要注意的是:
父类中没有空参构造,子类该如何?
- 在父类中添加一个无参的构造方法
- 子类通过super关键字去显式调用父类其他的带参的构造方法
- 子类通过this去调用本类的其他构造方法,而本类其他构造方法也必须首先访问了父类构造
super( . . .)或者this(. . .)必须出现在构造方法的第一条语句。
方法重写(Override)
何为方法重写?
子类中出现了和父类中一模一样的方法声明(方法名,参数列表,返回值类型),也被称为方法覆盖,方法复写。
方法重写的思想:子类对父类的功能实现不满意,想要覆盖,或者说扩展,那就可以使用方法重写。
那么何时需要用到方法重写呢?
当子类需要父类的功能,而功能主体子类有自己的特有内容时,可以重写父类中的方法。这样做的好处是:即沿袭了父类的功能,又定义了子类特有的内容。
在使用方法重写时,需要注意的是:
- 父类中私有方法不能被重写
父类的私有方法不能被继承,何谈重写 - 子类重写父类方法时,访问权限不能更低
重写的方法最好和父类的方法的访问权限一样 - 父类静态方法,子类也必须通过静态方法进行重写(其实这个算不上方法重写,但是现象确实如此)
子类重写父类方法的时候,最好声明一模一样。
方法重写和方法重载有何区别?
- 方法重写指的是子类继承父类的方法,且对方法实现不满意,想要重写方法,重写时,返回值类型,方法名,形参列表都不改变,变的只是方法体。
- 方法重载指的是在一个类中,有几个方法名相同的方法,但这些方法的形参个数或者形参数据类型不相同。
final关键字
为什么要有final关键字?
由于继承中有一个方法重写的现象,而有时候我们不想让子类去重写父类的方法。针对这种情况java就给我们提供了一个关键字: final。
final关键字:最终的,可以修饰类、变量和成员方法。
那么被final修饰的类、变量和成员方法有什么特点呢?
- 修饰类: 被修饰类不能被继承
- 修饰方法: 被修饰的方法不能被重写
- 修饰变量: 被修饰的变量不能被重新赋值,因为这个量其实是一个常量
当final修饰局部变量时:
- 修饰基本数据类型的变量,修饰之后,该变量的值不能被修改
- 修饰引用数据类型的变量,修饰之后,指的是地址值不能被修改
代码块
什么是代码块?
在Java中,使用{}括起来的代码被称为代码块。
分类:
根据其位置和声明的不同,可以分为局部代码块,构造代码块,静态代码块,同步代码块。
- 局部代码块:在方法中出现;限定变量生命周期,及早释放,提高内存利用率
- 构造代码块:在类中方法外出现;多个构造方法方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行
- 静态代码块:在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。