javaSE学习-面向对象7-三大特性之-继承

继承

java 是面向对象的,对象与对象之间有时会有很多相同的行为以及部分不同的行为,如果两个不同的对象,要有相同的行为,写两个类会出现代码的复用,所以出现了继承

继承,是子与父的关系,子类继承父类,所以子类可以拥有父类下的成员和方法,具有跟父类相同的行为,而且子类还可以拥有属于自己独特的行为

继承可以实现代码的复用,使得子类将父类作为基础,在父类拥有的基础上添加新的功能

继承格式

使用 extends 关键字进行对类的继承

1
2
3
4
5
6
class 父类{
...
}
class 子类 extends 父类{
...
}

父类,又称基类或超类;子类,可以称为派生类

使用继承的情况

继承并不是随意使用的,一般情况下,只有子类需要父类的所有方法,才进行继承

子类对父类是“是”的关系,需要继承,如果是“像”的关系,不需要继承;在父类中不需要考虑子类的特殊性,只需要写一些通用的方法

如:中国人是人,name中国人可以继承人;大卡车像汽车,都有轮胎,但是大卡车不是汽车,所以不能继承

对于继承,可以将多个类的共有方法和属性抽取出来,定义成父类,让这些类继承,然后再各自实现自己的新成员和独有行为方法

继承的特性

  • 子类拥有父类除 private 之外的所有属性、方法;不能继承父类的构造方法,默认调用无参构造方法,或使用super显式调用
  • 只能单继承:一个子类只能继承一个父类
  • 可以多重继承:可以多个类同时继承一个父类
  • 子类中可以用自己的独特方式实现父类的方法,并且实现自己的属性和方法,对父类进行扩展

继承的两种方式

子类继承父类,在子类中会有两种方式使得子类与分类不同

  1. 子类中添加新的成员变量或成员方法
  2. 覆盖重写父类的方法

添加新的成员

如果父类中没有子类需要的方法,那么子类中可以添加自己的成员变量或成员方法

覆盖

如果父类中方法的功能不能满足子类,那子类可以覆盖父类的方法,重写方法的功能,这也叫重写;

方法名称和方法参数必须相同,使用 @Override 注解;

如果在子类重写父类的方法中,还想要调用父类的这个方法,可以使用 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
public class Father{
public void methodShow(){
System.out.println("methodShow father");
}
public void methodFather(){
System.out.println("methodFather");
}
}

class Son extends Father{
public void methodShow(){
System.out.println("methodShow son");
}
public void methodSon(){
System.out.println("methodSon");
}
}

class TestDemo{
public static void main(String[] args) {
Son son = new Son();
son.methodShow();
son.methodSon();
son.methodFather();
}
}

运行结果:

1
2
3
methodShow son
methodSon
methodFather

构造器

子类除了不能继承父类的 private 方法,还有构造器也不能继承,

虽然不能继承,但是子类必须调用父类的构造器:

  • 父类和子类中都没有构造器或者有无参构造器:子类会默认调用父类无参构造器,程序运行系统自动调用
  • 父类中没有无参构造器,只有有参构造器:
    • 给父类添加无参构造方法
    • 子类在构造器中用 super(参数) 显示调用父类有参构造器

子类继承父类后初始化的顺序

在子类继承父类后,在子类初始化之前,必须进行父类的初始化

类的初始化过程:静态代码块->构造代码块->构造方法

存在子类和父类:

父类static -> 子类static -> 父类构造代码块、成员变量 -> 父类构造器 -> 子类构造代码块、成员变量->子类构造器

  1. 加载子类,发现子类继承父类
  2. 进入父类,初始化父类的 static 部分
  3. 然后进入子类,初始化子类的 static 部分
  4. 执行 new 子类时,会先 new 父类,初始化父类的成员变量,然后调用父类构造器
  5. 接着调用子类自身的构造器

eg1

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
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 构造器");
}
}
public class TestDemo{
public static void main(String[] args) {
new Zi();
}
}

运行结果:

1
2
3
4
5
6
fu 静态代码块
zi 静态代码块
fu 构造代码块
fu 构造器
zi 构造代码块
zi 构造器
  1. 先进入 TestDemo,运行 main()
  2. 加载 Zi 类,Zi 类继承了 Fu 类,所以先初始化 Fu 类,初始化静态代码块;
  3. 加载子类,初始化静态代码块
  4. 进入子类构造器,默认先调用父类构造器,去 Fu 中初始化创建对象
  5. 进入 Fu 初始化构造代码,然后 new Fu,调用 Fu 构造器
  6. 回到子类,初始化子类 Zi 的构造代码块,然后执行 Zi 的构造器

eg2

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
32
33
34
35
36
37
38
public class Person{
Animal animalP = new Animal("person and animal");
static{
System.out.println("person static");
}
public Person(){
System.out.println("person constructor");
}
public static void main(String[] args) {
new Student();
}
}

class Student extends Person{
Animal animalS = new Animal("cat");
static{
System.out.println("student static1");
}
public Student(){
System.out.println("student constructor");
}
static{
System.out.println("student static2");
}
}

class Animal{
{
System.out.println("animal 构造代码块");
}
public Animal(){}
public Animal(String s){
System.out.println("animal:"+s);
}
static{
System.out.println("animal static");
}
}

运行结果:

1
2
3
4
5
6
7
8
9
person static
student static1
student static2
animal static
animal:person and animal
person constructor
animal:cat
student 构造代码块
student constructor

在上面的程序中,初始化顺序是这样的:

  1. 先加载 Person 类,初始化 Person 的 static 部分:“person static”

  2. 运行 main() 方法,运行子类 Student ,初始化 static 部分,按照static 在程序的先后顺序:“student static1,student static2”

  3. 进行 new Student(),但是有父类 Person 存在,所以默认先调用父类 Person 无参构造器:new Person(),在 new 之前,初始化 Person 的成员变量:animalP

  4. 初始化 animalP:进入 Animal 初始化 static:animal static;调用 new Animal(“person and animal”):“animal:person and animal”

  5. 接着父类 Person 成员变量初始化完成后,调用父类无参构造器:person constructor

  6. 父类 new 完成,回到子类 Student:初始化子类成员变量:animalS,此时 Animal 已经加载过,所以 static 部分不再执行,然后直接调用 new Animal(“cat”):“animal:cat”

  7. Student 初始化完成,调用子类无参构造器:“student constructor”

注意

记住,

在调用之前,加载类时最先初始化父类、子类中的 static 代码块!!!

在子类中无论显式使用 super() 还是系统默认调用 super() 调用父类构造方法,都会先进入父类初始化父类的构造代码块和成员变量,然后再调用父类构造器!调用完成之后,在子类中同样是先初始化子类中的构造代码块和成员变量