Java面向对象(三) 继承和final实例
继承
概述
- 继承是面向对象思想的三大特性之一,使类与类之间产生
特殊 - 一般
的关系,即is-a
关系。 - 继承是
从已有类中派生出新的类
,新的类能吸收已有类的属性和方法
,并且能拓展新的属性和行为
。 - 在Java中使用
extends
关键字表示继承,语法表示为:class 子类 extends 父类{}
- 子类被称为
派生类
,父类又被称为超类
。 - 子类继承父类,表名
子类是一种特殊的父类
,子类拥有父类的属性和方法,并且子类可以拓展具有父类所没有的一些属性和方法。 - 子类即是不扩展父类,也能维持拥有父类的操作。
优缺点
- 继承的好处
- 提高了代码的复用性
- 提高了代码的维护性
- 让类与类之间产生了关系,是多态的前提
- 缺点
- 增加了耦合性
- OOP思想开发原则:高内聚,低耦合
- 耦合:类与类之间的关系
- 内聚:自身完成事情的能力
Java继承特点
Java
只支持单继承
,不支持多重继承
操作(extends A,B,C..)1
2
3class A {}
class B {}
class C extends A,B {} // 错误的,一个子类继承了两个父类,Java中不允许为什么只支持单继承?
多继承
会存在安全隐患
,因为当继承的多个类都存在相同的属性
或方法名相同方法体不同的方法
,子类进行调用时,就会产生不知道该调用哪一个类中的方法的情况。
Java支持
多层继承
(继承体系)1
2
3class A {}
class B extends A {}
class C extends B {}如果想用这个继承体系的所有功能,那么就实用对底层的子类创建的对象
如果想看这个体系的共性功能,那么就看最顶层的类的功能
继承注意点
成员变量和方法
- 子类
只能
继承父类的所有非私有
的成员变量和方法。可以
继承public protected
修饰的成员,不可以
继承private
修饰的。 - 但是可以通过父类中提供的
public 的setter和getter方法
进行间接
的访问和操作
private 的属性 - 对于子类可以继承父类中的成员变量和成员方法,如果
子类
中出现
了和父类同名的成员变量和成员方法
时,父类
的成员变量会被隐藏
,父类的成员方法会被覆盖
。需要使用父类的成员变量和方法时,就需要使用super
关键字来进行引用
。- 隐藏是针对成员变量和静态方法,覆盖是针对普通方法。
- 子类
构造器
子类
不能继承
父类的构造方法
,但是可以通过super
关键字来访问
父类构造方法。super 和 this 的调用都必须是在第一句,否则会产生编译错误,this和super只能存在一个。
不能进行
递归构造器调用
,即多个构造器之间互相循环调用。如果
父类有无参构造
时,所有
构造方法(包含任意有参构造)自动默认
都会访问父类中的空参构造方法
。(自带super();
)- 因为继承的目的是子类获取和使用父类的属性和行为,所以子类初始化之前,一定要先完成父类数据的初始化。
- 在Java中,每个类都会
默认继承Object超类
,所以每一个构造方法的第一条默认语句都是super()
如果
父类没有无参构造
,反而有其他的有参构造方法
时,子类继承父类后,子类必须显式的创建构造器,不论子类的构造器是否和父类构造器中参数类型是否一致,都必须在子类的构造器中显式的通过super关键字调用和父类构造器相应参数的构造方法
。否则编译都通不过。代码示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Person {
public Person(int age){
System.out.println(age);
}
}
class Student extends Person{
public Student(int age) {
super(age);
}
public Student(){
super(10); //必须调用父类的有参构造
System.out.println("子类可以创建其他类型构造器,但是必须显式的用super调用父类构造器")
}
}也可以使用
this
先调用子类中的构造方法,再间接调用父类中的有参构造方法,实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class ExtendTest1 {
public static void main(String[] args) {
new Student();
}
}
class Person {
public Person(int age){
System.out.println("父类有参构造");
}
}
class Student extends Person{
public Student(int age) {
super(age);
System.out.println("子类有参构造");
}
public Student(){
this(10); //可以使用this先调用子类中的有参构造,从而间接调用父类中的有参构造
System.out.println("子类无参构造");
}
}使用this,执行顺序结果为:先调用了子类中无参构造,此无参构造会接着调用子类中的有参构造,又接着调用父类中的有参构造,此时首先执行完毕了父类有参构造,接着子类有参构造执行完毕,最后子类无参构造才执行完毕。
1
2
3父类有参构造
子类有参构造
子类无参构造
以下这种是错误的:(因为当父类中没有无参构造器时,父类中没有这种类型的构造方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student extends Person{
public Student(String name){
super();
} //错误的,因为当父类中没有无参构造器时,父类中没有这种类型的构造方法
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
以下这种正确:(因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student extends Person{
//因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student extends Person{
//因为当父类中没有无参构造器时,子类中的构造方法的类型在父类中有
public Student(String name ,int age){
super(name,age);
}
public Student(int age) {
super(age);
}
}
class Person {
public Person(String name ,int age){
System.out.println(name+age);
}
public Person(int age){
System.out.println(age);
}
}
结论:**当父类中没有无参构造器时,子类继承父类,子类中的构造器方法类型可以和父类中的构造器不同,但是必须每个构造器都显式的使用super关键字调用父类中的某个有参构造器,也可以使用this调用子类中的某个有参构造器,但这个有参构造器必须通过super访问父类中的有参构造器。**
继承的执行顺序问题
父类和子类中
都有静态代码块和构造代码块
,示例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Test2_Extends {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}此时的执行结果:
1
2
3
4
5
6静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi执行顺序分析:
- 主类Test2_Extends先加载到内存,其中的main方法入栈执行,main方法中创建了子类对象
- 子类对象创建过程中,父类和子类都加载到内存中,并且Fu.class优先于Zi.class加载,父类中的静态域先执行后,再执行子类中的静态域,此时会第一个输出:静态代码块Fu,第二个输出:静态代码块Zi
- 创建对象时进入子类的构造器,因为Java是分层初始化的,所以会先初始化父类再初始化子类,子类构造器会自动默认先执行父类的构造器,因为构造代码块优先于构造方法执行,所以此时就会先执行父类的构造代码块后,再执行父类的构造方法。所以第三个输出:构造代码块Fu,第四个输出:构造方法Fu
- Fu类初始化结束后,子类初始化,第五个输出的是:构造代码块Zi,第六个输出:构造方法Zi
方法重写
- 重写:子父类出现一模一样的方法,但
返回值类型
可以是子父类
。 - 方法重写的应用:
- 当子类需要父类的功能,而功能主体
子类有自己的特有内容
时,可以重写父类中的方法。即沿用了父类的功能,又定义了子类特有的内容
- 当子类需要父类的功能,而功能主体
重写注意点
- 父类中的
私有方法不能被重写
- 因为父类的私有方法子类无法继承,得不到
子类重写父类方法
时,访问权限不能更低
,子类方法访问权限应该大于或等于
父类的,最好和父类中一致- 父类
静态方法
,子类也必须
通过静态方法进行覆盖,即静态只能覆盖静态
- 子类重写父类方法时,最好声明得一模一样
重写和重载
Override
和Overload
的区别?Overload
能改变返回值类型吗?Override是重写
,Overload是重载
。重载可以改变返回值类型
,它是方法名相同,参数列表不同,与返回值类型无关
。
- 方法
重写
:子类中出现和父类中方法声明一模一样的方法
。返回值类型相同(或者是子父类,多态),方法名和参数列表一模一样
- 方法
重载
:本类中出现方法名相同
,参数列表不同
的方法,和返回值类型无关,可以改变
。 - 子类对象调用方法时,
先找子类
本身的方法,再找父类
中的方法。
final实例域
- 可以将实例域定义为
final
。构造对象时必须初始化这样的域
。 - 必须确保在每一个
构造器执行之后
,这个域的值被设置初始化
,并且在后面的操作中,不能够再对它进行修改
。 - final修饰的大都应用于
基本类型域
,或不可变类的域
。- 不可变类:如果
类中的每个方法都不会改变其对象
,这种类是不可变的类。例如String
类。
- 不可变类:如果
- 对于
可变类
,使用final修饰只是
表示存储在对象变量中的对象引用不会再指向其他对象
,不过这个对象中的属性可以更改
。
final修饰特点
- 修饰
类
时,类不能被继承
。 - 修饰
变量
时,变量就变成了常量
,只能被初始化赋值一次
,后续不可进行改变。
一般与public static
配合使用。 - 修饰
方法
时,方法不能被重写
。 - final修饰局部变量时:
- 方法内部或者方法声明上
- 基本类型:值不能被改变
引用类型
:地址引用不能被改变,不会再指向其他对象,但是对象中的属性可以改变。
会报 “无法为最终变量x分配值” 的错误
final变量的初始化时机
final
修饰的成员变量必须显式的进行初始化赋值
,否则默认的是个无效值
,会报“可能尚未初始化变量xxx
”的错误。1
2
3
4
5
6
7//方式1:直接进行显式得初始化赋值,不进行赋值,会报错
Class Demo{
final int num ; //没有显式赋值,报错
final int num = 10; //进行显式得初始化赋值
public Demo(){
}
}
- 也可以在
构造方法执行完毕前
对其进行初始化赋值
1 | //方式2:在构造方法执行完毕之前进行赋值初始化 |