0%

Java(11)面向对象三大特性之继承性

面向对象三大特性之继承性

面向对象的三大特征:封装、继承、多态

继承是多态的前提,如果没有继承,就没有多态。

在继承的关系中,”子类就是一个父类“。也就是说,子类可以被当作父类看待。

例如父类是员工,子类是讲师,那么”讲师就是一个员工“。

Java语言是单继承的。一个类的直接父类只能由唯一一个。

但是,Java语言可以多级继承。

一个子类的直接父类是唯一的,但是一个父类可以由多个子类。

image-20211210164118381

16.1 继承的基本格式

定义父类格式:(一个普通的类定义)

1
2
3
public class 父类名称{
// ...
}

定义子类的格式:

1
2
3
public class 子类名称 extends 父类名称{
// ...
}

Employee类

1
2
3
4
5
6
7
8
package com.tipdm.demo01;

// 定义一个父类
public class Employee {
public void method(){
System.out.println("方法执行!");
}
}

Teacher类

1
2
3
4
5
package com.tipdm.demo01;

// 定义了一个员工的子类:讲师
public class Teacher extends Employee{
}

Assistant类

1
2
3
4
5
package com.tipdm.demo01;

// 定义一个子类:助教
public class Assistant extends Employee{
}

主类:

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
package com.tipdm.demo01;

/**
* 面向对象的三大特征:封装、继承、多态
* 继承是多态的前提,如果没有继承,就没有多态。
*
* 在继承的关系中,”子类就是一个父类“。也就是说,子类可以被当作父类看待。
* 例如父类是员工,子类是讲师,那么”讲师就是一个员工“。关系:is-a。
*
* 定义父类格式:(一个普通的类定义)
* public class 父类名称{
* // ...
* }
*
* 定义子类的格式:
* public class 子类名称 extends 父类名称{
* // ...
* }
*/
public class demo1 {
public static void main(String[] args) {
// 创建一个子类对象:讲师
Teacher teacher1 = new Teacher();
// Teacher类当中虽然什么都没写,但是会继承来自Employee的方法。
teacher1.method();

// 创建一个子类对象:助教
Assistant assistant1 = new Assistant();
// Assistant类当中虽然什么都没写,但是会继承来自Employee的方法。
assistant1.method();
}
}
1
2
3
4
方法执行!
方法执行!

进程已结束,退出代码0

继承后的特点

在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:

直接通过子类对象访问成员变量:

  • 等号左边是谁,就有优先谁,没有则向上找。

间接通过成员方法访问成员变量:

  • 该方法属于谁,就优先用谁,没有则向上找。

父类

1
2
3
4
5
6
7
8
9
10
11
package com.tipdm.demo02;

public class Fu {
int numFu = 10;
int num = 100;

public void methodFu(){
// 使用的是本类当中的,不会向下找子类的。
System.out.println(num);
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
package com.tipdm.demo02;

public class Zi extends Fu{
int numZi = 20;

int num = 200;

public void methodZi(){
// 因为本类当中有num,所以这里用的是本类的num
System.out.println(num);
}
}

主类:

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
package com.tipdm.demo02;

/**
* 在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
* 直接通过子类对象访问成员变量:
* 等号左边是谁,就有优先谁,没有则向上找。
* 间接通过成员方法访问成员变量:
* 该方法属于谁,就优先用谁,没有则向上找。
*/
public class demo1 {
public static void main(String[] args) {
Fu fu = new Fu(); // 创建父类对象
System.out.println(fu.numFu); // 10 只能使用父类的东西,没有任何子类的内容

System.out.println("================");
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
System.out.println("================");

System.out.println(zi.num); // 如果父类中出现同名变量,以子类为主
// System.out.println(zi.abc); // 不存在则编译报错
System.out.println("================");

// 这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi(); // 200
// 这个方法是在父类中定义的,优先使用父类中的num
zi.methodFu();
}
}
1
2
3
4
5
6
7
8
9
10
11
10
================
10
20
================
200
================
200
100

进程已结束,退出代码0

继承后变量的访问规则

父类

1
2
3
4
5
package com.tipdm.demo03;

public class Fu {
int num = 10;
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
package com.tipdm.demo03;

public class Zi extends Fu{
int num = 20;

public void method(){
int num = 30;
System.out.println(num); // 局部变量
System.out.println(this.num); // 本类的成员变量
System.out.println(super.num); // 父类的成员变量
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tipdm.demo03;

/**
* 局部变量:直接写
* 本类的成员变量:this.成员变量名
* 父类的成员变量:super.成员变量名
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();

zi.method();
}
}
1
2
3
4
5
30
20
10

进程已结束,退出代码0

继承后方法的访问规则

在父子类的继承关系中,创建子类对象,访问成员方法的规则:

  • 创建的对象是谁,就优先用谁,如果没有则向上找

注意事项:

  • 无论是成员方法还是成员变量,如果没有都是向上找父类,绝不会向下找子类的。

重写(Override)概念:在继承关系中,当方法的名称一样,参数列表也一样。

重写(Overrie): 方法的名称【一样】,参数列表【也一样】。覆盖、覆写。

重载(Overload): 方法的名称【一样】,参数列表【不一样】。

  • 方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。

父类

1
2
3
4
5
6
7
8
9
10
11
package com.tipdm.demo04;

public class Fu {
public void methodFu(){
System.out.println("父类方法执行!");
}

public void method(){
System.out.println("父类重名方法执行!");
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
package com.tipdm.demo04;

public class Zi extends Fu{
public void methodZi(){
System.out.println("子类方法执行!");
}

public void method(){
System.out.println("子类重名方法执行!");
}
}

主类:

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
package com.tipdm.demo04;

/**
* 在父子类的继承关系中,创建子类对象,访问成员方法的规则:
* 创建的对象是谁,就优先用谁,如果没有则向上找
*
* 注意事项:
* 无论是成员方法还是成员变量,如果没有都是向上找父类,绝不会向下找子类的。
* 重写(Override)
* 概念:在继承关系中,当方法的名称一样,参数列表也一样。
* 重写(Overrie): 方法的名称【一样】,参数列表【也一样】。覆盖、覆写。
* 重载(Overload): 法的名称【一样】,参数列表【不一样】。
*
* 方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();

zi.methodFu();
zi.methodZi();

// 创建的是new了子类对象,所以优先用子类方法
zi.method();
}

}
1
2
3
4
5
父类方法执行!
子类方法执行!
子类重名方法执行!

进程已结束,退出代码0

继承之重写(Override)

父类

1
2
3
4
5
6
7
package com.tipdm.demo05;

public class Fu {
public void method(){
System.out.println("父类方法!");
}
}

子类

1
2
3
4
5
6
7
8
package com.tipdm.demo05;

public class Zi extends Fu{
@Override
public void method(){
System.out.println("子类方法!");
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tipdm.demo05;

/**
* 方法覆盖重写的注意事项:
* 1. 必须保证父子类之间方法相同,参数列表也相同。
* @Override:写在方法前面,用来检验是不是有效的正确覆盖重写,如果写在方法前面没有报错说明是有效的覆盖重写。
* 这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
* 2. 子类方法的返回值必须小于等于父类的返回值范围。
* java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类
* 父类返回Object,子类返回String是正确写法。
* 父类返回String,子类返回Object是错误写法。
* 3. 子类方法的权限必须大于等于父类方法的权限修饰符。
* 小扩展提示:public > protected > (default) > private
* 备注:(default)不是关键字default,而是什么都不写,留空
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}

1
2
3
子类方法!

进程已结束,退出代码0

复用父类的方法

父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tipdm.demo06;

// 本来的老款手机
public class Phone {
public void call(){
System.out.println("打电话!");
}

public void send(){
System.out.println("发短信!");
}

public void show(){
System.out.println("显示号码");
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
package com.tipdm.demo06;

public class NewPhone extends Phone{
@Override
public void show() {
super.show(); // 把父类的show方法拿过来重复利用
// 自己子类再添加
System.out.println("显示姓名");
System.out.println("显示头像");
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tipdm.demo06;

public class demo1 {
public static void main(String[] args) {
Phone phone = new Phone();
phone.call();
phone.send();
phone.show();
System.out.println("==============");

NewPhone newPhone = new NewPhone();
newPhone.call();
newPhone.send();
newPhone.show();
}
}
1
2
3
4
5
6
7
8
9
10
11
打电话!
发短信!
显示号码
==============
打电话!
发短信!
显示号码
显示姓名
显示头像

进程已结束,退出代码0

父子类构造方法访问特点

父类

1
2
3
4
5
6
7
8
9
10
package com.tipdm.demo07;

public class Fu {
public Fu(){
System.out.println("父类无参构造方法");
}
public Fu(int num){
System.out.println("父类有参构造方法");
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
package com.tipdm.demo07;

public class Zi extends Fu{
public Zi(){
super(); // 在调用父类无参构造方法
// super(10); // 调用父类重载的构造方法
System.out.println("子类构造方法!");
}
// public void method(){
// super(); // 错误写法!只有子类构造方法,才能调用父类构造方法!。
// }
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tipdm.demo07;

/**
* 继承关系中,父子类构造方法访问特点:
* 1. 子类构造方法中,有一个默认隐含的“super()”调用,所以一定是先调用父类的构造方法,再调用子类的构造方法。
* 2. 可以通过super关键字来子类构造调用父类重载构造。
* 3. super的父类构造调用,必须是自来构造方法的第一个语句。不能一个子类构造调用多次super构造。
*
* 总结:
* 子类必须调用父类构造方法,不写则赠送super(); 写了则用写的指定的super调用,super只能有一个,还必须是第一个。
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
1
2
3
4
父类无参构造方法
子类构造方法!

进程已结束,退出代码0

super关键字的三种用法

super关键字的用法有三种:

  1. 在子类的成员方法中,访问父类的成员变量
  2. 在子类的成员方法中,访问父类的成员方法
  3. 在子类的构造方法中,访问父类的构造方法

父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tipdm.demo08;

public class Fu {
int num = 10;

public Fu(){
System.out.println("父类的无参构造方法!");
}
public Fu(int num){
System.out.println("父类的有参构造方法!");
}

public void methodFu(){
System.out.println(num);
}
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tipdm.demo08;

public class Zi extends Fu{
int num = 20;

public Zi(int num){
super(num);
}

public void methodZi(){
super.methodFu(); // 访问父类的成员方法
System.out.println(super.num); // 访问父类的成员变量
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.tipdm.demo08;

/**
* super关键字的用法有三种:
* 1. 在子类的成员方法中,访问父类的成员变量
* 2. 在子类的成员方法中,访问父类的成员方法
* 3. 在子类的构造方法中,访问父类的构造方法
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi(123);
System.out.println("============");
zi.methodZi();
}
}
1
2
3
4
5
6
父类的有参构造方法!
============
10
10

进程已结束,退出代码0

this关键字的三种用法

super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:

  1. 在本类的成员方法中,访问本类的成员变量。
  2. 在本类的成员方法中,访问本类中的另一个成员方法。
  3. 在本类的构造方法中,访问本类中的另一个构造方法。

在第三种用法当中要注意:

  • A. this(…)调用也必须是构造方法的第一个语句,唯一一个。
  • B. this和super只能一个放在构造方法的第一个语句。

父类

1
2
3
4
5
package com.tipdm.demo09;

public class Fu {
int num = 30;
}

子类

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
package com.tipdm.demo09;

public class Zi extends Fu{
int num = 20;

public Zi(){
// super(); // 错误写法
this(123); // 本类的无参构造,调用本类的有参构造
// this(12, 123); // 错误写法
}

public Zi(int n){
this(12, 13);
}

public Zi(int m, int n){}

public void method(){
int num = 10;
System.out.println(num); // 局部变量
System.out.println(this.num); // 本类中的成员变量
System.out.println(super.num); // 父类中的成员变量

this.showMessage();
}

public void showMessage(){
System.out.println("本类中的另外一个成员方法!");
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.tipdm.demo09;

/**
* super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
* 1. 在本类的成员方法中,访问本类的成员变量。
* 2. 在本类的成员方法中,访问本类中的另一个成员方法。
* 3. 在本类的构造方法中,访问本类中的另一个构造方法。
* 在第三种用法当中要注意:
* A. this(...)调用也必须是构造方法的第一个语句,唯一一个。
* B. this和super只能一个放在构造方法的第一个语句。
*/
public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
1
2
3
4
5
6
10
20
30
本类中的另外一个成员方法!

进程已结束,退出代码0

抽象方法和抽象类

image-20211210164054220

抽象父类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.tipdm.demo10;

/**
* 抽象方法:就算加上abstract关键字,然后去掉大括号,直接分号结束
* 抽象类:抽象方法所在的类,必须是抽象类才行.在class之前写上abstract即可.
*
* 如何使用抽象类和抽线方法:
* 1. 不能直接创建new抽象对象.
* 2. 必须用一个子类继承抽象类.
* 3. 子类必须覆盖重写抽象父类中所有的抽象方法.
* 覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号.
* 4. 创建子类对象进行使用
*
* 注意事项:
* 1.
*/
public abstract class Animal {
public abstract void eat();
}

子类

1
2
3
4
5
6
7
8
package com.tipdm.demo10;

public class Cat extends Animal{
@Override
public void eat() {
System.out.println("吃鱼!");
}
}

主类

1
2
3
4
5
6
7
8
package com.tipdm.demo10;

public class demo1 {
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.eat();
}
}
1
2
3
吃鱼!

进程已结束,退出代码0

抽象类的构造方法

父类

1
2
3
4
5
6
7
8
package com.tipdm.demo11;

public abstract class Fu {
public Fu(){
System.out.println("抽象父类构造方法执行!");
}
public abstract void eat();
}

子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.tipdm.demo11;

public class Zi extends Fu{

public Zi(){
// super();
System.out.println("子类构造方法执行!");
}

@Override
public void eat() {
System.out.println("吃饭饭");
}
}

主类

1
2
3
4
5
6
7
8
package com.tipdm.demo11;

public class demo1 {
public static void main(String[] args) {
Zi zi = new Zi();
zi.eat();
}
}

注意事项:

  • 一个抽象类不一定含有抽象方法, 只要保证抽象方法所在的类是抽象类即可.
  • 这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途.
1
2
public abstract class MuAbstract {
}

抽象类的继承

父类

1
2
3
4
5
6
7
8
package com.tipdm.demo12;

// 最高的抽象父类
public abstract class Animal {
public abstract void sleep();

public abstract void eat();
}

二级父类

1
2
3
4
5
6
7
8
9
10
package com.tipdm.demo12;

public abstract class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}

// public abstract void sleep(); 这个抽象方法没有重写导致子类也应该为抽象类
}

子类1

1
2
3
4
5
6
7
8
package com.tipdm.demo12;

public class Dog2ha extends Dog{
@Override
public void sleep() {
System.out.println("hehehehe~~");
}
}

子类2

1
2
3
4
5
6
7
8
package com.tipdm.demo12;

public class DogGolden extends Dog{
@Override
public void sleep() {
System.out.println("呼呼呼....");
}
}

主类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.tipdm.demo12;

public class demo {
public static void main(String[] args) {
// Animal animal = new Animal(); // 错误写法,抽象类无法直接new创建
// Dog dog = new Dog(); // 由于Dog类中仍然存在抽象方法,故Dog类也为抽象类,同样无法直接创建
Dog2ha dog2ha = new Dog2ha();
dog2ha.eat();
dog2ha.sleep();
System.out.println("================");

DogGolden dogGolden = new DogGolden();
dogGolden.eat();
dogGolden.sleep();
}
}
1
2
3
4
5
6
7
狗吃骨头
hehehehe~~
================
狗吃骨头
呼呼呼....

进程已结束,退出代码0

题目:群主发红包

群主发普通红包.某群有多名成员,群主给成员发普通红包.

  • 普通红包规则:
    1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取.
    1. 成员领取红包后,保存到成员余额中.

image-20211210164447406

User类

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
package com.tipdm.demo13;

public class User {
private String name;
private int money;

public void show(){
System.out.println("我是:" + this.name + ",我有多少钱:" + this.money);
}
public String getName() {
return name;
}

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

public int getMoney() {
return money;
}

public void setMoney(int money) {
this.money = money;
}

public User(String name, int money) {
this.name = name;
this.money = money;
}

public User() {
}
}

Manager类

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
package com.tipdm.demo13;

import java.util.ArrayList;

public class Manager extends User{
public Manager(){

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

// 发红包
public ArrayList<Integer> send(int totalMoney, int count){
// 设置一个数据用来存储发出去的红包
ArrayList<Integer> RPList = new ArrayList<>();
// 获取用户余额
int restMoney = super.getMoney();
// 判断一下,剩下的钱是否够发红包
if(totalMoney > restMoney){
System.out.println("余额不足!");
return RPList;
}
// 开始发红包
// 扣钱
super.setMoney(restMoney - totalMoney);
// 计算每个红包应该发多少钱,有可能除不尽,把余数也拿上
int RP = totalMoney / count;
int rest = totalMoney % count;
// 装红包
for (int i = 0; i < count - 1; i++) {
RPList.add(RP);
}
RPList.add(RP + rest); // 如果有余数就把剩下的钱全部放入最后一个红包
return RPList;
}
}

member类

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
package com.tipdm.demo13;

import java.util.ArrayList;
import java.util.Random;

public class Member extends User{
public Member(){

}

public Member(String name, int money) {
super(name, money);
}

public void get(ArrayList<Integer> RPList){
// 判断一下红包池中是否还有红包
if (RPList.size() == 0){
System.out.println("*红包数量不够,[" + super.getName() + "]未抢到红包!");
}else {
// 抽取一个红包编号
int index = new Random().nextInt(RPList.size());
// 获取红包,并将该红包从红包池中移除
Integer rp = RPList.remove(index);
// 将获取到的红包放入余额
int restMoney = super.getMoney();
super.setMoney(restMoney + rp);
}
}
}

主类

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
package com.tipdm.demo13;

import java.util.ArrayList;

/**
* 群主发普通红包.某群有多名成员,群主给成员发普通红包.
* 普通红包规则:
* 1. 群主的一笔金额,从群主余额中扣除,平均分成n等份,让成员领取.
* 2. 成员领取红包后,保存到成员余额中.
*/
public class demo1 {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member member1 = new Member("员工1", 0);
Member member2 = new Member("员工2", 0);
Member member3 = new Member("员工3", 0);
Member member4 = new Member("员工4", 0);
// 展示各用户余额
manager.show();
member1.show();
member2.show();
member3.show();
member4.show();
System.out.println("=========== 开始发送红包 ===========");
// 开始发红包
ArrayList<Integer> RPList = manager.send(50, 4);
member1.get(RPList);
member2.get(RPList);
member3.get(RPList);
member4.get(RPList);
System.out.println("=========== 红包发送结束 ===========");
manager.show();
member1.show();
member2.show();
member3.show();
member4.show();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
我是:群主,我有多少钱:100
我是:员工1,我有多少钱:0
我是:员工2,我有多少钱:0
我是:员工3,我有多少钱:0
我是:员工4,我有多少钱:0
=========== 开始发送红包 ===========
=========== 红包发送结束 ===========
我是:群主,我有多少钱:50
我是:员工1,我有多少钱:12
我是:员工2,我有多少钱:14
我是:员工3,我有多少钱:12
我是:员工4,我有多少钱:12

进程已结束,退出代码0
-------------本文结束感谢您的阅读-------------