一、继承

1.1概述

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要 继承那一个类即可。

其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类

继承描述的是事物之间的所属关系,这种关系是: is-a 的关系。例如,图中兔子属于食草动物,食草动物属于动 物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系

定义:

继承:就是子类继承父类的属性行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接 访问父类中的非私有的属性和行为

继承主要解决的问题就是:共性抽取

好处:

1.提高代码的复用性。

  1. 类与类之间产生了关系,是多态的前提

1.3 继承的格式

通过extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:

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

具体如下:

  1. //定义一个父类:员工
  2. public class Employee {
  3. public void method(){
  4. System.out.println("方法执行");
  5. }
  6. }
  7. //定义了一个员工的子类:讲师
  8. public class Teacher extends Employee {
  9. }
  10. /*
  11. 在继承的关系中,“子类is-a父类“。
  12. */
  13. public class Demo01Extends {
  14. public static void main(String[] args) {
  15. //创建一个子类对象
  16. Teacher teacher= new Teacher();
  17. //Teacher继承了父类的method方法
  18. teacher.method(); //方法执行
  19. //创建另一个子类对象
  20. Assistant assistant=new Assistant();
  21. assistant.method();
  22. }
  23. }

1.3 继承后的特点—成员变量

如果子类和父类的变量没有重名则不影响,直接调用即可

但是如果子类和父类的变量名重名了,那么如下:

  1. package roderick.day09.demo02;
  2. /*
  3. 在父子类的继承关系当中,如果成员变量充满,则创建子类对象时,有两种访问方式:
  4. 直接->子类.成员变量
  5. 等号左边是谁就优先用谁,没有则向上找
  6. 间接->成员方法访问成员变量
  7. 该方法属于谁,就优先用谁,没有则向上找
  8. */
  9. class Fu {
  10. int num=5;
  11. public void showFu(){
  12. System.out.println("num="+num);
  13. }
  14. }
  15. class Zi extends Fu{
  16. int num=6;
  17. public void show(){
  18. //访问父类中的num
  19. System.out.println("Fu num="+num);
  20. //访问子类中的num2
  21. System.out.println("Zi num="+num);
  22. }
  23. }
  24. public class Extends {
  25. public static void main(String[] args) {
  26. //创建子类对象
  27. Zi z=new Zi();
  28. //调用子类中的show方法
  29. z.show();
  30. // Fu num = 6
  31. // Zi num = 6
  32. z.showFu();
  33. //num = 5
  34. }
  35. }

如果是 对象名.成员变量 调用则对象名是谁的就优先用谁的 如果是通过方法来调用则 方法属于谁优先用谁的

Super关键字->解决重名问题

如果父子类中出现重名变量,在子类中访问父类非私有变量的情况,可以使用super关键字,类似之前学过的this

super.父类成员变量名
class Zi extends Fu{
    //Zi中的成员变量
    int num=6;
    public void show(){
        //访问父类中的num
        System.out.println("Fu num="+ super.num);
        //访问子类中的num
        System.out.println("Zi num="+ this.num);
    }
}

小贴士:Fu 类中的成员变量是非私有的,子类中可以直接访问。若Fu 类中的成员变量私有了,子类是不能 直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员 变量呢?对!可以在父类中提供公共的getXxx方法setXxx方法

1.4 继承后的特点—成员方法

继承关系中,无论是成员方法还是成员变量,都是向上找的

成员方法重名—重写(Override)

另称—>覆盖重写(推荐)

方法重写

子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效 果,也称为重写或者复写。声明不变,重新实现

这里要和方法重载区别开来,重载是参数列表不一样

要点:@Override

代码如下

// @Override  -->安全监测覆盖重写是否正确,写在方法前面
class Fu{
    public void show(){
        System.out.println("Fu show");
    }
}

@Override
class Zi extends Fu{
    //子类重写了父类的show方法
    public void show(){
        System.out.println("Zi show");
    }
}
public class ExtendsDemo05{
    public static void main(String[] args){
        Zi z = new Zi();
        //子类中有show方法,只执行覆盖重写后的show方法
        z.show();//Zi show
    }
}

小贴士:子类方法的返回值必须小于等于父类方法的返回范围;->使用@Override可监测

注意:

子类 方法的全选必须【大于等于】父类方法的权限修饰符。

public > protected > (default) > private

PS: default 修饰符不需要写-> (default) class xxx{ }

1.5 继承后的特点—构造方法

首先我们要回忆两个事情,构造方法的定义格式和作用

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构 造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代 码如下:
class Fu{
    private int n;
    Fu(){
        System.out.println("Fu()");
        //无参构造
    }
}
class Zi extends Fu{
    Zi(){
        //super () 调用父类构造方法
        //先执行父类初始化
        super();//->不写的话默认有一个super();
        System.out.prinln("Zi()");
    }
}
public class ExtendsDemo07{
    publlic static void main(String args[]){
        Zi zi=new Zi();
    }
}
输出结果:
Fu ()
Zi ()

注意:

  • 如果父类中没有无参构造而是有参构造,则需要在子类无参构造中重载super(参数)
  • super()必须在子类构造方法的第一行

1.6 this 和 super

父类空间优先于子类对象产生

在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。

目的在于子类对象中包含了其对应的父类空间,便可以包含其父类的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。代码体现在子类的构 造方法调用时,一定先调用父类的构造方法。理解图解如下:

super 和 this的用法

1.访问成员

this.成员变量  --- 本类的
super.成员变量 --- 父类的

this.成员方法名() --- 本类的
super.成员方法名() ---父类的

2.访问构造方法

this(...) --本类的构造方法
super(...) --父类的构造方法

子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。 super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现

1.7 继承的特点

1.Java只支持单继承,不支持多继承

//一个类只能有一个父类,不能有多个
class C extends A{}  //ok
class C extends A,B... //error
  1. Java支持多层继承(继承体系)
class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类(祖先),所有的类默认继承Object

3.子类和父类是一种相对的概念;

二、抽象类

2.1 概述

父类中的方法,被它的子类们重写,子类各自的实现都不尽相同。那么父类的方法声明和方法主体,只有声明还有 意义,而方法主体则没有存在的意义了。我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法 的类就是抽象类

父类中的方法不确定如何进行{ }方法体实现,那么这就应该是一个抽象方法

  • 抽象方法:没有方法体的方法
  • 抽象类:包含抽象方法的类

2.2 abstract使用格式

抽象方法

使用 abstract 关键字修饰方法,该方法就成了抽象方法,抽象方法只包含一个方法名,而没有方法体。
定义格式

修饰符 abstract 返回值类型 方法名(参数列表);

public abstract void eat();

抽象类

如果一个类包含抽象方法,那么该类必须是抽象类

定义格式:

abstract class 类名字{

}

public abstract class Animal{
    public abstract void eat();//抽象方法
}

抽象方法的使用

继承抽象类的子类必须覆盖重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父 类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

@Override
public class Cat extends Animal{
    public void eat(){
        System.out.println("猫吃鱼");
    }
}
public class CatTest{
    public static void main(String[] args){
        //创建子类对象
        Cat c= new Cat();

        //调用eat方法
        c.eat();
    }
}
输出结果
猫吃鱼

2.3 注意事项

1.抽象类不能创建对象,如果创建则报错,只能创建其非抽象子类的对象

假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义

2.抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

子类的构造方法中,有默认的super(),需要访问父类构造方法。

3.抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类

未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设 计。

4.抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象 类。

假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有 意义。

三、继承的综合案例

3.1 综合案例:群主发普通红包

群主发普通红包。某群有多名成员,群主给成员发普通红包。普通红包的规则:

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

请根据描述,完成案例中所有类的定义以及指定类之间的继承关系,并完成发红包的操作。

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

class User{
    //定义成员变量
    private String name;  //用户名
    private int leftMoney; //余额

    //构造方法
    public User(){}
    public User(String name, int leftMoney) {
        this.name = name;
        this.leftMoney = leftMoney;
    }

    public void show()
    {
        System.out.println("我叫:"+this.name+"我的余额:"+this.leftMoney);
    }

    public String getName() {
        return name;
    }

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

    public int getLeftMoney() {
        return leftMoney;
    }

    public void setLeftMoney(int leftMoney) {
        this.leftMoney = leftMoney;
    }
}

class Manager extends User{

    public Manager() {}

    //调用父类构造方法
    public Manager(String name, int leftMoney) {
        super(name, leftMoney);
    }

    public ArrayList<Integer> send(int totalMoney,int count)
    {
        //需要一个集合,存储若干个红包的金额
        ArrayList<Integer> list=new ArrayList<>();

        //获取群主余额,是否够发红包
        //不能就返回null,并提示,能则继续
        int leftMoney=super.getLeftMoney();//群主当前余额
        if(totalMoney>leftMoney)
        {
            System.out.println("余额不足");
            return null;
        }
        //设置群主余额
        super.setLeftMoney(leftMoney-totalMoney);

        //发红包拆分为count份
        int avg=totalMoney/count;
        int mod=totalMoney%count; //余数。分给最后一个红包
        for (int i = 0; i < count-1; i++) {
            list.add(avg);
        }

        //最后一个红包
        int last=avg+mod;
        list.add(last);

        return list;
    }

}

class Staff extends User{

    public Staff(){}

    public Staff(String name, int leftMoney) {
        super(name, leftMoney);
    }

    public void receive(ArrayList<Integer> list){
        //从多个红包当中抽取一个,给我自己
        //随机获取一个集合当中的索引编号
        int index = new Random().nextInt(list.size());
        //根据索引,从集合中删除,并且得到被删除的红包,
        int delta=list.remove(index);
        //当前成员本有多少钱
        int money=super.getLeftMoney();
        //加法,并且设置回去
        super.setLeftMoney(money+delta);
    }
}


public class Demo04TestRedPacket {
    public static void main(String[] args) {
        Manager manager=new Manager("群主",100);

        Staff one=new Staff("A",0);
        Staff two=new Staff("B",0);
        Staff three=new Staff("C",0);
        Staff four=new Staff("D",0);

        manager.show();
        one.show();
        two.show();
        three.show();
        four.show();
        System.out.println("------------------------------");

        //获取群主发的红包
        ArrayList<Integer> redList =manager.send(22,4);
        //四个成员去收红包
        one.receive(redList);
        two.receive(redList);
        three.receive(redList);
        four.receive(redList);

        manager.show();
        one.show();
        two.show();
        three.show();
        four.show();
    }
}

输出结果:
我叫:群主 我的余额:100
我叫:A 我的余额:0
我叫:B 我的余额:0
我叫:C 我的余额:0
我叫:D 我的余额:0
------------------------------
我叫:群主 我的余额:78
我叫:A 我的余额:7
我叫:B 我的余额:5
我叫:C 我的余额:5
我叫:D 我的余额:5