黑马程序员技术交流社区

标题: 【石家庄校区】基础班day11-final,权限,内部类 [打印本页]

作者: xiekai_sjz    时间: 2018-9-9 10:19
标题: 【石家庄校区】基础班day11-final,权限,内部类
本帖最后由 xiekai_sjz 于 2018-9-9 10:30 编辑

day11 final, 权限, 内部类    今天我们来学习面向对象中其他的相关知识。首先是final关键字的使用,然后是java中权限修饰符,最后是内部类的分类和应用,最后我们会优化我们之前的发红包案例。
以下是今天的学习目标:


以下是今天的详细笔记:


final/权限修饰符final关键字概念与四种用法final: 关键字, 最终的意思
4种使用方式:
    1. 修饰一个类
    2. 修饰一个方法
    3. 修饰一个局部变量
    4. 修饰一个成员变量

final关键字用于修饰类
final修饰类的作用:
        该类不能被继承
        
        // final类
    public final class 类名称 {
        // ...
    }
final关键字用于修饰成员方法
final修饰成员方法的作用:
        该方法不能被覆盖重写
        
    // final方法
    public final void method() {

    }

final不能与abstract同时使用
        final修饰的类不能被继承, 而abstract修饰的类需要被继承
        final修饰的方法不能被重写, 而abstract修饰的方法需要被重写
final关键字用于修饰局部变量
final修饰局部变量的作用:
        自定义常量, 一旦赋值不能修改

注意:
        对于基本数据类型, 不可变指变量中的"值"不可改变
        对于引用数据类型, 不可变指变量中的"地址值"不可改变
补充:
[Java] 纯文本查看 复制代码
final int num = 10;  // 赋值后不能修改

// 分开赋值也可以
final int num2;
num2 = 20;           // 赋值后不能修改

final Student stu = new Student("高圆圆");
//stu = new Student("赵又廷");    // 错误! 用新地址赋值是不行的
stu.setName("高圆圆圆圆");       // 正确! 地址没变, 只是修改对象中的成员变量的值

final关键字用于修饰成员变量
final修饰成员变量的作用:
        自定义常量, 一旦赋值不能修改

注意:
    1. 成员变量final之后必须手动赋值, 不会再给默认值
    2. final的成员变量, 要么直接赋值, 要么通过构造方法赋值. 二者选其一
    3. 必须保证类当中所有重载的构造方法, 都最终会对final的成员变量进行赋值
补充:
[Java] 纯文本查看 复制代码
public class Person {

    private final String name /*= "鹿晗"*/;    // 声明时直接赋值

    public Person() {
        name = "关晓彤";                       // 在构造内部赋值
    }

    public Person(String name) {
        this.name = name;                      // 通过参数赋值
    }
}

四种权限修饰符
四种权限修饰符 (从大到小):
        public > protected > (default) > private
注意事项:(default)并不是关键字“default”,而是根本不写。
访问范围 public protected (default) private
同一个类 (我自己) YES YESYES YES
同一个包 (我邻居) YESYES YES NO
不同包子类 (我儿子) YESYES NO NO
不同包非子类 (陌生人) YESNO NO NO

内部类内部类的概念与分类内部类: 定义在一个类的内部的类

一个事物在另一个事物内部:
        汽车 内部有 发动机
        人 内部有 心脏

内部类的分类:
    1. 成员内部类
    2. 局部内部类
    3. 匿名内部类 (属于局部内部类的一种)
成员内部类的定义
成员内部类的定义格式:
        定义在成员位置

    修饰符 class 外部类名称 {

        修饰符 class 成员内部类名称 {
            // ...
        }

        // 其他成员如 成员变量, 成员方法, 构造方法...

    }

注意事项:
        内部类和外部类的相互访问规则:
                内部类中使用外部类成员, 随意访问, 包括外部类私有的
                外部类中使用内部类成员, 需要通过内部类对象
        内部类在编译后, 也会生成独立的字节码文件. 文件名规则: 外部类名$内部类名.class
成员内部类的使用
使用成员内部类有2种方式:
        1. 间接方式: 在外部类的方法当中, 创建内部类对象调用方法; 然后main只是调用外部类的方法
        2. 直接方式: 在其他类当中, 创建对象
                外部类名.内部类名 对象名 = new 外部类名().new 内部类名();

为什么先要创建内部类对象?
        因为Heart类是非静态的成员内部类, 非静态的成员生命周期跟随对象, 所以创建了外部类对象, 才有非静态成员内部类的存在
补充:
[Java] 纯文本查看 复制代码
// 外部类
public class Body {
        // 内部类
    public class Heart {
        public void beat() {
                System.out.println("心脏跳动...");
        }
    }
   
    // 方式1: 在外部类的方法中创建内部类对象
    public void methodBody() {
        // 创建内部类对象并调用方法
        new Heart().beat();
    }
}

// 其他类
public class Test {
    public static void main(String[] args) {
        // 方式1: 创建外部类对象, 调用方法, 间接在方法中创建内部类对象
        new Body().methodBody();
        
        // 方式2: 外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}

5分钟练习: 创建内部类对象并调用方法
需求:
定义类: 身体 Body. 其中包含:
        成员内部类: 心脏 Heart. 其中包含:
                心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
        成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
定义测试类
        使用间接方式1: 创建Body对象, 调用methodBody()
        使用直接方式2: 创建Heart对象, 调用beat()方法
代码:
[Java] 纯文本查看 复制代码
/*
定义类: 身体 Body. 其中包含:
        成员内部类: 心脏 Heart. 其中包含:
                心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
        成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
*/
public class Body {       // 身体

    // 成员内部类
    public class Heart {  // 心脏
        // 内部类的成员方法: 心脏跳动
        public void beat() {
            System.out.println("心脏跳动");
        }
    }

    // 外部类的成员方法: 创建一个心脏, 让其跳动
    public void methodBody() {
        new Heart().beat();
    }

}

/*
需求:
定义类: 身体 Body. 其中包含:
        成员内部类: 心脏 Heart. 其中包含:
                心脏跳动的方法: beat(). 方法内部打印"心脏跳动"
        成员方法: methodBody(). 方法内部创建Heart对象, 并调用beat()方法
定义测试类
        使用间接方式1: 创建Body对象, 调用methodBody()
        使用直接方式2: 创建Heart对象, 调用beat()方法
*/
public class Test {
    public static void main(String[] args) {
        // 间接方式: 先创建Body, 然后通过Body的方法间接创建内部类Heart
        Body body = new Body();
        body.methodBody();

        // 直接方式: 外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}

内部类的同名变量访问
内部类访问外部类非静态成员变量格式:
        外部类名称.this.外部类成员变量名
补充:
[Java] 纯文本查看 复制代码
// 外部类
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);       // 内部类的成员变量
            System.out.println(Outer.this.num); // 外部类的成员变量
        }
    }
}

局部内部类定义
局部内部类: 定义在方法中的类

定义格式:

    修饰符 class 外部类名称 {

        修饰符 返回值类型 外部类方法名称(参数列表) {
                        // 定义在方法中
            class 局部内部类名称 {
                // ...
            }

        }
    }

局部内部类出了方法就不能使用了

定义一个类的时候,权限修饰符规则:
        1. 外部类: public / (default)
        2. 成员内部类: public / protected / (default) / private
        3. 局部内部类: 什么都不能写
补充:
[Java] 纯文本查看 复制代码
class Outer {
   
        // 方法
    public void methodOuter() {
        int num = 10;
        
            // 局部内部类
        class Inner {
            public void methodInner() {
                int num = 20;
                System.out.println(num); // 20
            }
        }
                // 在定义完类后, 创建局部内部类对象
        Inner inner = new Inner();
        inner.methodInner();
    }
}

// 要调用方法才能创建局部内部类的对象
new Outer().methodOuter();

局部内部类的final问题
如果局部内部类访问所在方法的局部变量, 那么这个局部变量必须是"有效final"的
        有效final的:
                Java 8之前, 局部变量需要加"final"关键字, 定义为常量
                Java 8开始, 只要局部变量"事实不变(代码中没有修改值)", 那么final关键字"可以省略"

要求局部变量有效final的原因:
    1. new 出来的局部内部类对象, 在"堆内存"中
    2. 局部变量是跟着方法走的, 在"栈内存"中
    3. 方法运行结束之后, 立刻出栈, 局部变量就会立刻消失
    4. 但 new 出来的局部内部类对象, 会在堆当中持续存在, 直到垃圾回收消失
    5. 所以如果该 局部内部类对象 要想继续使用 局部变量, 则必须延长局部变量的生命周期且保持不变
补充:
[Java] 纯文本查看 复制代码
public class MyOuter {

    public void methodOuter() {
        // 有效final
        int num = 10; // 所在方法的局部变量
        //final int num = 10;

        class MyInner {
            public void methodInner() {
                System.out.println(num);  // 该局部变量声明周期必须足够长且保持不变
            }
        }
    }
}

匿名内部类
匿名内部类的定义格式: (定义在方法中)

    父接口名 对象名 = new 父接口名() {
        // 覆盖重写所有抽象方法
    };

    父类名 对象名 = new 父类名() {
        // 覆盖重写所有抽象方法
    };

        // 还可以创建匿名内部类的匿名对象, 直接调用方法
        new 父接口名() {
        // 覆盖重写所有抽象方法
    }.方法名();

匿名内部类的使用场景:
        如果接口的实现类(父类的子类)对象, 只需要使用唯一的一次, 就可以省略该类的定义, 改为使用匿名内部类
补充:
[Java] 纯文本查看 复制代码
// 创建匿名内部类对象, 并赋值给一个变量
MyInterface objA = new MyInterface() {
    @Override
    public void method1() {
        System.out.println("匿名内部类实现了方法!");
    }
};
// 对象调用方法
objA.method1();

// 创建匿名内部类的匿名对象, 直接调用方法
new MyInterface() {
    @Override
    public void method1() {
        System.out.println("匿名内部类实现了方法!");
    }
}.method1();

匿名内部类的注意事项
new 接口名称() {...}匿名内部类解析:

1. new代表创建对象的动作
2. 接口名称就是匿名内部类需要实现哪个接口
3. {...}是匿名内部类的内容


注意问题:
    1. 匿名内部类, 在创建对象的时候, 只能使用唯一一次
        如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
    2. 匿名对象, 在调用方法的时候, 只能调用唯一一次
        如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
    3. 匿名内部类是省略了实现类/子类名称,但是匿名对象是省略了对象名称
        强调:匿名内部类和匿名对象不是一回事!!!
5分钟练习: 匿名内部类
需求:
定义接口Animal, 接口中定义抽象方法eat();
定义实现类 Cat, 实现Animal接口
        重写eat()方法, 打印"猫吃鱼"
定义测试类
        创建Cat对象, 调用重写的方法
        创建Animal匿名内部类对象, 重写eat()方法, 打印"狗吃肉", 并调用重写的方法
        创建Animal匿名内部类对象, 重写eat()方法, 打印"奥特曼打打小怪兽", 并调用重写的方法
体会匿名内部类相比定义实现类有什么好处?
代码:
[Java] 纯文本查看 复制代码
/*
定义接口Animal, 接口中定义抽象方法eat();
*/
public interface Animal {
    public abstract void eat();
}

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

public class Test {
    public static void main(String[] args) {
        // 普通方式: 创建Cat对象
        Cat cat = new Cat();
        cat.eat();

        // 匿名内部类对象方法  多态
        Animal dog = new Animal() {
            @Override
            public void eat() {
                System.out.println("狗吃肉");
            }

            public void watchHouse() {
                System.out.println("狗看家");
            }
        };
        dog.eat();
        // 无法向下转型: 子类类型 变量名 = (子类类型) 父类对象;
//        dog.watchHouse();

        new Animal() {
            @Override
            public void eat() {
                System.out.println("奥特曼打打小怪兽");
            }

            public void charge() {
                System.out.println("充电");
            }

        }.charge();
    }
}

引用类型的使用类作为成员变量类型
知识点:
一个类的成员变量, 能否使用"类"(引用数据类型) 作为数据类型?
    private 类名 变量名;

如:
[Java] 纯文本查看 复制代码
// 水杯类
public class Cup {
    // 水杯的成员变量, 成员方法, 构造方法...
}  
public class Phone {}

public class Student {
    private int age;
    private String name;   // String是引用类型, 是一个类
    // 学生都有自己的水杯, 怎么定义为成员变量?
    private Cup cup;
    private Phone phone;
    // set/get
}

Student s = new Student();
s.setName("张三");  // "张三"是String类的对象
// 要设置水杯, 如何设置?
s.setCup(new Cup());
s.setPhone(new Phone());


总结:
成员变量的类型, 既可以用基本数据类型, 也可以用引用数据类型
类作为成员变量类型, 成员变量的值实际是该类的对象

5分钟练习: 使用类作为成员变量
需求:
定义类: 武器 Weapon.
        私有属性: String code 表示武器名称
        生成无参有参构造, set/get方法
定义类: 英雄 Hero
        私有属性: 英雄名字String name, 英雄年龄int age, 英雄武器Weapon weapon
        生成无参有参构造, set/get方法
        定义成员方法: attack(), 打印"年龄为XXX的YYY用ZZZ攻击敌方。"
定义测试类
        创建10岁的提莫 Hero对象, 为他设置武器蘑菇炸弹, 让提莫攻击
代码:
[Java] 纯文本查看 复制代码
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;
    }
}

public class Hero {
    private String name;
    private int age;
    private Weapon weapon;

    public Hero() {
    }

    public Hero(String name, int age, Weapon weapon) {
        this.name = name;
        this.age = age;
        this.weapon = weapon;
    }

    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("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建英雄对象
        Hero timo = new Hero();
        timo.setName("提莫");
        timo.setAge(10);

        // 创建武器
        Weapon weapon = new Weapon();
        weapon.setCode("蘑菇炸弹");

        // 为英雄设置武器
        timo.setWeapon(weapon);

        // 开始进攻!
        timo.attack();
    }
}

接口作为成员变量类型
接口作为成员变量的类型, 值实际上是接口的实现类对象
可以利用匿名内部类对象, 简化接口类型成员变量的赋值, 同时提高程序的灵活性
5分钟练习: 接口作为成员变量
需求:
定义接口Skill, 包含抽象方法: 施放技能 use()
定义实现类: 推塔技能 TuiTaSkill, 重写use方法, 打印"人在塔在! 德玛西亚万岁!"
定义类: 英雄 Hero
        私有属性: 英雄姓名String name, 英雄技能Skill skill
        生成无参有参构造, set/get方法
        定义攻击方法: attack(), 方法中执行以下代码:
                打印: "我叫XXX, 开始施放技能:"
                施放技能: skill.use();
                打印: "技能施放完成"
定义测试类:
        创建英雄: 盖伦, 设置技能TuiTaSkill对象, 调用攻击方法让英雄攻击
代码:

[Java] 纯文本查看 复制代码
public interface Skill {
    // 施放技能
    public abstract void use();
}

public class TuiTaSkill implements Skill {
    @Override
    public void use() {
        System.out.println("人在塔在! 德玛西亚万岁!");
    }
}

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("技能施放完成");
    }
}

public class Test {
    public static void main(String[] args) {
        // 创建英雄
        Hero hero = new Hero();
        hero.setName("盖伦");

        // 创建技能
        TuiTaSkill tuiTaSkill = new TuiTaSkill();
        hero.setSkill(tuiTaSkill);

        // 开始进攻
        hero.attack();

        // 创建另一个英雄
        new Hero("奥巴马", new Skill() {
            @Override
            public void use() {
                System.out.println("双枪 Biu Biu Biu~");
            }
        }).attack();
    }
}














接口作为方法的参数和或返回值知识点:
    接口是否可以作为方法的 参数类型 或 返回值类型 ?
    实际传入参数的是接口的什么?
    实际作为返回值返回的是接口的什么?

    如:
    // 使用USB设备
    public USB useDevice(USB usb) {

    }
    参数可以, 返回值是否也可以?

总结:
    接口作为方法参数类型和返回值类型也是可以的, 调用方法时实际执行的还是子类重写的方法    编译看左边, 运行看右边


补充:
[Java] 纯文本查看 复制代码
/*
java.util.List正是ArrayList所实现的接口。
*/
public class DemoInterface {

    public static void main(String[] args) {
        // 左边是接口名称,右边是实现类名称,这就是多态写法
        List<String> list = new ArrayList<>();

        List<String> result = addNames(list);
        for (int i = 0; i < result.size(); i++) {
            System.out.println(result.get(i));
        }
    }

    public static List<String> addNames(List<String> list) {
        list.add("迪丽热巴");
        list.add("古力娜扎");
        list.add("玛尔扎哈");
        list.add("沙扬娜拉");
        return list;
    }
}

发红包案例发红包案例: 分析5分钟练习: 导入代码, 创建基础类需求:
发红包. 红包发出去之后,所有人都有红包,大家抢完了之后,最后一个红包给群主自己
红包分发策略:
        1. 普通红包(平均):
                int avg = totalMoney / count; 平均的钱
                int mod = totalMoney % count; 余数的钱加到最后
        2. 手气红包(随机):
                随机金额: 最少1分钱, 最多不超过平均数的2倍
                越发越少
操作步骤:
已经提供的类:       
        RedPacketFrame: 一个抽象类, 包含了一些属性, 是红包案例的页面
        OpenMode: 一个接口, 包含一个分配方法, 用来指定红包类型

1. 导入图片和代码
        将 pic 目录放入当前模块下
        建包 com.itheima.red , 将 OpenMode.java 和 RedPacketFrame.java 2个文件复制到包中
2. 创建基础类:
        建类 MyRed , 继承 RedPacketFrame 类, 生成有参构造
        建类 Bootstrap , 作为程序启动类:
                定义main方法, 方法中创建 MyRed 对象, 通过构造方法参数设置标题
发红包案例: 普通红包平均分发5分钟练习: 实现普通发红包
我们自己要做的事情有:
    1. 设置一下程序的标题,通过构造方法的字符串参数
    2. 设置群主名称
    3. 设置分发策略:平均,还是随机?
操作步骤:
1. 定义类 NormalMode 表示普通红包模式, 实现 OpenMode 接口
        重写 divide() 方法, 方法内部按照以前平分红包方式编写代码:
                创建ArrayList<Integer>对象, 用于保存分好的钱
                算出平均值和零头:
                        int avg = totalMoney / totalCount; // 平均值
                        int mod = totalMoney % totalCount; // 零头
                循环 totalCount - 1 次, 添加平均值
                最后添加 平均值+零头
                返回红包集合
2. 在 Bootstrap 类中:
        使用MyRed对象调用 setOwnerName(群主姓名), 设置群主姓名
        使用MyRed对象调用 setOpenWay(new NormalMode()), 设置普通发红包模式





代码:
[Java] 纯文本查看 复制代码
/*
普通模式: 平分
*/
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;
    }
}

/*
程序启动类
*/
public class Bootstrap {
    public static void main(String[] args) {
        // 创建MyRed对象
        MyRed myRed = new MyRed("发红包, 发大红包");

        // 设置群主名称
        myRed.setOwnerName("王大锤");
        // 设置红包模式
        myRed.setOpenWay(new NormalMode());
    }
}

发红包案例: 手气红包随机分发5分钟练习: 实现手气红包随机金额: 最少1分钱, 最多不超过平均数的2倍
                平均数: leftMoney / leftCount
                平均数的2倍: leftMoney / leftCount * 2
         1 <= 金额 <= leftMoney / leftCount * 2

生成符合范围的随机金额:
        int money = random.nextInt(leftMoney / leftCount * 2) + 1;
        list.add(money);
每次生成的随机金额要减掉
        leftMoney -= money;
剩余个数也要减少
        leftCount--;
实现步骤:
1. 定义类 RandomMode 表示手气红包模式, 实现 OpenMode 接口
        重写 divide() 方法:
                创建ArrayList<Integer>对象, 用于保存分好的钱
                创建Random对象
                定义变量保存总金额: int leftMoney = totalMoney;
                定义变量保存份数: int leftCount = totalCount;
                循环 totalCount - 1 次, 在循环中:
                        利用公式随机生成金额: int money = random.nextInt(leftMoney / leftCount * 2) + 1;
                        将随机金额添加到集合: list.add(money);
                        从剩余金额中减去该随机金额: leftMoney -= money;
                        从剩余份数中减去该份数: leftCount--;
                循环接收后, 将最后剩下的金额添加到集合: list.add(leftMoney);
                返回集合
2. 在 Bootstrap 类中:
        使用MyRed对象调用 setOpenWay(new RandomMode()), 设置手气发红包模式

代码:
[Java] 纯文本查看 复制代码
public class RandomMode implements OpenMode {
    @Override
    public ArrayList<Integer> divide(int totalMoney, int totalCount) {
        // 创建装钱的红包
        ArrayList<Integer> list = new ArrayList<>();

        // 先将总金额和红包份数存入两个临时变量, 避免直接修改总金额和红包份数导致数据不准确
        int leftMoney = totalMoney;
        int leftCount = totalCount;

        // 除了最后一次, 每次都随机生成金额, 规则: 最少1分钱, 最多不超过平均金额的2倍
        Random random = new Random();
        // 按照总次数-1循环
        for (int i = 0; i < totalCount - 1; i++) {
            // 每次用剩余的金额, 生成随机金额
            int money = random.nextInt(leftMoney / leftCount * 2) + 1;
            // 添加到红包
            list.add(money);
            // 剩余金额要减去本次生成的随机金额
            leftMoney -= money;
            // 红包份数减少1个
            leftCount--;
        }
        // 最后一次剩下的钱, 全都放入红包
        list.add(leftMoney);

        // 返回红包
        return list;
    }
}

/*
程序启动类
*/
public class Bootstrap {
    public static void main(String[] args) {
        // 创建MyRed对象
        MyRed myRed = new MyRed("发红包, 发大红包");

        // 设置群主名称
        myRed.setOwnerName("王大锤");
        // 设置红包模式
//        myRed.setOpenWay(new NormalMode());  // 普通平分模式
        myRed.setOpenWay(new RandomMode());  // 手气随机模式
    }
}
















欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2