继承

1. 继承

1.1 继承的实现

null|697

  • 继承的概念

    • 继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,以及追加属性和方法
  • 实现继承的格式

    • 继承通过extends实现
    • 格式:class 子类 extends 父类 { }
      • 举例:class Dog extends Animal { }
  • 继承带来的好处

    • 继承可以让类与类之间产生关系,子父类关系,产生子父类后,子类则可以使用父类中非私有的成员
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
39
40
41
42
43
44
45
46
47
48
49
50
public class Fu {
private String name;
private int age;
public double money;

public Fu() {
System.out.println("父类的空参数的构造方法执行了...");
}

public Fu(String name, int age, double money) {
System.out.println("父类的带参数的构造方法执行了...");
this.name = name;
this.age = age;
this.money = money;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
...
}

/*
验证子类可以直接使用父类,非私有的内容
*/
public class Zi extends Fu{
public void show(){
//System.out.println("访问父亲的姓名:"+name); // 不能直接访问私有的属性,但是可以通过getter方法访问
System.out.println("访问父亲的姓名:"+getName()); // 直接访问父类的getName方法
System.out.println("访问父亲的钱:"+ money); // 直接访问父类的money
}
}

public class TestZi {
public static void main(String[] args) {
// 1: 创建子类对象
Zi zi = new Zi();
// 2: 面向子类对象,调用子类自身的方法,或父类的非私有的方法
zi.setName("张三");
zi.setAge(18);
zi.setMoney(100);
zi.show();

System.out.println(zi.getAge());
}
}

1.2 继承的好处和弊端

  • 继承好处

    • 提高了代码的复用性(多个类相同的成员可以放到同一个类中)
    • 提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
  • 继承弊端

    • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性
  • 继承的应用场景:

    • is..a的关系:谁是谁的一种,例如:老师和学生是人的一种,那人就是父类,学生和老师就是子类

1.3. Java中继承的特点(掌握)

  • Java中继承的特点

    1. Java中类只支持单继承,不支持多继承
      • 错误范例:class A extends B, C { }
    2. Java中类支持多层继承
      null
  • 多层继承示例代码:

    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
    public class Granddad {

    public void drink() {
    System.out.println("爷爷爱喝酒");
    }
    }

    public class Father extends Granddad {

    public void smoke() {
    System.out.println("爸爸爱抽烟");
    }
    }

    public class Mother {

    public void dance() {
    System.out.println("妈妈爱跳舞");
    }
    }
    public class Son extends Father {
    // 此时,Son类中就同时拥有drink方法以及smoke方法
    }

    class A {} //extends Object{}
    class B extends A{}
    // class C extends B , A{} // 报错
    class D extends B{}

2. 继承中的成员访问特点

2.1 继承中变量的访问特点

在子类方法中访问一个变量,采用的是就近原则

  1. 子类局部范围找
  2. 子类成员范围找
  3. 父类成员范围找
  4. 如果都没有就报错(不考虑父亲的父亲…)
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Fu {
    int num = 10;
    }

    class Zi {
    int num = 20;
    public void show(){
    int num = 30;
    System.out.println(num);
    }
    }

    public class Demo1 {
    public static void main(String[] args) {
    Zi z = new Zi();
    z.show(); // 输出show方法中的局部变量30
    }
    }

2.2 super

  • this&super关键字:

    • this:代表本类对象的引用
    • super:代表父类存储空间的标识(可以理解为父类对象引用)
  • this和super的使用分别

    • 成员变量:
      • this.成员变量 - 访问本类成员变量
      • super.成员变量 - 访问父类成员变量
    • 成员方法:
      • this.成员方法 - 访问本类成员方法
      • super.成员方法 - 访问父类成员方法
  • 构造方法:

    • this(…) - 访问本类构造方法
    • super(…) - 访问父类构造方法

2.3 继承中构造方法的访问特点

注意:子类中所有的构造方法默认都会访问父类中无参的构造方法

子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()

问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?

子类必须手动调用”可用的构造方法”,如果调自己的,需要使用 this(xxx),如果是调用父类的,需要使用 super(xxx);

1
2
3
4
1. 通过使用super关键字去显示的调用父类的带参构造方法
2. 子类通过this去调用本类的其他构造方法,本类其他构造方法再通过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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
1.构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的
2.子类继承父类,是为了使用父类的内容,所以子类创建对象调用构造方法时,必须先调用父类的构造方法,完成父类成员的初始化动作,子类才可以使用父类的成员,super()表示调用父类的空参构造
3.子类的构造方法中如果没有手动给出super调用父类构造,编译器默认提供一个super()调用父类的空参构造
4.super调用父类构造,只能写在第一句
5.构造方法可以重载,所以:super(...):调用父类带参数的构造方法
*/
//父类
public class Fu06 {
int num;
//父空参构造
public Fu06(){
System.out.println("父空参...");
}
//父有参构造
public Fu06(int num) {
System.out.println("父有参");
this.num = num;
}
}

//子类
public class Zi06 extends Fu06{
//子空参构造
public Zi06(){
super();//调用父类空参构造,不写也有
//super();//错误: 只能调用一次
System.out.println("子空参...");
//super();//错误: 必须写在第一行,先调用父类空参构造
}
//子有参构造
public Zi06(int num) {
/*
子类所有构造方法中,
只要没有手动给出super调用父类构造,
JVM会隐藏提供super()调用父类空参构造
*/
//super();
super(num);
System.out.println("子有参...");
}
}
//测试类
public class Demo06Extends {
public static void main(String[] args) {
//空参构造创建对象
Zi06 zi = new Zi06();

//有参构造创建对象
Zi06 zi2 = new Zi06(100);
System.out.println(zi2.num);
}
}

1747370860912

2.4 继承中成员方法的访问特点(掌握)

定义一个父类,代码如下

1
2
3
4
5
6
7
public class F {
String name = "父类名字";

public void print1(){
System.out.println("==父类的print1方法执行==");
}
}

再定义一个子类,代码如下。有一个同名的name成员变量,有一个同名的print1成员方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Z extends F {
String name = "子类名称";

public void showName(){
String name = "局部名称";
System.out.println(name); // 局部名称
}

@Override
public void print1(){
System.out.println("==子类的print1方法执行了=");
}

public void showMethod(){
print1(); // 子类的
}
}

接下来写一个测试类,观察运行结果,我们发现都是调用的子类变量、子类方法。

1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
// 目标:掌握子类中访问其他成员的特点:就近原则。
Z z = new Z();
z.showName();
z.showMethod();
}
}
  • 如果子类和父类出现同名变量或者方法,优先使用子类的
  • 此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Z extends F {
String name = "子类名称";

public void showName(){
String name = "局部名称";
System.out.println(name); // 局部名称
System.out.println(this.name); // 子类成员变量
System.out.println(super.name); // 父类的成员变量
}

@Override
public void print1(){
System.out.println("==子类的print1方法执行了=");
}

public void showMethod(){
print1(); // 子类的
super.print1(); // 父类的
}
}

2.5 super内存图

  • 对象在堆内存中,会单独存在一块super区域,用来存放父类的数据

    01_super内存图

1747368575472

new子 会在子类内存中开辟一块super

不同对象new出来的super不影响 是在new子的堆里面

2.6 方法重写

当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。

  • 1、方法重写概念
    • 子类出现了和父类中一模一样的方法声明(方法名一样,参数列表也必须一样)
  • 2、方法重写的应用场景
    • 当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容
  • 3、Override注解
    • 用来检测当前的方法,是否是重写的方法,起到【校验】的作用

重写后,方法的访问遵循就近原则

写一个A类作为父类,定义两个方法print1和print2

1
2
3
4
5
6
7
8
public class A {
public void print1(){
System.out.println("111");
}
public void print2(int a, int b){
System.out.println("111111");
}
}

再写一个B类作为A类的子类,重写print1和print2方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class B extends A{
// 方法重写
@Override // 安全,可读性好
public void print1(){
System.out.println("666");
}

// 方法重写
@Override
public void print2(int a, int b){
System.out.println("666666");
}
}

接下来,在测试类中创建B类对象,调用方法

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
B b = new B();
b.print1();
b.print2(2, 3);
}
}

执行代码,我们发现真正执行的是B类中的print1和print2方法

2.7 方法重写的注意事项

  • 方法重写的注意事项
  1. 私有方法不能被重写(父类私有成员子类是不能继承的)
  2. 子类方法访问权限不能更低(public > 默认 > 私有)
  3. 静态方法不能被重写,如果子类也有相同的方法,并不是重写的父类的方法
  • 示例代码
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
public class Fu {
private void show() {
System.out.println("Fu中show()方法被调用");
}

void method() {
System.out.println("Fu中method()方法被调用");
}
}

public class Zi extends Fu {

/* 编译【出错】,子类不能重写父类私有的方法*/
@Override
private void show() {
System.out.println("Zi中show()方法被调用");
}

/* 编译【出错】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
private void method() {
System.out.println("Zi中method()方法被调用");
}

/* 编译【通过】,子类重写父类方法的时候,访问权限需要大于等于父类 */
@Override
public void method() {
System.out.println("Zi中method()方法被调用");
}
}
1
2
3
4
5
6
7
8
9
10
11
12

public class Zi extends Fu{
    int a = 1;
   
    public void show(){
        int a = 2;
        System.out.println(a);// 2
        System.out.println(this.a);// 1
        System.out.println(super.a);
        // 3  绕过子类,直接找父类的成员变量,前提是父类的成员 变量不能私有
    }
}

总结一下

1
2
3
4
5
6
7
8
9
10
11
12
13
访问本类成员:
this.成员变量 //访问本类成员变量
this.成员方法 //调用本类成员方法
this() //调用本类空参数构造器
this(参数) //调用本类有参数构造器

访问父类成员:
super.成员变量 //访问父类成员变量
super.成员方法 //调用父类成员方法
super() //调用父类空参数构造器
super(参数) //调用父类有参数构造器

注意:this和super访问构造方法,只能用到构造方法的第一句,否则会报错。

2.8权限修饰符

null在本类中可以访问到哪些权限修饰的方法。

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
public class Fu {
// 1、私有:只能在本类中访问
private void privateMethod(){
System.out.println("==private==");
}

// 2、缺省:本类,同一个包下的类
void method(){
System.out.println("==缺省==");
}

// 3、protected: 本类,同一个包下的类,任意包下的子类
protected void protectedMethod(){
System.out.println("==protected==");
}

// 4、public: 本类,同一个包下的类,任意包下的子类,任意包下的任意类
public void publicMethod(){
System.out.println("==public==");
}

public void test(){
//在本类中,所有权限都可以被访问到
privateMethod(); //正确
method(); //正确
protectedMethod(); //正确
publicMethod(); //正确
}
}

接下来,在和Fu类同一个包下,创建一个测试类Demo,演示同一个包下可以访问到哪些权限修饰的方法。

1
2
3
4
5
6
7
8
9
public class Demo {
public static void main(String[] args) {
Fu f = new Fu();
// f.privateMethod(); //私有方法无法使用
f.method();
f.protectedMethod();
f.publicMethod();
}
}

接下来,在另一个包下创建一个Fu类的子类,演示不同包下的子类中可以访问哪些权限修饰的方法。

1
2
3
4
5
6
7
8
9
public class Zi extends Fu {
//在不同包下的子类中,只能访问到public、protected修饰的方法
public void test(){
// privateMethod(); // 报错
// method(); // 报错
protectedMethod(); //正确
publicMethod(); //正确
}
}

接下来,在和Fu类不同的包下,创建一个测试类Demo2,演示一下不同包的无关类,能访问到哪些权限修饰的方法;

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo2 {
public static void main(String[] args) {
Fu f = new Fu();
// f.privateMethod(); // 报错
// f.method(); //报错
// f.protecedMethod();//报错
f.publicMethod(); //正确

Zi zi = new Zi();
// zi.protectedMethod();
}
}

一般情况下

//成员变量私有
//方法公开

2.8 继承综合案例

1
2
3
4
假如我们要定义如下类: 讲师类,班主任类和就业指导类,分析如下:
1. 讲师类 属性:姓名,年龄,工资 行为:展示信息,讲课
2. 班主任类 属性:姓名,年龄,工资 行为:展示信息,管理班级
3. 就业指导类 属性:姓名,年龄,工资 行为:吃饭,展示信息,辅导就业
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
1.定义父类员工Employee类
(1)成员变量(必须使用private修饰): 姓名,年龄,薪资
(2)成员方法: 展示信息,工作方法
(3)所有成员变量private修饰,提供空参/满参/set/get方法
*/
public class Employee {
//(1)成员变量(必须使用private修饰): 姓名,年龄,薪资
private String name;//姓名
private int age;//年龄
private int salary;//薪资

//(2)成员方法: 展示信息
public void showInfo() {
System.out.println("姓名: " + name +
", 年龄: " + age + ", 薪资: " + salary);
}

//(2)成员方法: 工作方法
public void work() {
System.out.println("员工: " + name + ", 正在努力的工作~");
}

//(3)所有成员变量private修饰,提供空参/满参
public Employee() {
}

public Employee(String name, int age, int salary) {
this.name = name;
this.age = age;
this.salary = salary;
}

//(3)所有成员变量private修饰,提供set/get方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public int getSalary() {
return salary;
}

public void setSalary(int salary) {
this.salary = salary;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
2.定义子类讲师Teacher类 继承 父类员工Employee类
(1)根据父类生成空参/满参构造方法
(2)覆盖重写父类work方法: 指定讲师讲课功能
*/
public class Teacher extends Employee {
//(1)根据父类生成空参/满参构造方法
public Teacher() {
//super();
}

public Teacher(String name, int age, int salary) {
super(name, age, salary);
}
//(2)覆盖重写父类work方法: 指定讲师讲课功能
@Override
public void work() {
System.out.println("讲师: " + getName() + ", 正在努力的讲解面向对象(OOP)编程~");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
3.定义子类班主任Manager类 继承 父类员工Employee类
(1)根据父类生成空参/满参构造方法
(2)覆盖重写父类work方法: 指定班主任辅导班级功能
*/
public class Manager extends Employee {
//(1)根据父类生成空参/满参构造方法
public Manager() {
}

public Manager(String name, int age, int salary) {
super(name, age, salary);
}

//(2)覆盖重写父类work方法: 指定班主任辅导班级功能

@Override
public void work() {
System.out.println("班主任: " + getName() + ", 正在严格的管理班级~");
}
}
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
/*
继承综合案例--员工类
实现步骤:
1.定义父类员工Employee类
(1)成员变量(必须使用private修饰): 姓名,年龄,薪资
(2)成员方法: 展示信息,工作方法
(3)所有成员变量private修饰,提供空参/满参/set/get方法

2.定义子类讲师Teacher类 继承 父类员工Employee类
(1)根据父类生成空参/满参构造方法
(2)覆盖重写父类work方法: 指定讲师讲课功能

3.定义子类班主任Manager类 继承 父类员工Employee类
(1)根据父类生成空参/满参构造方法
(2)覆盖重写父类work方法: 指定班主任辅导班级功能

*/
public class Demo06ExtendsTest {
public static void main(String[] args) {
//创建子类对象: 调用空参构造方法
Teacher t = new Teacher();
//调用set方法给成员变量赋值
t.setName("响哥哥");
t.setAge(18);
t.setSalary(66666);

//调用成员方法
t.showInfo();
t.work();

//创建子类对象: 调用满参构造方法
Manager m = new Manager("苗苗姐", 16, 88888);
//调用成员方法
m.showInfo();
m.work();
}
}

3.抽象类

3.1抽象类的概述

当我们在做子类共性功能抽取时,有些方法在父类中并没有具体的体现,这个时候就需要抽象类

在Java中,一个没有方法体的方法应该定义为抽象方法,而类中如果有抽象方法,该类必须定义为抽象类

3.2抽象类的特点(记忆)

  • 抽象和抽象方法必须使用 abstract 关键字修饰

    1
    2
    3
    4
    5
    //抽象类的定义
    public abstract class 类名 {}

    //抽象方法的定义
    public abstract void eat();
  • 抽象类中不一定有抽象方法有抽象方法的类一定是抽象类

  • 抽象类不能实例化*(抽象类不能直接创建对象,只能被继承使用)

不能直接造出一个 “动物”,因为它没有具体形态、没有具体行为;
你只能造出猫、狗这种具体的动物。
这就是 抽象类不能实例化 的意思。

  • 抽象类可以有构造方法

  • 抽象类的子类

    ​ 要么重写抽象类中的所有抽象方法
    ​ 要么是抽象类

    //抽象方法不能有方法体

综上所述

1
2
3
1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。

2.不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。

3.3抽象类的案例(应用)

  • 案例需求

    ​ 定义猫类(Cat)和狗类(Dog)
    ​ 猫类成员方法:eat(猫吃鱼)drink(喝水…)
    ​ 狗类成员方法:eat(狗吃肉)drink(喝水…)

  • 实现步骤

    1. 猫类和狗类中存在共性内容,应向上抽取出一个动物类(Animal)
    2. 父类Animal中,无法将 eat 方法具体实现描述清楚,所以定义为抽象方法
    3. 抽象方法需要存活在抽象类中,将Animal定义为抽象类
    4. 让 Cat 和 Dog 分别继承 Animal,重写eat方法
    5. 测试类中创建 Cat 和 Dog 对象,调用方法测试
  • 代码实现

    • 动物类
    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
    /*
    所有的动物都有名字和年龄 并且一定具备 吃东西的能力,但是不同的动物吃的具体内容不同
    */
    public abstract class Animal {
    private String name;
    private int age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public Animal() {
    }

    public Animal(String name, int age) {
    this.name = name;
    this.age = age;
    }

    // 描述所有的动物都一定具备 吃东西的能力,但是不同的动物吃的具体内容不同
    public abstract void eat();
    }
    • 猫类
    1
    2
    3
    4
    5
    public class Cat extends Animal{
    public void eat() {
    System.out.println(getAge()+"岁的猫"+getName()+"正在吃狗...");
    }
    }
    • 狗类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class Dog extends Animal{
    public Dog() {
    }

    public Dog(String name, int age) {
    super(name, age);
    }

    @Override
    public void eat() {
    System.out.println(getAge()+"岁的狗"+getName()+"正在吃骨头...");
    }
    }
    • 测试类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Test {
    public static void main(String[] args) {
    // 创建猫狗对象
    Dog dog = new Dog("旺财",3);
    dog.eat();
    Cat cat = new Cat();
    cat.setName("花花");
    cat.setAge(2);
    cat.eat();
    }
    }

3.4 抽象类综合案例

  • 案例需求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    智晟科技有很多员工(Employee),按照工作内容不同分教研部员工(Teacher)和行政部员工(AdminStaff)
    教研部根据教学的方式不同又分为讲师(Lecturer)和助教(Tutor)
    行政部根据负责事项不同,有分为维护专员(Maintainer),采购专员(Buyer)
    公司的每一个员工都编号,姓名和其负责的工作
    工作内容:
    讲师: 工号为 666 的讲师 乔峰 在讲课
    助教: 工号为 668的助教 段誉 在帮助学生解决问题
    维护专员: 工号为 686 的维护专员 柳岩 在解决不能共享屏幕问题
    采购专员: 工号为 888 的采购专员 景甜 在采购音响设备
    提示:赋值可以用set方法或者构造方法
  • 实现步骤

    1
    2
    3
    4
    1.抽取共有的id name work()方法,形成员工类父类
    2.教研部类和行政部类继承员工类
    3.定义讲师类和助教类继承教研部类,重写work方法
    4.定义维护类和采购类,继承行政类,重写work方法
  • 代码实现

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
//员工类
public abstract class Employee {
private String id;
private String name;
public abstract void work();

public Employee() {
super();
}
public Employee(String id, String name) {
super();
this.id = id;
this.name = name;
}

public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1
2
3
4
5
6
7
8
9
//教研部类
public abstract class Teacher extends Employee{
public Teacher() {
}

public Teacher(String id, String name) {
super(id, name);
}
}
1
2
3
4
5
6
7
8
9
//行政部类
public abstract class AdminStaff extends Employee{
public AdminStaff() {
}

public AdminStaff(String id, String name) {
super(id, name);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//讲师类
public class Lecturer extends Teacher{
//提供无参和带参的构造方法
public Lecturer() {
super();
}
public Lecturer(String id, String name) {
super(id, name);
}

//实现抽象方法: void work();
//输出格式: 工号为 666 的讲师 乔峰 在讲课
public void work() {
System.out.println("工号为 "+getId()+" 的讲师 "+getName()+" 在讲课");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//助教类
public class Tutor extends Teacher{
//提供无参和带参的构造方法
public Tutor() {
super();
}

public Tutor(String id, String name) {
super(id, name);
}
//实现抽象方法: void work();
//输出格式: 工号为 668的助教 段誉 在帮助学生解决问题
public void work() {
System.out.println("工号为 "+getId()+"的助教 "+getName()+" 在帮助学生解决问题");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//维护人员类
public class Maintainer extends AdminStaff{
//提供无参和带参的构造方法
public Maintainer() {
super();
}

public Maintainer(String id, String name) {
super(id, name);
}
//实现抽象方法: void work();
//输出格式: 工号为 686 的维护专员 柳岩 在解决不能共享屏幕问题
public void work() {
System.out.println("工号为 "+getId()+" 的维护专员 "+getName()+" 在解决不能共享屏幕问题");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//采购人员类
public class Buyer extends AdminStaff{
//提供无参和带参的构造方法
public Buyer() {
super();
}

public Buyer(String id, String name) {
super(id, name);
}
//实现抽象方法: void work();
//输出格式: 工号为 888 的采购专员 景甜 在采购音响设备
public void work() {
System.out.println("工号为 "+getId()+" 的采购专员 "+getName()+" 在采购音响设备");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//测试类
public class Test {
public static void main(String[] args) {
// a)创建讲师对象 l, 把工号赋值为666,姓名赋值为”傅红雪”
Lecturer l = new Lecturer("666", "乔峰");
// b)调用讲师对象l的工作方法
l.work();
// c)创建助教对象 t, 把工号赋值为668,姓名赋值为”顾棋”
Tutor t = new Tutor("668", "段誉");
// d)调用助教对象t的工作方法
t.work();
// e)创建维护专员对象 m, 把工号赋值为686,姓名赋值为”庖丁”
Maintainer m = new Maintainer("686", "柳岩");
// f)调用维护专员对象m的工作方法
m.work();
// g)创建采购专员对象 b, 把工号赋值为888,姓名赋值为”景甜”
Buyer b = new Buyer("888", "景甜");
// h)调用采购专员对象b的工作方法
b.work();
}
}

3.5 模板设计模式

学习一种利用抽象类实现的一种设计模式。先解释下一什么是设计模式?

设计模式是解决某一类问题的最优方案

那模板方法设计模式解决什么问题呢?模板方法模式主要解决方法中存在重复代码的问题

比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。

null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
模版设计模式
设计一个用于写作文的模版,让别人直接使用即可,不能修改我的模版
当前这个类就是一个模版类
*/
public abstract class MoBan {
public final void WriteComposition(){
System.out.println("我的爸爸:");
this.context(); //正文填充
System.out.println("啊~~~这就是我的爸爸!");
}

public abstract void context();
}

1
2
3
4
5
6
public class Tom extends MoBan{
@Override
public void context() {
System.out.println("记忆中,那是一个秋天,风儿那么缠绵~爸爸骑车接我放学,我的脚卡在了车链中,爸爸蹬不动,于是,他就站起来蹬......");
}
}
1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Tom tom = new Tom();
tom.WriteComposition();
}
}

综上所述:模板方法模式解决了多个子类中有相同代码的问题。

1
2
3
1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。
2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。
3步:子类继承抽象类,只需要父类抽象方法就可以了。

//接口和抽象类都不能创建对象

4. final关键字

4.1 final的概述

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
final关键字: 代表最终的,不可改变
可以修饰的内容:

1.类:
不可以被继承,不能够有子类
看: MyString和MyArrayList
称之为太监类: 不能有子类,但是有父类
2.方法:
不能被子类覆盖重写
3: 变量
只能赋值一次,不可以进行第二次赋值,变量的值是不可以发生改变的 常量
- 基本数据类型
- 数据值不能改变
- 引用数据类型
- 地址值不能改变 内容可变

- 如果是局部变量,只需要保证在方法内只赋值1次即可
- 如果是成员变量,必须保证在对象创建成功之后,给所有的final变量完成一次赋值;

成员变量: 看MyClass03 认为默认值无效,要么显式赋值,要么构造方法中赋值
(1)定义未赋值:
所有构造方法中,必须完成对final修饰的变量的赋值
所有成员方法中,不能修改final修饰的变量的值

(2)定义并赋值:
所有构造方法/成员方法中,不能修改final修饰的变量的值

4.2 final修饰的类的特点

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
final修饰的类的特点
该类不可以被继承,不能有子类
//父类
public final class Fu01 extends Object {
public void method() {
System.out.println("Fu01...method...");
}
public void show() {
System.out.println("Fu01...show...");
}
}
/*
子类: Fu01被final修饰以后,不能被继承,不能有子类
没有子类,但是有父类
*/
public class Zi01 /*extends Fu01*/ {
}

//String类被final修饰,不能有子类
public class MyString /*extends String*/ {
}
//ArrayList集合没有被final修饰,可以被继承
public class MyArrayList extends ArrayList {
public MyArrayList(int initialCapacity) {
super(initialCapacity);
}

public MyArrayList() {
}

public MyArrayList(Collection c) {
super(c);
}
}

4.3 final修饰的方法的特点

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
final修饰的方法: 不能被子类覆盖重写

//父类
public class Fu02 {
public final void method() {
System.out.println("Fu02...method...");
}

public void show() {
System.out.println("Fu02...show...");
}
}
//子类
public class Zi02 extends Fu02 {
/*
//父类method方法被final修饰,子类不能进行覆盖重写
@Override
public void method() {
System.out.println("Zi02...method...");
}*/

//子类如果可以重写父类的方法,
//子类重写后的方法可以添加final关键字
@Override
public final void show() {
System.out.println("Zi02...show...");
}
}
//测试类
public class Demo02FinalMethod {
public static void main(String[] args) {
//创建子类对象
Zi02 zi02 = new Zi02();

zi02.method();
zi02.show();
}
}

4.4 final修饰的变量的特点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final修饰变量:   
1.特点: 只能赋值一次,不可以进行第二次赋值,变量的值是不可以发生改变的 常量
2.修饰局部变量(方法内部定义的变量):
(1)基本类型: 基本类型变量中存储的具体的数字是不可以被改变的

(2)引用类型: 存储的对象的地址值,被final修饰后,说明变量存储的对象的地址值是不可以被改变的
但是该地址代表的内存空间中的`内容`是可以改变的

3.成员变量: 看MyClass03 认为默认值无效,要么显式赋值,要么构造方法中赋值
(1)定义未赋值:
所有构造方法中,必须完成对final修饰的变量的赋值
所有成员方法中,不能修改final修饰的变量的值

(2)定义并赋值:
所有构造方法/成员方法中,不能修改final修饰的变量的值
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//final修饰局部变量
public class Demo03FinalVar {
public static void main(String[] args) {
final int num = 10;//定义变量,并第一次赋值
System.out.println(num);//10
//num = 20;//错误: 不能进行第二次赋值(不能修改变量中的值)

final int num2;//定义变量,未赋值
num2 = 100;//正确: 第一次赋值
System.out.println(num2);//100

//num2 = 1000;//错误: 不能进行第二次赋值(不能修改变量中的值)

//创建Student类的对象
final Student stu = new Student("张三", 18);
stu.show();

//stu被final修饰,存储的地址值是不可以发生改变的
//stu = new Student("李四",28);

stu.setName("李四");
stu.setAge(28);

stu.show();

//数组名保存地址值,被final修饰后,地址值不可以被改变
final int[] arr = new int[]{10,20,30};
System.out.println(Arrays.toString(arr));

//arr = new int[]{100,200,300};

//把每个元素扩大10被
for (int i = 0; i < arr.length; i++) {
arr[i] *= 10;
}
System.out.println(Arrays.toString(arr));
}
}
//final修饰成员变量
public class Demo03FinalVar2 {
String name;
//定义未赋值
final int num;
//定义并赋值
final int num2 = 20;
//空参构造
public Demo03FinalVar2() {
num = 10;
//错误: 第二次赋值
//num2 = 200;
}
//满参构造
public Demo03FinalVar2(String name) {
this.name = name;
num = 10;
//错误: 第二次赋值
//num2 = 200;
}
//成员方法
public void show() {
//num = 100;
//错误: 第二次赋值
//num2 = 200;
}
}

final可变地址中的内容

image-20260320161130828

5. Object类

5.1 Object类介绍

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
1.java.lang.Object类的介绍:	
是整个类的体系结构中的最顶层父类,是所有类的最终父类,它里面定义的方法,其它类都有.
类Object是类层次结构的根类。每个类都使用Object作为超类。
所有对象(包括数组)都实现这个类的方法。
2.常用方法:
(1)public String toString(): 返回调用方法的对象的字符串表示形式
字符串形式: 理解位返回对象的地址值,类的全名称 + @ + 16进制的int数字
内部源代码分析:
public String toString() {
return getClass().getName() + "@"
+ Integer.toHexString(hashCode());
}
getClass().getName(): 获取类的全名称
Integer.toHexString(hashCode()): 把对象的哈希值转换成16进制的int数字
(2)public boolean equals(Object obj): 比较调用方法的对象和方法参数对象是否相等
true: 说明相等
false: 说明不相等
默认比较对象的地址值,然而只要new对象的地址值就是不相同
内部源代码分析:
public boolean equals(Object obj) {
return (this == obj);
}
this代表: 调用方法的对象
obj代表: 调用方法时传递的参数对象
==: 在比较两个对象的内存地址值是否相同
(3)重写toString方法
目的: 返回对象的内容,而不是地址值
快捷键: alt + insert --> toString --> 选择成员变量 --> ok
(4)重写equals方法:
目的:比较对象的内容,而不是地址值
快捷键: alt + insert --> equals && hashCode() --> 选择成员变量 --> ok

5.2 Object类的代码演示

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
public class Demo01Object {
public static void main(String[] args) {
//创建Object类的对象
Object obj1 = new Object();
//调用toString方法
String s1 = obj1.toString();
System.out.println(s1);//内存地址值: Object@1540e19d

//对象调用toString方法获取字符串,然后输出字符串
System.out.println(obj1.toString());//内存地址值:Object@1540e19d
//println方法参数传递引用变量: println内部会使用引用变量调用toString方法
System.out.println(obj1);//内存地址值:Object@1540e19d

Object obj2 = new Object();
/*
==的使用:
1.基本类型: 比较具体的数据值是否相同
2.引用类型: 比较的是内存地址值是否相同
注意:
只要使用new创建对象,内存地址和谁都不一样
*/
System.out.println(obj1 == obj2);//false
/*
equals方法内部原理:
默认使用==比较对象的内存地址值
然而obj1和obj2都是new的,地址都不一样
*/
System.out.println(obj1.equals(obj2));//false
}
}

/*
toString方法源码:
obj1.getClass().getName(): 获取当前对象所属类的全名称 后面反射中讲解
Integer.toHexString(obj1.hashCode()): 根据对象获取一个int数字并转换成16进制 后面讲
*/
System.out.println(obj1.getClass().getName() + "@" + Integer.toHexString(obj1.hashCode()));
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
39
40
41
42
public class Student extends Object {
private String name;
private int age;
//覆盖重写toString方法
//目的: 返回对象的内容,而不再返回地址值
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/*
覆盖重写equals方法
目的: 比较对象的内容,而不再比较对象的地址值
认为: 姓名相同 同时 年龄也相同,这两个对象就是相同的
*/
@Override
public boolean equals(Object o) {
//this == o: 调用方法的对象和参数对象是同一个对象,直接返回true
//自己和自己比,内容必然相同,直接返回true
if (this == o) return true;
/*
o == null: 如果参数对象是null,直接返回false
||: 或者
getClass() != o.getClass(): 反射讲,调用方法的对象和参数对象不是同一个类的对象,直接返回false
*/
//if (o == null || getClass() != o.getClass()) return false;
//和下面的写法是等价的
if (o == null || !(o instanceof Student)) return false;
//向下转型: 把参数对象,强转成Student对象
Student student = (Student) o;
/*
age == student.age: 判断两个对象的年龄是否相同
&&: 并且
Objects.equals(name, student.name): 调用工具类Objects静态方法equals比较两个字符串是否相同
*/
return age == student.age &&
Objects.equals(name, student.name);
}
//空参/满参,set/get方法 自己生成
}
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 Demo01ToStringAndEquals {
public static void main(String[] args) {
Student stu1 = new Student("张三", 18);
/*
Student类没有覆盖重写Object类的toString方法
结果: zhisheng01_object.Student@1540e19d

Student类覆盖重写Object类的toString方法
结果: Student{name='张三', age=18}
*/
System.out.println(stu1.toString());
System.out.println(stu1);

Student stu2 = new Student("李四", 38);
Student stu3 = new Student("张三", 18);
/*
Student类没有覆盖重写Object类的equals方法
结果: false,false,false
Student类覆盖重写Object类的equals方法
结果: false,true,false
*/
System.out.println(stu1.equals(stu2));
System.out.println(stu1.equals(stu3));
System.out.println(stu2.equals(stu3));
}
}
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@Override
public boolean equals(Object o) {
// 比地址 地址一样,直接返回true
if (this == o) return true;
// 比类型 类型不一样,直接返回false
if (o == null || getClass() != o.getClass()) return false;

// 比内容 逐个属性比较,任意一个不一样,都会返回false
Student student = (Student) o;
if (age != student.age) return false;
return Objects.equals(name, student.name);
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}

public class Test {
public static void main(String[] args) {
Student student = new Student("张三",18);
System.out.println(student); // println 方法会自动调用 student 对象的 toString,由于我们自定义的类中,没有写 tiString,因此最终实际执行的是 Object类的toString方法
// 如果对 Object;类的toString方法返回的结果不满意,我们只需要重写它即可
System.out.println(student.toString());
Student student2 = new Student("李四",18);
System.out.println(student2);

System.out.println("--------------------");
Student student3 = new Student("李四",18);
System.out.println(student3);
System.out.println(student2.equals(student3));// 如果子类没有重写equals方法,会得到false,因为Object类的equals方法,仅仅比较的是两个对象的地址值,不比较内容,如果想比较内容,需要重写它

}
}

public class TestA {
public static void main(String[] args) {
// 1: 创建一个对象,并多次调用 hashcode方法
A a1 = new A("aaa",123);
System.out.println(a1.hashCode());
System.out.println(a1.hashCode());
System.out.println(a1.hashCode());
A a2 = new A("aaa",123);
System.out.println(a1.equals(a2));
System.out.println(a2.hashCode());

System.out.println("------------------------");
String s1 = "重地";
String s2 = "通话";
System.out.println(s1.equals(s2));
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());

String s3 = new String("abc");
String s4 = "abc";
System.out.println("----------------------");
System.out.println(s3.equals(s4));
System.out.println(s3.hashCode());
System.out.println(s4.hashCode());
}
}

5.3 输出语句是Object对象的原理分析

1669605996363