Java SE面向对象--09.继承、super、this、抽象类

版权声明:转载请注明原始链接 https://blog.csdn.net/sswqzx/article/details/82713241

学习目标:

  • 三大特性——继承
  • 方法重写
  • super关键字
  • this关键字
  • 抽象类

一、 继承

1.1 继承引入

面向对象语言三大基本特征:

封装(private)、继承、多态

Java中的类是描述生活中的某类事物的。而在生活中事物和事物之间难免会存在一些联系

生活中事物和事物之间会有继承的关系。在Java中我们的类就是描述事物的,那么也就是说我们的类之间也应该存在继承的关系。

需求:

分别描述学生和老师两个类。

学生:

​ 属性:年龄、性别、姓名

​ 行为:学习、吃饭

定义构造方法给属性初始化

老师:

​ 属性:年龄、性别、姓名、薪水

​ 行为:上课、吃饭

定义构造方法给属性初始化.

代码如下所示:

/*
学生:
    属性:年龄、性别、姓名
    行为:学习、吃饭
*/
class Student{
    String name;
    int age;
    String sex;

    public Student( String name , int age , String sex ){
        this.name = name;
        this.age  = age;
        this.sex = sex;
    }
    //行为
    public void study(){
        System.out.println( name + "在 学习 java ......");
    }

    public void eat(){
        System.out.println("吃饭.......");
    }
}
/*
老师:
    属性:年龄、性别、姓名、薪水
    行为:上课、吃饭
*/
class Teacher{
    String name;
    int age;
    String sex;
    double salary;

    public Teacher( String name , int age , String sex , double salary ){
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
    }

    public void teach(){
        System.out.println( name + "正在讲 Java....");
    }
    public void eat(){
        System.out.println("吃饭.......");
    }
}

学生和老师,没有继承关系,但是学生和老师都属于人这类事物。那么我们就可以把学生和老师的共性内容抽取到人这个类中,然后让学生和老师与人这个类产生关系即可。

这里写图片描述

1.2 Java中的继承体现

在java中使用extends关键字来建立类和类之间的继承关系,extends在java中表示继承的意思。

代码如下:

/*
    把学生和老师中的共性内容抽取到人这个类中
*/
class Person{
    // 共性的属性
    String name;
    int age;
    String sex;
    // 共性的行为
    public void eat(){
        System.out.println("吃饭.......");
    }
}
/*
学生:
    属性:年龄、性别、姓名
    行为:学习、吃饭
*/
class Student extends Person{
    public Student( String name , int age , String sex ){
        this.name = name;
        this.age  = age;
        this.sex = sex;
    }
    //行为
    public void study(){
        System.out.println(name + "在 学习 java ......");
    }
}
/*
老师:
    属性:年龄、性别、姓名、薪水
    行为:上课、吃饭
*/

class Teacher extends Person{
    // 老师特有的属性
    double salary;
    pubic Teacher( String name , int age , String sex , double salary ){
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
    }
    // 老师特有的行为
    public void teach(){
        System.out.println( name + "正在讲 Java....");
    }
}
// 测试类
class ExtendsDemo {
    public static void main(String[] args) {
        // 创建学生类对象
        Student s = new Student("张三",23,"男");
        s.eat();
        s.study();
        // 创建老师类对象
        Teacher t = new Teacher("黑旋风",28,"男",9999.99);
        t.eat();
        t.teach();
    }
}

1.3 继承概述

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

这里写图片描述

其中,多个类(继承其他类的的类)我们称为子类,单独那一个类(被继承的类)称为父类超类(superclass)或者基类

定义

  • 继承:就是子类继承父类的属性行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
  • 父类中描述的为当前事物体系的共性的属性和行为。
  • 子类能够继承父类中的所有非私有的属性和行为,同时子类也能够具备自己本身特有的属性和行为。

继承格式

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

class 父类 {
    共性的属性
    共性的行为
}

class 子类 extends 父类 {
    /*隐式存在父类非私有的成员*/
    特有的属性
    特有的行为
}

注意事项

在Java的继承语法中,只要使用extends 关键字 任何2个类之间都可以完成继承。但是我们在写程序的时候,一定要考虑清楚当前两种事物在逻辑上是否存在继承的可能,能不能继承。

好处

  1. 让类和类之间产生的关系,父类定义好的成员,子类可以直接使用。提供代码的复用性。
  2. 类与类之间产生了关系,是多态的前提

1.4 继承中的特点

1、Java只支持单继承,不支持多继承。

说明:

单继承:一个子类只能有一个父类。支持

多继承:一个子类可以有多个父类。不支持

//一个类只能有一个父类,不可以有多个父类。
class C extends A{}     //ok
class C extends A,B...  //error

2、Java支持多重继承(继承体系)。

说明:多重继承也叫做多层次继承

class A{}
class B extends A{}
class C extends B{}

顶层父类是Object类。所有的类默认继承Object,作为父类。在java中不管是java已经提供好的类,还是我们自己手动手写的自定义的类,只要是类,最最顶层的父类一定是Object。

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

学习继承:主要围绕 类中的成员变量 、成员方法 、 构造方法的变化。

当类之间产生了关系后,其中各类中的成员变量,又产生了哪些影响呢?

成员变量不重名

如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:

// 父类
class Fu{
    // 父类的成员变量
    int x = 3;
    // 父类的构造方法
    public Fu(){
        System.out.println("this" + this);
    }
}
// 子类
class Zi extends Fu{
    // 子类的成员变量
    int y = 10;
    // 子类的成员方法
    public void show(){
        System.out.println("x= "+ x + ", y = " + y);
    }
    // 子类的构造方法
    public Zi(){
        System.out.println("this" + this);
    }
}
// 测试类
class ExtendsDemo {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

在上述程序中 Zi 类 继承 Fu 类,在测试的程序中 我们仅仅只创建了 Zi类的对象,而没有创建Fu类的对象,但是在程序运行的过程中,使用到了Fu类中的成员变量,并且能够正常访问到,说明程序中一定给Fu类的成员变量开辟了空间。

当子类继承了父类,在创建子类对象的时候,堆中的子类对象空间中会划分出一片空间来保存父类中的成员变量。

成员变量重名

如果子类父类中出现重名的成员变量,这时的访问是有影响的。代码如下:

class Fu {
    // Fu中的成员变量。
    int x = 5;
}
class Zi extends Fu {
    // Zi中的成员变量
    int x = 6;
    public void show() {
        // 访问父类中的x
        System.out.println("Fu x=" + x); // 6
        // 访问子类中的x
        System.out.println("Zi x=" + x); // 6
    }
}
class ExtendsDemo03 {
    public static void main(String[] args) {
        // 创建子类对象
        Zi z = new Zi(); 
        // 调用子类中的show方法
        z.show(); 
    }
}

子父类中出现了同名的成员变量时,基于就近原则,子类本身具备x这个变量,所以会优先使用子类本身的变量。在子类中需要访问父类中非私有成员变量时,需要使用super 关键字,修饰父类成员变量,类似于之前学过的 this 关键字。

super关键字

如果在子类中要访问父类中的成员,可以使用super关键字。在Java中super关键字的作用就是用来在子类中去访问父类的内容。

使用格式:

super.父类成员变量名;
super.父类成员方法名(参数列表);
super(参数类别); // 访问父类的构造方法

this和super关键字的区别:

​ this表示的本类中的成员,super表示父类中的成员。

​ 在Java中,this是一个引用变量,它中保存的是当前调用方法的那个对象的内存地址。

​ super就是一个标记性质的关键字,用来标记当前内容属于父类。它不是对象是标记。

子类方法需要修改,代码如下:

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

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

总结

1、在继承中子类可以直接使用父类中的非私有的成员变量。

2、当子父类中存在一模一样的成员变量时,子类对象优先使用属于子类对象自己的成员变量,

​ 如果在子类中需要使用父类中重名的成员变量时,需要使用super关键字:super.父类成员变量名;

3、子类能否继承父类中私有的成员变量?

​ 不能直接继承,可以间接使用,通过成员的getXxx()和setXxx()方法进行访问。

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

当类之间产生了关系,其中各类中的成员方法,又产生了哪些影响呢?

子父类成员方法不重名

如果子类父类中出现方法名不相同的成员方法,这时的调用是没有影响的。代码如下:

class Fu{
    public void show(){
        System.out.println("Fu类中的show方法执行");
    }
}
class Zi extends Fu{
    public void show2(){
        System.out.println("Zi类中的show2方法执行");
    }
}
public  class ExtendsDemo0{
    public static void main(String[] args) {
        Zi z = new Zi();
        //子类中没有show方法,但是可以找到父类方法去执行
        z.show(); 
        z.show2();
    }
}

子父类成员方法重名

如果子类父类中出现一模一样的成员方法,这是一种特殊情况,叫做方法重写 (Override)。

  • 方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现
  • 注意区分java中方法重写和重载的区别。 重载:本类中出现两个以上方法名同名但是参数列表不同的方法。
  • (重载是同名不同参)
//演示方法的重写
class Fu{
    public void show(){
        System.out.println("Fu show run.....");
    }
}

class Zi extends Fu{
    //假设父类中的show方法的方法体不适合子类时,
    //子类可以沿用父类的方法定义格式,但重新书写新的方法体
    public void show(){
        System.out.println("Zi show run ...........");
    }
}
class MethodOverrideDemo {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}

方法重写的使用场景和练习

在继承中,子类可以继承到父类中的非私有的成员方法。但是随着事物的不断进化,会出现父类方法的方法体的功能实现不在适合子类使用。这时就需要子类沿用父类的方法定义格式,在本类中重新定义该方法并重写符合自己本身的方法体实现的代码。简单来说就是重写就是子类在增强父类方法的功能实现。

注意: 如果父类中的成员方法被私有了,子类是无法访问的。更无法重写。

练习:需求:描述手机这类事物。原始的手机和现代的手机

// 描述老手机
class Phone{
    public void call(){
        System.out.println("打电话");
    }
    public void sendMessage(){
        System.out.println("发信息");
    }
}
// 描述新手机
class NewPhone extends Phone{
    //新一代的手机具备手机原有的功能,但是在打电话的基础上可以增加更多的显示效果
    public void call(){
        //打电话 , 显示大头贴  显示 归属地
        //System.out.println("打电话");  父类已经实现了,这时我们只要沿用父类的功能,
        //在父类的功能基础上增加子类的功能
        super.call();
        System.out.println("显示大头贴和归属地");
    }
    public void sendMessage(){
        //现在的手机依然可以发短信,但也可以发送图片,音频等信息
        //System.out.println("发信息");
        super.sendMessage();
        System.out.println("发彩信");
    }
}

class MethodOverrideTest {
    public static void main(String[] args) {
        // 创建新手机对象
        NewPhone np = new NewPhone();
        // 调用手机的功能
        np.call();
        np.sendMessage();
    }
}

小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。

结论:

当父类描述好的行为,但是行为体不适合自己的时候,子类可以沿用父类的行为定义方式,对行为体进行重新的书写。

总结

1、在继承中子类可以直接调用父类中非私有的成员方法

2、如果子父类中出现了一模一样(返回值类型、方法名、参数列表相同)的方法时,我们叫做方法的重写。此时子类在调用方法的时候,调用的是子类重写之后的方法。

​ 如果子类想调用父类中同名的方法可以使用super关键字完成: super.方法名(参数列表);

3、子类在重写父类的方法时,访问权限要大于或等于父类方法的访问权限。

​ a)在java中访问权限,public权限 > 默认权限 > private权限;

​ b)在java中成员方法和成员变量,如果什么修饰符都不写,那就是默认修饰权限default;

4、子类在重写父类的方法时,不能修改父类的方法的访问方式(静态或非静态)。

5、如果子类中存在一个和父类中私有方法相同的方法时,子类中的方法不是重写父类方法,它属于子类中定义的特有的方法(父类中私有的方法不参与子类方法重写)。

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

当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?

首先我们要回忆两个事情,构造方法的定义格式和作用。 修饰符 类名(参数列表…){}

  1. 构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
  2. 构造方法的作用是创建对象并初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。

在前面学习对象的创建流程中,在使用new关键字创建对象的时候,先在堆中给对象分配内存空间,接着给非静态的成员变量进行默认初始化,开始调用对应的构造方法。而在执行构造函数中有隐式的三步:

​ 1、super(); 调用父类空参数的构造方法。父类成员变量初始化后,才可以给子类使用。

​ 2、非静态成员变量显示赋值。

​ 3、构造代码块运行。

​ 4、本构造方法中的代码运行。

构造方法代码演示:

//继承中的构造方法细节
class Fu{
    // 父类成员变量
    int x = 3;
    // 构造代码块
    {
        System.out.println("Fu  构造代码块  x= " + x);
    }
    // 父类构造 
    public Fu(){
        //super();
        System.out.println("Fu的构造方法执行");
        show();
    }
    // 父类成员方法
    public bvoid show(){
        System.out.println("Fu show x= " + x);
    }
}
class Zi extends Fu{
    // 子类成员变量
    int y = 10;
    // 子类构造代码块
    {
        System.out.println("Zi 的构造代码块  x="+x);
        System.out.println("Zi 的构造代码块  y="+y);
    }
    // 子类构造方法
    public Zi(){
        super();
        System.out.println("Zi的构造方法执行。。。。。");
        show();
    }
    public void show(){
        System.out.println("Zi show x="+x);
        System.out.println("Zi show y="+y);
    }
}
// 测试类
class ConstructorDemo {
    public static void main(String[] args) {
        Zi z = new Zi();
    }
}

继承中构造方法注意事项

1、为什么任何一个类(不包含Object)的构造方法中需要一个隐式的super() 语句?

​ 因为任何的子类在继承了父类之后,都会继承到父类的成员变量,这时在创建子类对象的时候,会在子类的对象空间中分配出空间存储父类的成员变量。而父类的成员变量显示初始化动作必须由父类的自己的构造方法来完成。所以在任何类的构造方法中有一个隐式的super()语句。其目的是为了完成父类的成员变量的初始化动作。

2、如果一个类中的所有构造方法全部私有了,问还可以有子类吗?子类能实例化(创建对象)吗?

​ 一个类的构造方法全部私有,这时这个类是无法再有子类的。就不存在实例化问题。

3、如果一个类没有空参数的构造方法,问这个类可以有子类吗?子类可以实例化(创建对象)吗?

​ 如果这个类还有其他的构造方法可以被访问,那么这个类就可以有子类。

​ 这时要求在子类的构造方法中必须手动的书写super语句,同时在super( 具体的参数 )。根据指定的参数去调用父类类型相同的构造方法。

可以创建这个类的对象 new 父类名(参数列表);

4、this调用构造方法可以和super调用父类构造方法共存吗?

不可以。因为this和super调用构造方法都必须在构造函数中的第一行。

this是调用本类其他构造方法对当前这个对象初始化,而super是调用父类构造方法进行初始化,而初始化动作在一个构造方法中只能有一个。

在一个类中有多个构造方法,之间可以使用this调用,但不能形成嵌套调用。最后肯定有一个构造方法中是没有this调用的,最后它中必然会有super语句调用父类的构造方法。

this关键字: this表示的是对象。 可以区分局部和成员

super关键字:super表示的是父类,仅仅标记

二、 抽象类

2.1抽象类的产生

我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类

这里写图片描述

这时我们就需要使用Java中的抽象的概念来描述这个无法写清楚的行为。由于行为我们在Java中以方法的形式体现,也就是说这个方法就变成一个抽象的方法,而一旦某个类中有了抽象的方法这个类就变成抽象类了。

// 描述狗
class Dog {
    public void eat(){
        System.out.println("啃骨头");
    }
    public void lookHome(){
        System.out.println("看家");
    }
}
// 描述老虎
class Tiger{
    public void eat(){
        System.out.println("吃鱼");
    }
    public void hunting(){
        System.out.println("老虎捕猎....");
    }
}

public class AbstractDemo {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.eat();
    }
}

通过分析:以上程序中存在共性的内容

Tiger类和Dog类中共性内容:

在上述两个类中都有吃的行为,遇到共性内容,需要向上抽取,抽取存放到类中:把共性内容封装到Animal类中。

我们把狗和老虎中的eat行为抽取到了Animal类中,但是我们没有办法去明确具体的方法体代码,如果没有办法明确具体的方法体的时候,这时可以把方法体省略。在java中没有方法体的方法我们称为抽象的方法,这个方法必须使用Java中的抽象关键字修饰。抽象关键字:abstract

abstract使用格式

抽象方法

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

定义格式:

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

代码举例:

//把狗和猫中的共性抽取到Animal类中
public abstract class Animal{
    public abstract void eat();
}

抽象类

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

定义格式:

abstract class 类名字 { 

}

代码举例:

抽象的使用

1、抽象类不能创建对象,因为抽象类中的方法属于抽象方法,方法本身并没有方法体完成功能,而我们创建类对象的目的是为了调用方法完成功能,而抽象类中的方法因为没有方法体,所以调用没有意义。

2、继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。因为当子类继承了抽象类之后就等于在子类中隐式存在有父类中的抽象方法,而子类中有抽象方法那么子类也必须声明为抽象类。因此最终,必须有子类实现该父类的抽象方法,否则,从最初的父类到最终的子类都不能创建对象,失去意义。

代码演示:

//把狗和猫中的共性抽取到Animal类中
abstract class Animal{
    abstract void eat();
}

class Dog extends Animal{
    public void eat(){
        System.out.println("啃骨头");
    }
    public void lookHome(){
        System.out.println("看家");
    }
}

class Tiger extends Animal{

    //复写Animal类中的抽象函数eat
    public void eat(){
        System.out.println("吃鱼");
    }
    public void hunting(){
        System.out.println("老虎捕猎....");
    }
}
public class AbstractDemo {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.eat();
    }
}

提示:此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法

2.2 注意事项

1、子类在继承抽象类后,需要把抽象类中所有的抽象方法全部复写(重写)完成。

2、抽象类和一般类有什么区别?

​ 抽象类肯定需要abstract修饰,一般类不能使用。

3、抽象类有没有构造方法,能不能创建对象?

​ 有构造方法,但不能创建对象。

​ 构造方法是给成员变量初始化的,抽象类中的构造方法就是为了子类对象初始化使用的,因为抽象类一定有子类,而创建子类对象的时候,在子类的构造方法中一定有super()语句会找自己的父类构造方法进行初始化动作。

4、抽象类一定是父类吗?

​ 一定是父类,一定不是顶层父类。

5、抽象类可以继承其他类吗?

​ 抽象类也是一个类,因此它必须具备类的继承特点。它可以有父类。

6、抽象关键字不能和哪些关键字共存?

​ private修饰符:表示私有的。

​ 父类中私有的成员,子类是不知道的,因此使用private 和 abstract关键字一起修饰方法,导致子类根本无法知道父类中有个抽象方法,但是又要求子类一定要重写实现父类的抽象方法。

​ static修饰符:表示静态的。

​ static:如果使用static和abstract关键字一起修饰抽象方法,导致这个抽象方法可以使用类名直接调用,而抽象方法是没有方法体的,调用这个抽象方法是没有意义的。

final 修饰符:表示最终的。可修饰类 属性 方法(先了解)

final :final修饰的方法子类是无法复写的,而abstract修饰的方法,要求子类必须复写。

三、继承的综合案例

3.1 综合案例:群主发普通红包( 难,最好自己写出来 )

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

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

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

3.2 案例分析

根据描述分析,得出如下继承体系:

这里写图片描述

3.3 案例实现

定义用户类:

package cmo.base.sh.demo_01;
/*
    定义用户类
 */
public class User {
    //成员变量
    private String name;
    private double leftMoney;
    //构造方法
    public User(String name, double leftMoney) {
        this.name = name;
        this.leftMoney = leftMoney;
    }
    public User() {
    }
    //成员方法 get set

    public String getName() {
        return name;
    }

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

    public double getLeftMoney() {
        return leftMoney;
    }

    public void setLeftMoney(double leftMoney) {
        this.leftMoney = leftMoney;
    }
    //定义成员方法显示信息
    public void showLeftMoney(){
        System.out.println("用户名: " + name+",余额为:"+leftMoney+"元");
    }
}

定义群主类:

分析:

​ 定义一个成员方法作为群主发红包就是把红包金额分成若干份,然后将红包金额
​ 存储到一个集合中,如果红包金额/份数,除不尽那么最后一个获取红包的人钱数就会多一些
​ 例如:20.0 / 3–>6.66 6.66 6.68 20.01
​ 注意:红包金额有可能会出现20.02那么如果这里除以3,这样会导致损失精度
​ 所以为了避免损失精度,我们将元换算成分。如20.02元换成分就是—-》20.02*100—-》2002.0
​ 然后在强制转换为int类型
​ 这样做的原因:份数n是int类型,上述转换完成之后也是int,那么商也是int
步骤:
​ 1.获取群主余额,判断是否够发红包的
​ 不够,则返回null,并提示
​ 够,则继续执行
​ 2.程序执行到第二步说明够发红包的 修改群主的余额

​ 3.拆分红包
​ 3.1 使用循环将(n-1)的平均金额分配到集合中
​ 3.2 将保存到集合中的金额从发红包的总金额中减去
​ 3.3 把最后一份保存到集合中
​ 4.返回集合
​ double totalMoney 表示红包总金额
​ int n 表示红包个数

package cmo.base.sh.demo_01;
import java.util.ArrayList;
/*
    定义群主类
 */
public class QunZhu extends User {
    //成员变量
    //构造方法
    public QunZhu() {
    }
    public QunZhu(String name, double leftMoney) {
        super(name, leftMoney);
    }
    //发红包的方法
    public ArrayList<Double> faHongBao( double totalMoney, int n ){
        //  1.获取群主余额,判断是否够发红包的
        double leftMoney = getLeftMoney();
        if (leftMoney < totalMoney) {
            // 不够,则返回null,并提示
            return null;//结束该方法
        }

        //  2.程序执行到第二步说明够发红包的 修改群主的余额
        setLeftMoney( leftMoney-totalMoney );

        // 创建集合保存每个红包的金额
        ArrayList<Double> list = new ArrayList<>();

        //  把totalMoney换算成为以分为单位的整数 20.02*100----》2002分
        int money = (int)(totalMoney * 100);
        //求出平均的金额
        int avg_money = money / n;//667分 2000
        //  3.拆分红包
        //  3.1 使用循环将(n-1)的平均金额分配到集合中
        for ( int i = 0; i < n - 1; i++ ) {
            list.add( avg_money / 100.0 );//将分转换为元6.67
            //  3.2 将保存到集合中的金额从发红包的总金额中减去
            money = money - avg_money;
        }
        //程序执行到这里,还有最后一份红包也保存到集合中
        //  3.3 把最后一份保存到集合中
        list.add(money / 100.0);
//        System.out.println(list);
        //  4.返回集合
        return list;
    }
}

定义成员类:

package cmo.base.sh.demo_01;
import java.util.ArrayList;
import java.util.Random;
/*
    定义成员类
 */
public class Member extends User {
    //构造方法
    public Member(String name, double leftMoney) {
        super(name, leftMoney);
    }
    public Member() {
    }
    //成员方法
    /*
        收取红包:就是从红包集合随机抽取一份放到自己的余额中,即根据索引进行删除
        返回值:void
        参数列表:ArrayList集合
     */
    public void shouHongBao( ArrayList<Double> list ){
        // 创建随机数类的对象
        Random r = new Random();
        // 获取要删除元素的索引0 ---> 集合长度 - 1
        int index = r.nextInt( list.size() );//比如集合长度是3 这里生成的索引是0 1 2
        // 根据索引删除数据
        Double money = list.remove(index);//移除金额
        //将移除的金额放到用户的余额中,但是注意,这里不能将用户原来的金额进行覆盖,应该是在原来的基础之上进行相加
        setLeftMoney( money + getLeftMoney() );
    }
}

定义测试类:

package cmo.base.sh.demo_01;
import java.util.ArrayList;
/*
    测试类
 */
public class Test {
    public static void main(String[] args) {
        //创建一个群主对象
        QunZhu qunZhu = new QunZhu( "群主" , 200.0);
        // 创建成员对象
        Member m1 = new Member("柳岩", 10.0);//成员柳岩余额是10元
        Member m2 = new Member("范冰冰", 10.0);//成员范冰冰余额是10元
        Member m3 = new Member("杨幂", 10.0);//成员杨幂余额是10元
        System.out.println("================没发红包之前======================");

        qunZhu.showLeftMoney();//200.0
        m1.showLeftMoney();//10
        m2.showLeftMoney();//10
        m3.showLeftMoney();//10
        //调用方法发红包
        ArrayList<Double> list = qunZhu.faHongBao(20.02, 3);
        //判断余额是否够用
        if (list == null) {
            System.out.println("余额不足。。。");
            return;
        }
        //余额够用
        System.out.println("++++++++++++++++收红包+++++++++++++++++");
        //收红包
        m1.shouHongBao(list);
        m2.shouHongBao(list);
        m3.shouHongBao(list);
        System.out.println("================发红包之后======================");
        qunZhu.showLeftMoney();
        m1.showLeftMoney();
        m2.showLeftMoney();
        m3.showLeftMoney();
    }
}

猜你喜欢

转载自blog.csdn.net/sswqzx/article/details/82713241