• Java继承与多态


    一、面向对象的继承

    1.继承基础

    继承是多态的前提,如果没有继承就没有多态
    继承主要解决的问题就是:共性抽取
    
    父类(超类、基类)==》子类(派生类)
    继承关系当中的特点:
    1、子类可以拥有父类的“内容”
    2. 子类还可以拥有自己专属的内容
    
    package func.extend;
    /*
    在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
    例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a.
    
    定义父类的格式:(一个普通的类定义)
    public class 父类名称{
        .....
    }
    
    定义子类的格式:
    public class 子类名称 extends 父类名称{
        //。。。
    }
    
    在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
    1.直接通过子类对象访问成员变量:
        等号左边是谁,就优先用水,没有则向上找。 new一个子对象,那么等号左边就是子对象,那么先找自己
    2.间接通过成员方法访问成员变量;
        该方法属于谁,就优先用谁,没有向上找
     */
    public class Demo01Extends {
        public static void main(String[] args) {
            Teacher teacher = new Teacher();
            System.out.println(teacher.numZi);
            System.out.println(teacher.numFu);  //除了可以调用本类的变量,还可以调用父类的变量
            teacher.method();   //本类没有方法,执行继承的父类的Method方法
    
        }
    }
    
    

    2.继承扩展:

    1.
    //三种成员变量的获取
    package func.extend;
    
    public class Fu {
        int num = 20;
    }
    
    ------------------
    package func.extend;
    
    public class Zi extends Fu {
        int num = 10;
    
        public void method() {
            int num = 30;
            System.out.println(num);    //30,局部变量
            System.out.println(this.num);   //10,本类变量
            System.out.println(super.num);  //20,父类的成员变量
        }
    }
    --------------------
    package func.extend;
    /*
    局部变量:   直接写成员变量名
    本类的成员变量:    this.成员变量名
    父类的成员变量:    super.成员变量名
     */
    public class ExtendsField {
        public static void main(String[] args) {
            Zi zi = new Zi();
            Fu fu = new Fu();
    
            zi.method();    //30,10,20
    
        }
    
    }
    
    
    2.继承中成员方法的访问特点
        同Python
        1.覆盖重写:可以使用 @Override 检测是否重写成功
        2.必须保证父子类之间方法名称跟参数列表相同
        2.子类方法的返回值必须小于等于父类方法的返回值范围
        3.子类方法的权限必须大于等于父类方法的权限修饰符
            扩展提示:public>protected>(default)>private
            备注:(default)不是关键字default,耳饰什么都不写,留空
        
        应用场景:    
            对于已经投入使用的类,尽量不要进行修改。而推荐定义一个新的类,来重复利用其中共性内容,并且添加新内容
            
            在子类的成员方法中,如果重写了父类的方法,又想使用父类的方法功能,可以在子类方法中使用super.重写方法名,获得父类当中需要继续使用的部分
    
    3.继承中构造方法的访问特点:
    父类:
    package func.extend;
    
    public class Fu {
    
        public Fu(int num) {
            System.out.println("父类构造方法");
        }
    }
    子类:
    package func.extend;
    
    public class Zi extends Fu {
    
        public Zi() {
    //        super();
            //子类构造方法默认有一个隐含的super();当父类自定义了有参构造方法,那么父类将不再默认拥有系统赠送的构造方法
            //那么这里如果不调用父类的有参构造方法,隐含的子类super()将会导致程序出错
            super(1);
        }
    }
    调用:
    package func.extend;
    
    /*
    继承关系中,父子类构造方法的访问特点:
    1.子类构造方法当中有一个默认隐含的“super()"调用,所以一定是先调用父类构造,随后调用子类构造方法
    2.子类构造可以通过super关键字来调用父类重载构造
    3.super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造
    总结:
        子类必须调用父类构造方法,不写则赠送super();写了则用写的指定的super调用。
        super只能有一个且必须是第一个语句
     */
    public class Constructor {
        public static void main(String[] args) {
            Zi zi = new Zi();
    
        }
    
    }
    
    4. super关键字的三种用法:
    /*
    super关键字的用法有三种:
    1.在子类的成员方法中,访问父类的成员变量。
    2.在子类的成员方法中,访问父类的成员方法。
    3.在子类的构造方法中,调用父类的构造方法
     */
    
    5. this关键字的用法:
    /*
    super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
    1.在本类的成员方法中,访问本类的成员变量。
        int num = 1;
        public void showNum(){
            System.out.printlt(this.num);
        }
    2.在本类的成员方法中,访问本类的另一个成员方法。
        public void showNum(){
            this.showNum1();
        }
        public void showNum1(){
            System.out.printlt(this.num)
        }
    3.在本类的构造方法中,访问本类的另一个构造方法。(无参有参相互调用)
        a.在此方法中,this()调用也必须是构造方法的第一个语句,也是唯一一个
        b.super和this两种构造调用,不能同时使用
     */
    
    6. Java继承的三个特点:
    Java语言是单继承的,一个类的直接父类只能有唯一一个。
    Java语言可以多级继承,父类可以有父类,爷爷类也叫父类,所有类都是继承Object

    二、抽象类

    如果父类当中的方法不确定如何进行{}方法实现,那么这就应该是一个 抽象方法
    
    1.抽象类的定义:
    package Demo316;
    
    /*
    抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束
    抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可
     */
    //public class Animal {
    //    public void eat(){
    //        System.out.println("吃鱼?");
    //        System.out.println("吃骨头?");  //对于动物,有很多种食物种类,不确定
    //    }
    //
    //}
    public abstract class Animal {
        //这是一个抽象方法,代表吃东西,但是具体吃什么(大括号内容)不确定。
        public abstract void eat();
    
        //抽象类中一样可以定义普通方法  
        public void normalMethod() {
    
        }
    
    }
    
    2.如何使用抽象类和抽象方法:
    2.1.定义子类继承
    package Demo316;
    
    public class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("猫吃鱼 ");
        }
    }
    2.2.使用:
    package Demo316;
    
    /*
    如何使用抽象类和抽象方法:
    2.2.1.不能直接创建new抽象类对象.
    2.2.2必须用一个子类来继承抽象父类。
    2.2.3.子类必须覆盖重写抽象父类当中所有的抽象方法
        覆盖重写(实现):去掉抽象方法的abstract关键字,然后补上大括号方法体
    2.2.4.创建子类对象进行使用。
    
     */
    public class AbstractUse {
        public static void main(String[] args) {
            Cat cat = new Cat();
            cat.eat();  //猫吃鱼
        }
    
    }
    
    3.注意事项:
        3.1抽象类不能创建对象,如果创建,编译无法通过而报错,只能创建其非抽箱子类的对象
        3.2抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
            - 子类的构造方法中,有默认的super(),需要访问父类构造方法(跟普通类一样)
        3.3抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类
        3.4抽象类的子类,必须重写抽象父类中所有的抽象方法。否则,编译无法通过而报错。除非该子类也是抽象类。
        
    4. 继承的综合案例:
    群主发普通红包。某群有多名成员,群主给成员发普通红包。红包的规则:
        1、群主的一笔金额,从群主余额中扣除,平均分成n等分,让成员领取。
        2.成员领取红包后,保存到成员余额中。
    请根据描述,完成案例中所以后类的定义以及指定类之间的继承关系,并完成发红包的操作。
    分析:
        发红包的逻辑,三要素:
        返回值类型:ArrayList<Integer>  //返回几份分好的红包
        方法名称:send
        参数列表:1.总共发多少钱 int totalMoney  2. 分成多少份 int count
        public ArrayList<Integer> send(int totalMoney,int count){
            ....
        }
        
        收红包的逻辑:三要素:
        返回值类型:void    //直接自己余额相加,不需要返回
        方法名称:receive
        参数列表:ArrayList<Integer>
        public void receive(ArrayList<Integer> list){
            
        }
        
    

    接上红包游戏:1.用户父类

    package Demo316;
    
    /*
     * 发红包,用户类
     * */
    public class User {
        private String name;    //姓名
        private int money;  //余额
    
        public User() {
        }
    
        public User(String name, int money) {
            this.name = name;
            this.money = money;
        }
    
        //展示一下当前用户有多少钱:
        public void show() {
            System.out.println("我叫:" + name + ",我有多少钱:" + 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;
        }
    
    }
    
    

    2.群主类:

    package Demo316;
    
    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> moneyList = new ArrayList<>();
            //查看群主余额,红包不能超过余额
            int leftMoney = super.getMoney();
            if (totalMoney > leftMoney) {
                System.out.println("余额不足");
                return moneyList;
            }
            //扣钱,其实就是重新设置余额:
            super.setMoney(leftMoney - totalMoney);
    
            //发红包,平均拆分
            int avg = totalMoney / count;
            int mod = totalMoney % count;   //余数,就是除不尽的零头
            //零头包在最后一个红包当中
            //把红包一个个放在集合当中
            for (int i = 0; i < count - 1; i++) {
                moneyList.add(avg);
            }
            //最后一个红包,如果有零头,最后一个红包稍微大一点,上边count-1是为了让这个红包有位置,不影响总体个数
            //微信怎么处理?微信没有总额平均分的形式,只有总额运气红包 或者平均分,需要设定单个金额跟红包个数,避免无限小数
            int last = avg + mod;
            moneyList.add(last);
    
            return moneyList;
        }
    }
    
    

    3.群员类:

    package Demo316;
    
    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 receive(ArrayList<Integer> list) {
            Random random = new Random();
            //随机从红包集合中获取一个红包
            int getIndex = random.nextInt(list.size());
            int getMoney = list.remove(getIndex);
            //获取当前用户当前余额
            int currentMoney = super.getMoney();
            //设置余额为当前余额加红包
            super.setMoney(currentMoney + getMoney);
        }
    }
    
    

    4.结果展示:

    package Demo316;
    
    import java.util.ArrayList;
    
    public class DemoShow {
        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("-------------------------");
            //群主发红包,并获取红包列表,为了验证随机性,金额设置为21,除不尽
            int redMoney = 21;
            ArrayList<Integer> redList = manager.send(redMoney, 4);
            //核对群主余额:
            manager.show();
            //群员领红包:因为维护的是同一个集合地址,所以领取remove后后面的成员共享修改后的集合
            member1.receive(redList);
            member2.receive(redList);
            member3.receive(redList);
            member4.receive(redList);
    
            //群员展示领取后金额:
            member1.show();
            member2.show();
            member3.show();
            member4.show();
    
        }
    }
    
    

    三、接口

    接口就是一种公共的规范标准。
    只要符合规范标准,就可以大家通用
    
    1.接口的定义:
    /*
    接口就是多个类的公共规范。
    接口就是一种引用数据类型,最重要的内容就是其中的:抽象方法
    如何定义一个接口的格式:
    public interface 接口名称{
        //接口内容
    }
    备注:与类的编译一致,接口编译生成的字节码文件仍然是: .java --> .class
    
    如果是Java7,那么接口中可以包含的内容有:
    1.常量
    2.抽象方法
    
    如果是Java8,还可以额外包含有:
    3.默认方法
    4.静态方法
    
    如果是Java9,还可以额外包含有:
    5.私有方法
    
     */
     
     1.1
     package demo317.interface1;
    /*
    在任何版本的Java中,接口都能定义抽象方法。
    格式:
    public abstract 返回值类型 方法名称(参数列表);
    
    注意事项:
    1.接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
    2.这两个关键字修饰符,可以选择性地省略(熟悉之前不推荐).
    3.方法的三要素可以随意定义。
     */
    
    public interface MyInterfaceAbstract {
        //这是一个抽象方法
        public abstract void methodAbs1();
        //这也是抽象方法
        abstract void methodAbs2();
        //这也是抽象方法
        public void methodAbs3();
        //这也是抽象方法
        void methodAbs4();
    }
    
    2.接口的实现
    2.1 定义实现类
    package demo317.interface1;
    
    public class InterfaceAbstractImpl implements MyInterfaceAbstract {
    
        @Override
        public void methodAbs1() {
            System.out.println("这是第一个方法!");
        }
    
        @Override
        public void methodAbs2() {
            System.out.println("这是第二个方法!");
        }
    
        @Override
        public void methodAbs3() {
            System.out.println("这是第三个方法!");
        }
    
        @Override
        public void methodAbs4() {
            System.out.println("这是第四个方法!");
        }
    }
    
    2.2 使用接口抽象方法:
    package demo317.interface1;
    /*
    接口使用步骤:
    1.接口不能直接使用,必须有一个实现类“”来“实现”该接口。
    格式:
    public class 实现类名称 implements 接口名称{
        //....
    }
    2.接口的实现类必须覆盖重写(实现)接口中的所有的抽象方法
    实现:去掉abstract关键字,加上方法体大括号。
    3.创建实现类的对象,进行使用
    注意事项:
    如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类
    */
    
    public class Demo01Interface {
        public static void main(String[] args) {
            //错误写法!不能直接New接口对象使用
    //    MyInterfaceAbstract inter = new MyInterfaceAbstract();
            //创建实现类的对象使用
            InterfaceAbstractImpl impl = new InterfaceAbstractImpl();
            impl.methodAbs1();
            impl.methodAbs2();
        }
    }
    
    3.接口默认方法定义:
    package demo317.interface1;
    
    /*
    从Java8开始,接口里允许定义默认方法。
    格式:
    public default 返回值类型 方法名称(参数列表){
        //方法体
    }
    备注:接口当中的默认方法,可以解决接口升级的问题。
        接口升级:由于实现接口的抽象方法,必须要定义一个实现类implements来继承接口,并且覆盖重写
        接口类的所有抽象方法,才能通过这个继承类使用方法,所以当接口升级,新增了抽象方法,会导致继承接口的所有类
        报错,直至它们把新增的方法覆盖重写,这样会很麻烦且可能导致很多牵涉问题
     */
    public interface MyInterfaceDefault {
        //抽象方法
        public abstract void methodAbs();
        //新添加了一个抽象方法,继承的类相应的要重写
    //    public abstract void methodAbs2();
        //新添加的方法,改成默认方法
        public default void methodDefault(){
            System.out.println("这是新添加的默认方法");
        }
    }
    3.1 默认方法实现类:
        实现类Apackage demo317.interface1;
    
    public class InterfaceDefault implements MyInterfaceDefault {
        @Override
        public void methodAbs() {
            System.out.println("实现了抽象方法,AAA");
    
        }
    }
        实现类Bpackage demo317.interface1;
    
    public class InterfaceDefaultB implements MyInterfaceDefault{
    
        @Override
        public void methodAbs() {
            System.out.println("实现了抽象方法:BBB");
        }
    
        @Override
        public void methodDefault() {
            System.out.println("实现类B重写了接口的默认方法");
        }
    }
        调用实现类:
    package demo317.interface1;
    /*
    1.接口的默认方法,可以通过接口实现类对象,直接调用。
    2.接口的默认方法,也可以被接口实现类进行覆盖重写。
     */
    public class Demo02Interface {
        public static void main(String[] args) {
            //创建了实现类对象
            InterfaceDefault a = new InterfaceDefault();
            a.methodAbs();  //调用抽象方法,实际运行的是右侧的实现类
            //调用默认方法,如果实现类当中没有,会向上找接口
            a.methodDefault();  //这是新添加的默认方法
            System.out.println("==============");
            InterfaceDefaultB b = new InterfaceDefaultB();
            b.methodAbs();
            //如果重写了默认方法,那么首先调用自身的方法
            b.methodDefault();  //实现类B重写了接口的默认方法
    
    
        }
    }
    
    4.静态方法:
    4.1 定义:
    package demo317.interface2;
    /*
    从Java8开始,接口当中允许定义静态方法。
    格式:
    public static 返回值类型 方法名称(参数列表){
        //方法体
    }
    提示:就是将abstract或者default换成static即可,带上方法体。
    
     */
    
    public interface MyInterfaceStatic {
        public static void methodStatic(){
            System.out.println("这是接口的静态方法!");
        }
    }
    
    4.2 实现:
    package demo317.interface2;
    /*
    注意:不能通过接口实现类的对象来调用接口当中的静态方法。
    正确用法:通过接口名称,直接调用其中的静态方法。
    格式:
    接口名称.静态方法名(参数);
        回顾:之前学过静态方法, 静态方法与对象无关,只跟类有关,所以这里调用接口的静态方法。
        是不需要通过实现类的对象来调用的,而是直接调用接口,静态方法名即可
     */
    
    public class Demo03Interface {
        public static void main(String[] args) {
            //创建实现类对象
            MyInterfaceStaticImpl myInterfaceStatic = new MyInterfaceStaticImpl();
            //错误写法
    //        myInterfaceStatic.methodStatic();
            //直接通过接口名称调用静态方法
            MyInterfaceStatic.methodStatic();   //这是接口的静态方法!
    
        }
    }
    
    5.私有方法:
    5.1 定义:
    package demo317.Interface3;
    /*
    问题描述:
        我们需要抽取一个公共方法, 用来解决两个默认方法之间重复代码的问题
        但是这个共有方法不应该让实现类使用,应该是私有化的
    
    解决方案:
    从Java 9 开始,接口当中允许定义私有方法。
    1.普通私有方法,解决多个默认方法之间重复代码问题
    格式:
        private 返回值类型 方法名称(参数列表){
            方法体
        }
    2.静态私有方法,解决多个静态方法之间重复代码问题
    格式:
        private static 返回值类型 方法名称(参数列表){
            方法体
        }
     */
    
    public interface MyInterfacePrivateA {
        public default void methodDefault1() {
            System.out.println("这是一个默认方法1");
    //        System.out.println("AAA");
    //        System.out.println("BBB");
            //当多个方法有重复代码,我们想到的第一个解决办法就是抽取重复代码单独定义一个方法用于调用
            //这里使用默认方法,会导致接口的实现类也能调用,而我们只希望接口类内部单独调用
            methodCommon();
            //使用私有方法,保证只有本类能调用
            //对于静态私有方法,作用同样生效,这里不再演示
            methodCommondA();
        }
    
        public default void methodDefault2() {
            System.out.println("这是一个默认方法2");
    //        System.out.println("AAA");
    //        System.out.println("BBB");
            methodCommon();
            methodCommondA();
        }
    
        public default void methodCommon() {
            System.out.println("AAA");
            System.out.println("BBB");
        }
    
        private void methodCommondA() {
            System.out.println("AAA");
            System.out.println("BBB");
        }
    }
    -------------------------------------
    package demo317.Interface3;
    
    public class MyInterfacePrivateImpl implements MyInterfacePrivateA {
        public void methodAnother() {
            //直接访问到了接口中的默认方法,这样是不应该的
            methodCommon();
            //而调用私有方法,会报错,保证了接口方法的私有性
    //        methodCommonA();
        }
    }
    
    6.接口的常量定义和使用:
    package demo317.Interface3;
    /**
     * 接口当中也可以定义"成员变量",但是必须使用public static final三个关键字进行修饰
     *从效果上看,这其实就是接口的【常量】
     * 格式:
     *  public static final 数据类型 常量名称 = 数据值;
     *  一旦使用final关键字进行修饰,说明不可改变。
     *
     * 注意:
     *  1.接口当中的常量,可以省略public static final,注意:不写其内部也照样是这样定义
     *  2.接口当中的常量,必须进行赋值,不能不赋值
     *  3.命名规范,完全大写,单词间用_连接
     *
     * 使用:接口名称.常量名称 如:MyInterfaceConst.NUM_OF_MY_CLASS
     *
     */
    
    public interface MyInterfaceConst {
        // 这其实就是一个常量,一旦赋值,不可以修改
        public static final int NUM_OF_MY_CLASS = 10;
        //错误,成员变量可以,接口常量不可以
    //    public static final int num;
    }
    
    7.接口内容小结:
    在Java 9+版本中,接口的内容可以有:
        1.成员变量其实是常量,格式:(中括号内容表示可以省略)
            [public] [static] [final] 数据类型 常量名称 = 数据值;
            注意:常量必须进行赋值,而且一旦赋值不能改变。常量名称完全大写,用下划线进行分隔
            
        2.接口中最重要的就是抽象方法,格式:
        [public] [abstract] 返回值类型 方法名称(参数列表);
        注意:
            实现类必须覆盖重写接口所有抽象方法,除非实现类是抽象类。
            
        3.从JAVA 8开始,接口里与允许定义默认方法,格式:
        [public] default 返回值类型 方法名称(参数列表){ 方法体 }
        注意:默认方法也可以被覆盖重写
        
        4.从Java8 开始,接口里允许定义静态方法,格式:
        [public] static 返回值类型 方法名称(参数列表){ 方法体 }
        注意:
            应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
            
        5. 从Java 9 开始,接口里允许定义私有方法,格式:
        普通私有私有方法: private 返回值类型 方法名称(参数列表){方法体}
        静态私有方法:private static 返回值类型 方法名称(参数列表){}
            注意:
                private的方法只有接口自己才能调用,不能被实现类或别人使用。
            
            
    8. 继承父类并实现多个接口注意事项:
    package demo318.interface1;
    
    import demo317.Interface3.MyInterfacePrivateA;
    
    /**
     * 使用接口的时候,需要注意:
     * 1.接口是没有静态代码块或者构造方法的
     * 2.一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
     * 格式:
     *  public calss MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
     *      //覆盖重写所有抽象方法
     *  }
     *  3.如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
     *  4.如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类必须是抽象类
     *  5.如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写
     *  6.一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法
     *
     */
    public interface Demo01Interface {
        //错误写法,接口不能有静态代码块
    //    static {
    //
    //    }
        //错误,接口不能有构造方法
    //    public Demo01Interface(){
    //
    //    }
    }
    
    9.接口之间的多继承关系:
    概念:
    package demo318.interface1;
    
    /**
     * 1.类与类之间是单继承的。直接父类只有一个
     * 2.类与接口之间是多实现的。一个类可以实现多个接口
     * 3.接口与接口之间是多继承的。
     *
     * 注意事项:
     *  1.多个父接口当中的抽象方法,如果重复没关系
     *  2.多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,而且要带default关键字
     */
    public class Demo01Relations {
    }
    
    父接口1package demo318.interface1;
    
    public interface MyInterface1 {
        public abstract void methodA();
    
        public abstract void methodCommon();
    
        public default void methodDefault(){
            System.out.println("111111111");
        }
    }
    
    父接口2package demo318.interface1;
    
    public interface MyInterface2 {
        public abstract void methodB();
    
        public abstract void methodCommon();
    
        public default void methodDefault(){
            System.out.println("222222");
        }
    
    }
    
    子接口:
    package demo318.interface1;
    
    /**
     * 这个子接口当中有几个方法?答:4个
     *  methodA 来源于接口1
     *  methodB 来源于接口2
     *  method 来源于自己
     *  methodCommon同时来源于接口1和2
     */
    public interface MyInterface extends MyInterface1, MyInterface2 {
        public abstract void method();
    
        @Override
        default void methodDefault() {
    
        }
    }
    
    
    

    三、多态

    1.多态的简单实现:
    extends继承或者implements实现,是多态性的前提
    如学生即是一个学生,也是一个人类
    定义:父类
    package demo318.Multi.Demo01;
    
    public class Fu {
        public void method(){
            System.out.println("父类方法");
        }
        public void methodFu(){
            System.out.println("这是一个父类特有方法");
        }
    }
     -----------------------------
    子类:
    package demo318.Multi.Demo01;
    
    public class Zi extends Fu{
        @Override
        public void method() {
            System.out.println("子类方法");
        }
    }
     -------------------------------
    使用:
    package demo318.Multi.Demo01;
    /**
    代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
     格式:
        父类名称 对象名 = new 子类名称();
     或者:
        接口名称 对象名 = new 实现类名称();
     */
    public class Multi {
        public static void main(String[] args) {
            //使用多态的写法
            //左侧父类的引用,指向了右侧子类的对象
            Fu obj = new Zi();
    
            obj.method();   //子类方法
            obj.methodFu(); //这是一个父类特有对象
        }
    }
    ------------------------------------------------------
    2.多态中成员变量的使用特点:(与继承时规则一致)
     package demo318.Multi.Demo01;
    
    /**
     访问成员变量的两种方式:
     1.直接通过对象名称访问: 看等号左边是谁,优先用谁,没有则向上找
        如Zi类继承Fu类,各自定义了一个int num变量,那么通过多态的写法,对象.变量名结果则是父类的变量
     2.间接通过成员方法访问: 看该方法属于谁,优先用谁,没有则向上找。
        注意:Fu obj = new Zi(); 这样的多态写法,生成的对象obj实际上还是Zi类对象,所以成员方法优先找子类。而成员变量则是要看左边
        那么如果调用obj.成员方法(),如果子类里没有覆盖重写,则是调用父类,如果覆盖重写则是调用子类
     */
    public class Demo02MultiField {
    }
    多态代码访问规则:
    /**
     在多态的代码中,成员方法的访问规则是:
        看New的是谁,就优先用谁,没有则向上找。
    
     口诀:编译看左边,运行看右边。
     对比:
        成员变量:编译看左边,运行还看左边
        成员方法:编译看左边,运行看右边
     */
     -------------------------------
     3.使用多态的好处:
     假设家中有宠物一猫一狗,猫吃鱼,狗吃骨头,那么铲屎官的就需要关心各自的喜好。如果使用多态,使用一个方法调用如下:
     有父类Animal,继承的子类Dog 和 Cat各自有方法 eat();
     编写一个类Master,调用方法
     public void feed(Animal an){
         an.eat();
     }
     在使用这个类的对象时,直接把对应的宠物对象传入即可,不再需要单独的Cat.eat()或者 Dog.eat();
     直接master.feed(new Dog()) 或者
     master.feed(new Cat())
    ------------------------------
     4.对象的向上转型:
        - 对象的向上转型,其实就是多态写法:
        格式:父类名称 对象名 = new 子类名称();  Animal animal = new Cat();
        含义:右侧创建一个子类对象,把它当做父类来看待使用。
            创建了一只猫,当做动物看待
        注意事项:向上转型一定是安全的。从小范围转向大范围 
            类似于:double num = 100; //正确,int --> double,自动类型转换。
     5.对象的向下转型:
        5.1定义并继承:
    package demo318.Multi.Demo01;
    
    public abstract class Animal {
        public abstract void eat();
    }
    ------------
    package demo318.Multi.Demo01;
    
    public class Cat extends Animal {
    
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    
        public void catchMouse(){
            System.out.println("猫抓老鼠");
        }
    }
        5.2 调用:
    package demo318.Multi.Demo01;
    
    /**
     向上转型一定是安全的,正确的,但是也有一个弊端:
        对象一旦向上转型为父类,那么久无法调用子类原本特有的内容。
    
     解决方案:用对象的向下转型【还原】。
     格式:
        子类名称 对象名 = (子类名称)父类对象;
     含义:将父类对象,还原成为本来的子类对象
     注意事项:
        必须保证对象原本创建的时候是这个对象,才能向下转型为这个对象
     */
    public class Demo01Main {
        public static void main(String[] args) {
            Animal an = new Cat();
            an.eat();   //猫吃鱼
    
    //        an.catchMouse();  //错误写法
            ((Cat) an).catchMouse();    //正确写法
    //        ((Dog) an).catchMouse();    //错误写法。java.lang.ClassCastException,类转换异常
    
        }
    
    }
    ---------------------------------------------
    6. instanceof关键字进行类型判断:
    package demo318.Multi.Demo01;
    
    /**
     * 如何才能知道一个父类引用的对象,本来是什么类?
     * 格式:
     *  对象 instanceof 类名称
     *  这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实力
     */
    public class Demo02Instanceof {
        public static void main(String[] args) {
            Animal animal = new Cat(); //本来是一只猫
            animal.eat();
            //如果希望调用子类特有方法,需要向下转型
            //使用instanceof 判断对象原本类型
            if (animal instanceof Cat) {
                ((Cat) animal).catchMouse();
            }
        }
    
    
    }
    -------------------------------
    7.笔记本USB接口案例分析(多态)
    USB接口:拥有打开关闭设备的功能
    笔记本电脑:拥有开关机功能,且能使用USB设备
    鼠标:实现USB接口功能,并且可以点击
    键盘:实现USB接口功能,并且可以敲击打字
    
    首先定义USB接口:
    package demo318.USBDemo;
    
    public interface UsbInterface {
        //打开设备
        public abstract void openDevice();
    
        // 关闭设备
        public abstract void closeDevice();
    
    }
    ---------------
    鼠标实现,并自定义点击功能:
    package demo318.USBDemo;
    
    //鼠标就是一个USB设备
    public class Mouse implements UsbInterface {
        @Override
        public void openDevice() {
            System.out.println("鼠标打开设备");
        }
    
        @Override
        public void closeDevice() {
            System.out.println("鼠标关闭设备");
        }
    
        public void mouseClick() {
            System.out.println("鼠标点击");
        }
    }
    -----------------------
    键盘实现,并自定义打字功能:
    package demo318.USBDemo;
    
    //键盘也是USB设备
    public class Keyboard implements UsbInterface {
        @Override
        public void openDevice() {
            System.out.println("打开键盘设备");
        }
    
        @Override
        public void closeDevice() {
            System.out.println("关闭键盘设备");
        }
    
        public void keyboardClick() {
            System.out.println("键盘敲击");
        }
    }
    -----------------------
    笔记本电脑定义,并使用USB设备:
    package demo318.USBDemo;
    
    public class Laptop {
        public void powerOn() {
            System.out.println("笔记本电脑开机");
        }
    
        public void powerOff() {
            System.out.println("笔记本电脑关机");
        }
    
        //使用USB设备的方法,使用接口作为方法参数
        public void useUsb(UsbInterface device) {
            device.closeDevice();   //打开设备
            device.openDevice();    //关闭设备
            if (device instanceof Mouse) {
                ((Mouse) device).mouseClick();
            } else if (device instanceof Keyboard) {
                ((Keyboard) device).keyboardClick();
            }
        }
    }
    -----------------------
    最后,使用笔记本电脑:
    package demo318.USBDemo;
    
    public class DemoMain {
        public static void main(String[] args) {
            //首先创建一个笔记本电脑
            Laptop laptop = new Laptop();
            //笔记本电脑正常的开关机操作
            laptop.powerOff();  //笔记本电脑关机
            laptop.powerOn();   //笔记本电脑开机
            //笔记本电脑使用usb接口
            //使用键盘,方法参数是USB类型,传递进去的是实现类对象,发生了向上转型
            laptop.useUsb(new Keyboard());  //关闭键盘设备 打开键盘设备 键盘敲击
            //使用鼠标
            laptop.useUsb(new Mouse());     //鼠标关闭设备 鼠标打开设备 鼠标点击
        }
    }
    
    
        
    

    四:final关键字

    说明:
    package demo318.DemoFinal;
    
    /**
     * final关键字代表最终、不可改变的。
     * 常见四种用法:
     *  1.可以用来修饰一个类
     *  2.可以用来修饰一个方法
     *  3.修饰一个局部变量
     *  4.修饰一个成员变量
     */
    public class Demo01final {
    }
    ----------------------------
    1.修饰类:
    package demo318.DemoFinal;
    
    /**
     * 当final关键字用来修饰一个类的时候,格式:
     *  public final class 类名称{
     *      //.....
     *  }
     *  含义:当前这个类不能有任何的子类。(太监类,有父无子)
     *  注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没有继承它的类)
     *
     */
    public final class MyClass {
        public void method(){
            System.out.println("方法执行");
        }
    
    }
    -----------------------------
    2.修饰方法: 
    package demo318.DemoFinal;
    
    /**
     * 当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写
     * 格式:
     *  修饰符 final 返回值类型 方法名称(参数列表){
     *      //方法体
     *  }
     *  注意事项:
     *      对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。(abstract规则一定要覆盖重写,而
     *      对于final是不能被覆盖重写)
     *
     */
    public class Fu {
        public final void method() {
            System.out.println("父类方法执行");
        }
    }
    
    package demo318.DemoFinal;
    
    public class Zi extends Fu {
        //错误写法,不能覆盖重写父类当中final的方法
    //    @Override
    //    public void method() {
    //        System.out.println("子类覆盖重写父类的方法");
    //    }
    }
    
    -------------------------
    3.修饰局部变量:
    package demo318.DemoFinal;
    
    public class Demo01final {
    
        public static void main(String[] args) {
            int num = 10;
            num = 20;
    
            //一旦使用final用来修饰局部变量,那么这个变量就不能进行更改
            //“一次赋值,终生不变”
            final int num2 = 200;
    //        num2 = 20;    //编译报错
    
            //正确写法!只要保证有唯一一次赋值即可
            final int num3;
            num3 = 30;
    
            //对于基本类型来说,不可变说的是变量当中的数据不可改变
            //对于引用类型来说,不可变说的是变量当中的地址值不可改变
            Zi zi = new Zi();
            zi = new Zi();  //正确写法
            final Zi zi1 = new Zi();
    //        zi1 = new Zi();   //错误写法,不可变
        }
    
    }
    -----------------------------------
    4.修饰成员变量:
    package demo318.DemoFinal;
    
    /**
     * 对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
     * 1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,就不会再给默认值了
     * 2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一
     * 3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值
     */
    public class Person {
        private final String name;  //如果这里这么写,那么必须保证无参与有参构造方法都执行赋值
    //    private final String name = "刘德华";  //正确,与上边结合的构造方法二选一
    
        public Person() {
            name = "郑学友";
        }
    
        public Person(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        //set是二次赋值,不能再使用
    //    public void setName(String name) {
    //        this.name = name;
    //    }
    }
    
    
    

    五、四种权限修饰符:

    package demo318.decorate;
    
    /**
     * JAVA 中有四种权限修饰符:左边纵列表示范围,右边第一行表示使用的变量修饰符
     * 如在同一个类中,不管用什么修饰符修饰变量,都可以调用此变量
     * 而不同包又无继承关系下,只有public修饰的变量能被类名.变量名调用
     *                 public > protected > (default) > private
     *  同一个类        YES      YES            YES         YES         (直接使用 .变量名)
     *  同一个包        YES     YES             YES         NO          (类.变量名)
     *  不同包子类      YES      YES             NO          NO          (super.变量名)
     *  不同包非子类    YES       NO            NO           NO           (类.变量名)
     *  注意事项:(default)并不是关键字"default",而是根本不写
     */
    public class Demo01Main {
    
    
    }
    
    
    

    六、内部类

    package demo318.InnerClass;
    
    /**
     * 如果一个事物的内部包含另一个事物,也就是一个类内部包含另一个类
     * 例如:身体和心脏的关系。又如:汽车和发动机的关系
     *
     * 分类:
     * 1.成员内部类
     * 2.局部内部类(包含匿名内部类)
     *  1.成员内部类定义格式:
     *      修饰符 class 外部类名称 {
     *          修饰符 class 内部类名称 {
     *              //...
     *          }
     *          //....
     *      }
     *      注意:内用外,可以随意访问;外用内,需要内部类对象
     * 如何使用成员内部类?有两种方式:
     * 1.间接使用:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
     * 2.直接方式:公式:
     *  外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
     *
     *==========================================
     *
     *
     */
     1.1定义:
     package demo318.InnerClass;
    
    public class Body {     //外部类
        public  class Heart {    //成员内部类
            //内部类方法
            public void beat(){
                System.out.println("心脏跳动:蹦蹦蹦");
                System.out.println("我叫:" + name);   //正确写法!
            }
        }
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        //外部类方法
        public void methodBody(){
            System.out.println("外部类方法");
            new Heart().beat();       //间接使用内部类方法
        }
    }
    -------------------------------
    2.使用:
    public class Demo01InnerClass {
        public static void main(String[] args) {
            Body body = new Body(); //外部类的对象
            body.methodBody();  //通过外部类的对象,调用外部类的方法,里面姐姐在使用内部类Heart
    
            //使用直接方式调用
            Body.Heart heart = new Body().new Heart();
            heart.beat();
        }
    
    }
    ------------------------------
    2.内部类的同名变量访问:
    定义:
    package demo318.InnerClass;
    
    /**
     * 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
     */
    
    public class Outer {
    
        int num = 10; //外部类成员变量
    
        public class Inner{
            int num = 20;   //内部类的成员变量
            public void methodInner() {
                int num =30;    //内部类方法的局部变量
                System.out.println(num);    //输出局部变量,就近原则
                System.out.println(this.num);   //内部本类成员变量,20
    //            System.out.println(super.num);    //错误,因为内部跟外部并不是继承关系
                System.out.println(Outer.this.num);     //正确方法,通过类名称定位到外部类
            }
        }
    }
    -------
    
    使用:
    package demo318.InnerClass;
    
    public class Demo02InnerClass {
        public static void main(String[] args) {
            Outer.Inner inner = new Outer().new Inner();
            inner.methodInner();
    
        }
    }
    --------------------------------
    3.局部内部类的定义:
    package demo318.InnerClass.jubu;
    
    /**
     * 如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
     * “局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
     * 定义格式:
     *  修饰符 class 外部类名称 {
     *      修饰符 返回值类型 外部类方法名称(参数列表){
     *          class 局部内部类名称 {
     *              // ...
     *          }
     *      }
     *  }
     *
     * 小结类的权限修饰符:
     * public > protected > (default) > private
     * 定义一个类的时候,权限修饰符规则:
     *  1.外部类:public / (default)
     *  2.成员内部类:public / (default) / protected / private
     *  3.局部内部类:什么都不能写,不等于(default)
     */
    public class Outer {
    
        public void methodOuter(){
            //定义局部内部类,不能再用public修饰
            class Inner{
                int num = 10;
                public void methodInner(){
                    System.out.println(num);    //局部内部类变量,10
                }
            }
            Inner iner = new Inner();
            iner.methodInner();
        }
    }
    使用:
    package demo318.InnerClass.jubu;
    
    public class DemoMain {
        public static void main(String[] args) {
            Outer obj = new Outer();
            obj.methodOuter();  //实质上就是调用一个普通的外部类对象的方法,方法里恰好使用了局部内部类的变量跟方法
        }
    }
    ------------------------------.
    4. 局部内部类的final问题:
    package demo318.InnerClass.jubu;
    
    /**
     * 局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的。
     * 从JAVA 8+开始,只要局部变量事实不变,那么final关键字可以省略。
     *  原因:
     *      1.new出来的对象在堆内存中,
     *      2.局部变量是跟着方法走的,在栈内存当中
     *      3.方法运行结束之后,立刻出栈,局部变量就会立刻消失。
     *      4.new出来的对象会在堆当中持续存在,知道垃圾回收消失
     *  猜测:因此,在下方示例中,MyInner对象的存活时间理论上会比变量num更长,那么为了不使程序出错,
     *  num就需要时final不可变的,相当于一个常量,这样MyInner对象可以copy一份常量用于保存使用。
     *
     */
    public class MyOut {
        public void methodOuter(){
            int num = 10;   //所在方法的局部变量
            class MyInner{
                public void methodInner(){
                    System.out.println(num);
                }
            }
        }
    }
    ------------------------------
    5.匿名内部类
    接口:
    package demo319.inner;
    
    
    public interface MyInterface {
        //接口中抽象方法可以省略public abstract
        void method();
    }
    常规实现:
    package demo319.inner;
    
    public class MyInterfaceImpl implements MyInterface {
        @Override
        public void method() {
            System.out.println("实现类覆盖重写了方法");
        }
    }
    使用匿名内部类的实现:
    package demo319.inner;
    
    /**
     *如果接口的实现类或者是父类的子类 只需要使用唯一的一次
     * 那么这种情况下就可以省略该类的定义,而改为使用【匿名内部类】
     *
     * 匿名内部类的定义格式:
     *  接口名称 对象名 = new 接口名称() {
     *      // 覆盖重写所有抽象方法
     *  }
     *对格式“new 接口名称 () {...}”进行解析:
     * 1. new代表创建对象的动作
     * 2. 接口名称就是匿名内部类需要实现哪个接口
     * 3. {...}这才是匿名内部类的内容
     * 注意:
     *  1. 匿名内部类,在创建对象的时候只能使用唯一一次
     *      如果希望多次创建对象,并且类的内容一样的话,那么应该使用单独定义的实现类
     *
     */
    public class DemoMain {
        public static void main(String[] args) {
            //常规方式使用实现类
    //        MyInterface obj = new MyInterfaceImpl();
    //        obj.method();
            //使用匿名内部类
            MyInterface obj = new MyInterface() {
                @Override
                public void method() {
                    System.out.println("匿名内部类实现了方法");
                }
            };
            obj.method();
        }
    }
    ------------------------------
    6.类作为成员变量类型
    定义:
        6.1 英雄类,使用武器类作为成员变量
    package demo319.inner;
    
    //游戏当中的英雄角色
    public class Hero {
        private String name; //英雄角色
        private int age;
        private Weapon weapon;  //武器
    
        public Hero(String name, int age, Weapon weapon) {
            this.name = name;
            this.age = age;
            this.weapon = weapon;
        }
    
        public Hero() {
        }
    
        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 Weapon getWeapon() {
            return weapon;
        }
    
        public void setWeapon(Weapon weapon) {
            this.weapon = weapon;
        }
    
        public void attack() {
            System.out.println(this.age + "岁的" + this.name + "正在用" + this.weapon.getCode() + "攻击敌方");
        }
    }
    --------
        6.2武器类:
    package demo319.inner;
    
    public class Weapon {
        private String code;    //武器代号
    
        public Weapon() {
        }
    
        public Weapon(String code) {
            this.code = code;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    }
    -----
    6.3 使用英雄类:
    package demo319.inner;
    
    public class Demo02Main {
        public static void main(String[] args) {
            //创建一个英雄角色:
            Hero hero = new Hero();
            // 起名并设置年龄
            hero.setAge(18);
            hero.setName("伊利丹怒风");
            //创建一个武器对象
            Weapon weapon = new Weapon("霜之哀伤");
            //添加英雄武器
            hero.setWeapon(weapon);
            //使用英雄技能攻击
            hero.attack();  //18岁的伊利丹怒风正在用霜之哀伤攻击敌方
    
        }
    }
    -----------------------------------
    7.接口作为成员变量类型:
    定义英雄类,使用技能接口作为成员变量:
    package demo319.inner.InterfaceHero;
    
    public class Hero {
        private String name;
        private Skill skill;    //英雄的法术技能
    
        public Hero() {
        }
    
        public Hero(String name, Skill skill) {
            this.name = name;
            this.skill = skill;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Skill getSkill() {
            return skill;
        }
    
        public void setSkill(Skill skill) {
            this.skill = skill;
        }
    
        public void attack() {
            System.out.println(name + "开始释放技能:");
            skill.use();    //调用接口的抽象方法
            System.out.println("技能释放完毕");
        }
    }
    ------
    定义技能接口:
    package demo319.inner.InterfaceHero;
    
    public interface Skill {
        void use(); //释放技能的抽象方法
    }
    ------
    调用英雄类并实现接口:
    package demo319.inner.InterfaceHero;
    
    public class DemoGame {
        public static void main(String[] args) {
            Hero hero = new Hero();
            hero.setName("卡尔"); //设置英雄名称
    
            //设置英雄技能,这里使用匿名内部类实现(也可以使用接口实现类)
            Skill skill = new Skill() {
                @Override
                public void use() {
                    System.out.println("使用毁天灭地,Pong!");
                }
            };
            hero.setSkill(skill);
            hero.attack();
            /*卡尔开始释放技能:
            使用天崩地裂,Pong!
            技能释放完毕*/
    
            //进一步简化,同时使用匿名内部类和匿名对象
            hero.setSkill(new Skill() {
                @Override
                public void use() {
                    System.out.println("使用电磁脉冲,PANG!");
                }
            });
            hero.attack();
        }
    }
    ------------------------------------------------------
    8.接口作为方法的参数或者返回值:
    package demo319.inner.InterfaceHero;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class DemoInterface {
        public static void main(String[] args) {
    //        ArrayList<String> list = new ArrayList<>();
            List<String> list = new ArrayList<>();  //多态写法,ArrayList正是List的实现类
            List<String> result = addNames(list);
            //循环结果验证是否成功,结果正确
            for (int i = 0; i < result.size(); i++) {
                System.out.println(result.get(i));
            }
        }
        //这里实现了使用接口List作为参数与返回值
        public static List<String> addNames(List<String> list) {
            list.add("刘德华");
            list.add("张学友");
            return list;
        }
    }
    -------------------------------------------------------------
    9.发红包案例分析(进阶版):
    1. 定义红包生成接口:
    package demo319.inner.redMoney;
    
    import java.util.ArrayList;
    
    public interface OpenMode {
        /**
         * 请将totalMoney分成count份,保存到ArrayList<Interger>中,返回即可
         *
         * @param totaoMoney:总金额,为方便计算,转换为整数,单位为分
         * @param totalCount:                     红包个数
         * @return ArrayList<Integer> 元素为各个红包的金额,所有值累加为总金额
         */
        //抽象方法
        ArrayList<Integer> divide(int totaoMoney, int totalCount);
    }
    ----------------
    2.普通红包生成实现类:
    package demo319.inner.redMoney;
    
    import java.util.ArrayList;
    
    public class NormalMode implements OpenMode {
        @Override
        public ArrayList<Integer> divide(int totalMoney, int totalCount) {
            ArrayList<Integer> list = new ArrayList<>();
            //平均红包,零头放在最后一个
            int avg = totalMoney / totalCount;
            int mod = totalMoney % totalCount;
    
            for (int i = 0; i < totalCount - 1; i++) {
                list.add(avg);
            }
            //上边少放一个红包,这里加入最后一个红包,算上多余的钱
            list.add(avg + mod);
    
            return list;
        }
    }
    ----------------------------------
    3.随机红包生成实现类:
    package demo319.inner.redMoney;
    
    import java.util.ArrayList;
    import java.util.Random;
    
    public class RandomMoney implements OpenMode {
        @Override
        public ArrayList<Integer> divide(final int totalMoney, final int totalCount) {
            ArrayList<Integer> list = new ArrayList<>();
            //随机红包,有可能多有可能少
            //最少1分钱,最多不超过"“剩下金额平均数的2倍”
            //假如发10块钱,3个人,也就是1000分,那么第一次红包随机范围是1--666分(剩下大概333//第一次发完,剩下至少是334分。
            //此时还需要再发2个红包
            //此时的再发范围应该是1分-334(random取不到右边,剩下1分)
            //总结一下,范围的公式是:1+ random.nexInt(leftMoney / leftCount*2);
            Random random = new Random();
            //定义2个变量,分别代表剩下多少钱,多少份
            int leftMoney = totalMoney;
            int leftCount = totalCount;
            //随机发前N-1个,最后一个不需要随机
            for (int i = 0; i < totalCount - 1; i++) {
                //按照公式生成随机金额
                int money = random.nextInt(leftMoney / leftCount * 2) + 1;
                list.add(money);
                leftMoney -= money; //总金额减去发出的金额
                leftCount--;   //剩下红包个数递减
            }
            list.add(leftMoney);    //剩下的为最后一个红包
    
            return list;
        }
    }
    -----------------------------------
    4.调用实现类获取红包,发红包方法暂不写:
    package demo319.inner.redMoney;
    
    import java.util.ArrayList;
    
    /**
     * 场景说明:
     * 红包发出去之后,所有人都有红包,大家抢完之后, 最后一个红包给群主自己。
     * <p>
     * 红包分发的策略:
     * 1.普通红包(平均)  totalMoney / totalCount, 余数放在最后一个红包
     * 2.手机红包(随机)  最少1分钱,最多不超过平均数的2倍。应该越发越少
     */
    public class Bootstrap {
        public static void main(String[] args) {
            NormalMode normalMode = new NormalMode();
            // 获得红包集合
            ArrayList<Integer> redList = normalMode.divide(1000, 7);  //
            System.out.println(redList);    //[142, 142, 142, 142, 142, 142, 148]
            // 获得随机红包集合:
            RandomMoney randomMoney = new RandomMoney();
            ArrayList<Integer> randomlist = randomMoney.divide(1000, 7);
            System.out.println(randomlist); //[126, 98, 111, 296, 27, 76, 266]
    
        }
    }
  • 相关阅读:
    H5 video播放视频遇到的问题
    IIS域名转发
    IIS Tomcat共享80端口
    C# 操作注册表WindowsRegistry
    Owin Middleware如何在IIS集成管道中执行
    如何定义一个有效的OWIN Startup Class
    mysql 数据库的备份与还原 at winows
    windows查看端口占用
    asp.net 二级域名表单认证情况下共享Cookie
    c# 多线程使用队列顺序写日志的类 (需要再优化)
  • 原文地址:https://www.cnblogs.com/mitsui/p/10559710.html
Copyright © 2020-2023  润新知