第19章 枚举类型

第19章 枚举类型

关键字enum可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用。

所有的枚举类都继承自Enum类,枚举类没办法继承

19.1 基本的enum特性

public enum Shrubbery {
    GROUND, CRAWLING, HANGING,HEHE
}

public class Test {
    public static void main(String[] args) {
        // 返回enum类的实例数组
        Shrubbery[] values = Shrubbery.values();
        for (Shrubbery value : values) {
            // 获得声明时的次序,从0开始
            System.out.println(value+"序数:"+value.ordinal());
            // 比较,返回相对位置(-1,0,1,2)
            System.out.println(value.compareTo(Shrubbery.CRAWLING));
            // 比较两者是否相等,默认重写了equals()和hashcode()
            System.out.println(value == Shrubbery.CRAWLING);
            // 获得其所属的类
            System.out.println(value.getDeclaringClass());
            // 返回enum实例声明时的名字
            System.out.println(value.name());
            System.out.println("-----------------");
        }
    }
}
/*
GROUND序数:0
-1
false
class com.enumerate.Shrubbery
GROUND
-----------------
CRAWLING序数:1
0
true
class com.enumerate.Shrubbery
CRAWLING
-----------------
HANGING序数:2
1
false
class com.enumerate.Shrubbery
HANGING
-----------------
HEHE序数:3
2
false
class com.enumerate.Shrubbery
HEHE
-----------------
*/

静态导入

import static com.enumerate.Shrubbery.*;

public class Test {
    public static void main(String[] args) {
        f(CRAWLING);
    }

    public static void f(Shrubbery s) {
    }
}

19.2 向enum中添加新方法

enum除了不能继承外,可以看作是一个常规的类。

public enum Person {
    // 定义枚举实例
    // 必须在最前
    // 当要定义其他成员时,实例后必须有“;”
    STUDENT("ding"),TEACHER("fu");
    private String name;
    // 定义其他成员
    private Person(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

重写方法

public enum SpaceShip {
    SCOUT,CARGO,TRANSPORT,CRUISER,BATTLESHIP,MOTHERSHIP;

    @Override
    public String toString() {
        String name = name();
        return name.charAt(0)+name.substring(1).toLowerCase();
    }
}

public static void main(String[] args) {
        for (SpaceShip value : SpaceShip.values()) {
            System.out.println(value);
        }
    }
/*
Scout
Cargo
Transport
Cruiser
Battleship
Mothership
*/

19.3 switch语句中的enum

    public static void main(String[] args) {
        SpaceShip spaceShip = SpaceShip.BATTLESHIP;
        // 因为在switch这里已经放置了枚举类型,在case中就不需要指定枚举类型了
        switch (spaceShip) {
            case CARGO:
                System.out.println("1");
                break;
            case SCOUT:
                System.out.println("2");
                break;
            case CRUISER:
                System.out.println(3);
        }
    }

19.4 values()的神秘之处

public class Test {
    public static void main(String[] args) {
        Set<String> subMethods = f(SpaceShip.class);
        Set<String> enumMethods = f(Enum.class);
        System.out.println("子包含父方法:"+subMethods.containsAll(enumMethods));
        System.out.println("移除所有父方法:"+subMethods.removeAll(enumMethods));
        System.out.println(subMethods);
    }

    public static Set<String> f(Class<?> enumClass){
        System.out.println("--分析"+enumClass+"---");
        System.out.println("接口:");
        for (Type t : enumClass.getGenericInterfaces()) {
            System.out.println(t);
        }
        System.out.println("父类:"+enumClass.getSuperclass());
        Set<String> methods = new TreeSet<>();
        for (Method method : enumClass.getMethods()) {
            methods.add(method.getName());
        }
        System.out.println("方法:");
        System.out.println(methods);
        return methods;
    }

}
/*
--分析class com.enumerate.SpaceShip---
接口:
父类:class java.lang.Enum
方法:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, values, wait]
--分析class java.lang.Enum---
接口:
java.lang.Comparable<E>
interface java.io.Serializable
父类:class java.lang.Object
方法:
[compareTo, equals, getClass, getDeclaringClass, hashCode, name, notify, notifyAll, ordinal, toString, valueOf, wait]

子包含父方法:true
移除所有父方法:true
[values]
*/

分析:

  • values()是由编译器添加的static方法;
  • 编译器添加了包含一个参数的valueOf()方法;
  • 编译器会将枚举类标记为final类型;
// 通过Class对象获取所有的枚举实例
// 非枚举类也可以调用,但是只会返回null
public static void main(String[] args) {
        Person student = Person.STUDENT;
        Person[] persons = student.getClass().getEnumConstants();
        for (Person person : persons) {
            System.out.println(person);
        }
    }

19.5 实现,而非继承

public enum Shrubbery implements Factory<Shrubbery> {
    GROUND, CRAWLING, HANGING,HEHE;
    private static Random random = new Random();
    // 随机返回一个实例
    @Override
    public Shrubbery next() {
        return values()[random.nextInt(values().length)];
    }
}

public class Test {
    public static void main(String[] args) {
        // 通过枚举上的一个类来调用枚举上的方法
        Factory<Shrubbery> factory = Shrubbery.CRAWLING;
        for (int i = 0; i < 5; i++) {
            System.out.println(printT(factory));
        }
    }
    public static <T>T printT(Factory<T> factory){
        return factory.next();
    }
}

19.6 随机选取

// 获得随机枚举实例的通用工具类
public class Enums {
    private static Random rand = new Random();
    public static <T extends Enum<T>> T randmEnum(Class<T> enumClass){
        return random(enumClass.getEnumConstants());
    }
    private static <T> T random(T[] values){
        return values[rand.nextInt(values.length)];
    }
}

19.7 使用接口组织枚举

// 使用接口组织枚举,达到将枚举分类的目的
public interface Food {
    enum Fruit implements Food{
        A_FRUIT,B_FRUIT,C_FRUIT;
    }

    enum Rice implements Food{
        A_RICE,B_RICE,C_RICE;
    }
}

创建枚举的枚举

public enum Course {
    // 以枚举Class作为构造器参数
    A_COURSE(Food.Rice.class),B_COURSE(Food.Fruit.class);
    private Food[] foods;
    Course(Class<? extends Food> aClass) {
        foods = aClass.getEnumConstants();
    }

    public Food getFood(){
        return Enums.random(foods);
    }
}

  public static void main(String[] args) {
        Course rice = Course.A_COURSE;
        System.out.println(rice.getFood());
    }

另一种组织枚举的方式

public enum Course {
    A_COURSE(Foods.Fruit.class),B_COURSE(Foods.Rice.class);
    private Foods[] foods;
    Course(Class<? extends Foods> aClass) {
        foods = aClass.getEnumConstants();
    }

    public Foods getFood(){
        return Enums.random(foods);
    }
    // 此种方式与第一种方式区别不大,只是将组织枚举的接口放置在枚举内
    interface Foods {
        enum Fruit implements Foods {
            A_FRUIT,B_FRUIT,C_FRUIT;
        }
        enum Rice implements Foods {
            A_RICE,B_RICE,C_RICE;
        }
    }
}

19.8 使用EnumSet替代标志

EnumSet非常快,内部(可能)使用一个long值作为比特向量。

  public static void main(String[] args) {
        // 创建一个Set,但不添加元素
        EnumSet<Person> enumSet = EnumSet.noneOf(Person.class);
        System.out.println(enumSet); // []
        enumSet.add(Person.TEACHER);
        System.out.println(enumSet); // [TEACHER]
        // // 创建一个Set,但添加所有元素
        enumSet = EnumSet.allOf(Person.class);
        System.out.println(enumSet); // [STUDENT, TEACHER]
      	// 为了效率考虑,of()有很多重载的方法
        enumSet.removeAll(EnumSet.of(Person.STUDENT));
        System.out.println(enumSet); // [TEACHER]
        // 创建一个Set,包含除了源Set中的所有元素
        enumSet = EnumSet.complementOf(enumSet);
        System.out.println(enumSet); // [STUDENT]
    }

19.9 使用EnumMap

命令设计模式 需要一个只有单一方法的接口,然后从该接口实现具有各自不同的行为的多个子类。

public interface Command {
    void action();
}

    public static void main(String[] args) {
        EnumMap<Person,Command> map = new EnumMap<>(Person.class);
        map.put(Person.STUDENT, new Command() {
            @Override
            public void action() {
                System.out.println("这是学生");
            }
        });
        map.put(Person.TEACHER, new Command() {
            @Override
            public void action() {
                System.out.println("这是老师");
            }
        });
        // 以枚举的顺序排列键值对
        for (Map.Entry<Person, Command> entry : map.entrySet()) {
            entry.getValue().action();
        }
    }

19.10 常量相关的方法

enum允许程序员为每个枚举实例编写自己的方法。

public enum Person {
    TEACHER{
        @Override
        void info(){
            System.out.println("学生");
        }
    },STUDENT{
        @Override
        void info() {
            System.out.println("老师");
        }
    };
    private String flag;
    // 定义抽象方法
    // 也可以覆盖非抽象方法
    abstract  void info(); 
}

 public static void main(String[] args) {
        Person.STUDENT.info(); // 老师
    }
/*
上述代码和内部类非常类似,但是还是有很大区别的,实例没办法访问枚举的非静态变量,实例不能作为Class类型。
*/
19.10.1 使用enum的职责链

职责链设计模式 以多种不同的方式来解决一个问题,然后将他们链接在一起;当一个请求到来时,遍历整个链,直到链中的某个解决方案处理该请求。

// 模拟邮局处理邮件
// 邮件
public class Mail {
    // 定义邮件的几处信息
    enum A_INFO{YES,NO1,NO2,NO3}
    enum B_INFO{YES,NO1,NO2,NO3}
    enum C_INFO{YES,NO1,NO2,NO3}

    public A_INFO aInfo;
    public B_INFO bInfo;
    public C_INFO cinfo;
    static long counter = 0;
    long id = counter++;

    @Override
    public String toString() {
        return "Mail"+id;
    }

    public String details(){
        StringBuilder sb = new StringBuilder();
        sb.append("Mail -> A_INFO:").append(aInfo)
                .append(" B_INFO:").append(bInfo)
                .append(" C_INFO:").append(cinfo);
        return sb.toString();
    }

    // 获得一个随机的邮件
    public static Mail randomMail(){
        Mail mail = new Mail();
        mail.aInfo = Enums.randmEnum(A_INFO.class);
        mail.bInfo = Enums.randmEnum(B_INFO.class);
        mail.cinfo = Enums.randmEnum(C_INFO.class);
        return mail;
    }

    // 获得一个迭代器
    public static Iterable<Mail> generator(final int count){
        return new Iterable<Mail>() {
            @Override
            public Iterator<Mail> iterator() {
                return new Iterator<Mail>() {
                    int n = count;
                    @Override
                    public boolean hasNext() {
                        return (n--) > 0;
                    }

                    @Override
                    public Mail next() {
                        return randomMail();
                    }
                };
            }
        };
    }
}

// 邮局
public class PostOffice {
    // 处理器
    enum MailHandler{
        A_INFO_HANDLER{
            @Override
            boolean handle(Mail m) {
                switch (m.aInfo){
                    case YES:
                        System.out.println("由A处理");
                        return true;
                }
                return false;
            }
        },
        B_INFO_HANDLER{
            @Override
            boolean handle(Mail m) {
                switch (m.bInfo){
                    case YES:
                        System.out.println("由B处理");
                        return true;
                }
                return false;
            }
        },
        C_INFO_HANDLER{
            @Override
            boolean handle(Mail m) {
                switch (m.cinfo){
                    case YES:
                        System.out.println("由C处理");
                        return true;
                }
                return false;
            }
        };
        abstract boolean handle(Mail m);
    }

    // 处理邮件
    public static void handle(Mail m){
        for (MailHandler handler : MailHandler.values()) {
            if (handler.handle(m)) {
                return;
            }
        }
        System.out.println("邮件没有被处理");
    }
}

   public static void main(String[] args) {
        Iterable<Mail> generator = Mail.generator(5);
        for (Mail mail : generator) {
            PostOffice.handle(mail);
        }
    }
/*
邮件没有被处理
由C处理
由B处理
由C处理
由A处理
*/
19.10.2 使用enum的状态机

模拟售货机

// 输入
public enum Input {
    NICKEL/*5美分*/(5), DIME/*十分钱*/(10), QUARTER/*25美分*/(25), DOLLAR/*美元*/(100),
    TOOTHPASTE/*牙膏*/(200), CHIPS/*芯片*/(75), SODA/*苏打水*/(100), SOAP/*肥皂*/(50),
    ABORT_TRANSACTION/*中止交易*/ {
        @Override
        int amout() {
            throw new RuntimeException("交易中止了");
        }
    },
    STOP {
        @Override
        int amout() {
            throw new RuntimeException("停止了");
        }
    };
    int value;

    Input() {
    }

    Input(int value) {
        this.value = value;
    }

    int amout() {
        return value;
    }

    private static Random random = new Random();
    public static Input random(){
        // 不包括STOP
        return values()[random.nextInt(values().length-1)];
    }
}

// 类别
public enum Category {
    // 钱
    MONEY(NICKEL,DIME,QUARTER,DOLLAR),
    // 项目选择
    ITEM_SELECTION(TOOTHPASTE,CHIPS,SODA,SOAP),
    // 退出交易
    QUIT_TRANSACTION(ABORT_TRANSACTION),
    // 关闭售货机
    SHUT_DOWN(STOP)
    ;
    Input[] inputs;
    Category(Input ... types){
        inputs = types;
    }

    // 输入-类别对应关系
    private static EnumMap<Input,Category> categorys = new EnumMap<>(Input.class);
    static {
        // 遍历所有类别
        for (Category category : Category.class.getEnumConstants()) {
            // 遍历该类别下的所有输入
            for (Input input : category.inputs) {
                categorys.put(input,category);
            }
        }
    }
	// 获得商品对应的类别
    public static Category getCategory(Input input){
        return categorys.get(input);
    }
}

// 售货机
public class VendingMachine {
    // 售货机中的金额
    private static int amout;
    // 售货机状态
    private static State state = State.RESTING;
    private static Input selection = null;

    enum StateDuration/*状态持续时间*/ {TRANSIENT/*短暂的*/}

    enum State {
        // 待机状态
        RESTING/*休眠*/ {
            @Override
            void next(Input input) {
                switch (Category.getCategory(input)) {
                    case MONEY:
                        amout += input.amout();
                        state = ADDING_MONEY;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                }
            }
        },
        ADDING_MONEY/*加钱*/ {
            @Override
            void next(Input input) {
                switch (Category.getCategory(input)) {
                    case MONEY:
                        amout += input.amout();
                        break;
                    case ITEM_SELECTION:
                        selection = input;
                        if (amout < selection.amout()) {
                            System.out.println("购买" + selection + "的钱不足");
                        } else {
                            state =DISPENSING;
                        }
                        break;
                    case QUIT_TRANSACTION:
                        state = GIVING_CHANGE;
                        break;
                    case SHUT_DOWN:
                        state = TERMINAL;
                }
            }
        },
        DISPENSING/*出货*/(StateDuration.TRANSIENT) {
            @Override
            void next() {
                System.out.println("这是您的" + selection);
                amout -= selection.amout();
                state =GIVING_CHANGE;
            }
        },
        GIVING_CHANGE/*退款*/(StateDuration.TRANSIENT) {
            @Override
            void next() {
                // 每次只能出售一个商品
                if (amout > 0) {
                    System.out.println("退款:" + amout);
                    amout = 0;
                }
                state = State.RESTING;
            }
        },
        // 关闭售货机
        TERMINAL/*终止*/ {
            @Override
            void output() {
                System.out.println("已停止");
            }
        };
        public boolean isTransient = false;

        State() {
        }

        State(StateDuration trans) {
            isTransient = true;
        }

        void next() {
            throw new RuntimeException("");
        }

        void next(Input input) {
            throw new RuntimeException("");
        }

        void output() {
            System.out.println(amout);
        }
    }

    public static void run(Factory<Input> factory){
        while (state != State.TERMINAL){
            Input input = factory.next();
            state.next(input);
            while (state.isTransient){
                state.next();
            }
            state.output();
        }
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        VendingMachine.run(new RandomFactory());
    }

    static class RandomFactory implements Factory<Input>{
        @Override
        public Input next() {
            return Input.random();
        }
    }
}

注意:run的运行逻辑

  • 一开始,状态处于RESTING待机;
  • 状态有两种,一种是需要input的RESTING(待机)和ADDING_MONEY(加钱);另一种是不需要输入的DISPENSING(出货)和GIVING_CHANGE(退款),而这两种都被标记为了瞬时状态,他们不会长期存在;
  • 出货和退款出现的可能有两种:出货->退款、退款,由于while (state.isTransient)的作用,上述两种情况最会产生的状态一定是待机状态;
  • 所以next(input)最终都是待机和加钱在执行,而出货和退款都在内层循环中执行。

19.11 多路分发

当处理多种交互类型时,程序可能会变得相当杂乱;

而动态机制只能处理一种类型,所以,需要我们自己来判定其他类型。

一个方法调用决定一个类型,当我们需要决定几种类型时,我们就需要几个方法。

此方法的含义是一层套一层的,如果有三种类型,在需要三个方法,b方法嵌套在a方法内,c方法在b内。

// 实现剪刀石头布游戏
public class Test {
    static Random random = new Random();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            match(getItem(),getItem());
        }
    }

    static Item getItem() {
        int i = random.nextInt(3);
        switch (i) {
            case 0:
                return new Paper();
            case 1:
                return new Scissors();
            default:
                return new Rock();
        }
    }

    static void match(Item a,Item b){
        System.out.println(a +" VS "+b+" "+a.compete(b));
    }


}

enum OutCome {WIN, LOSE, DRAW}
// 是这几种类型的接口,被用作多路分发
interface Item {
    OutCome compete(Item it);
    
    // 石头剪刀布有三种类型,所以有三个eval()方法
    OutCome eval(Paper paper);

    OutCome eval(Scissors scissors);

    OutCome eval(Rock rock);
}

class Paper/*布*/ implements Item {
    /* 
    	compete的逻辑是相反的逻辑,eval站在参数的角度是赢,那compete则是站在类的角度就是输
    */
    @Override
    public OutCome compete(Item it) {
        return it.eval(this);
    }

    @Override
    public OutCome eval(Paper paper) {
        return OutCome.DRAW;
    }

    @Override
    public OutCome eval(Scissors scissors) {
        return OutCome.WIN;
    }

    @Override
    public OutCome eval(Rock rock) {
        return OutCome.LOSE;
    }

    @Override
    public String toString() {
        return "布";
    }
}

class Scissors/*剪刀*/ implements Item {
    @Override
    public OutCome compete(Item it) {
        return it.eval(this);
    }

    @Override
    public OutCome eval(Paper paper) {
        return OutCome.LOSE;
    }

    @Override
    public OutCome eval(Scissors scissors) {
        return OutCome.DRAW;
    }

    @Override
    public OutCome eval(Rock rock) {
        return OutCome.WIN;
    }

    @Override
    public String toString() {
        return "剪刀";
    }
}

class Rock/*石头*/ implements Item {
    @Override
    public OutCome compete(Item it) {
        return it.eval(this);
    }

    @Override
    public OutCome eval(Paper paper) {
        return OutCome.WIN;
    }

    @Override
    public OutCome eval(Scissors scissors) {
        return OutCome.LOSE;
    }

    @Override
    public OutCome eval(Rock rock) {
        return OutCome.DRAW;
    }

    @Override
    public String toString() {
        return "石头";
    }
}

使用enum分发

public interface Competitor <T extends Competitor<T>>{
    OutCome compete(T competitor);
}

public enum RoShamBo2 implements Competitor<RoShamBo2>{
    // 定义了所有的类型
    PAPER(OutCome.DRAW,OutCome.LOSE,OutCome.WIN),
    SCISSORS(OutCome.WIN,OutCome.DRAW,OutCome.LOSE),
    ROCK(OutCome.LOSE,OutCome.WIN,OutCome.DRAW)
    ;
    private OutCome vPaper,vScissors,vRock;
    // 定义了与不同类型比较的结果
    RoShamBo2(OutCome paper,OutCome scissors,OutCome rock){
        vPaper = paper;
        vScissors = scissors;
        vRock = rock;
    }

    // 实现了比较,it只有这三种类型
    // 这里仍然使用了两路分发,第一次在传入参数时,第二次在switch选择块中
    @Override
    public OutCome compete(RoShamBo2 it) {
        switch (it){
            default:
            case PAPER:return vPaper;
            case SCISSORS:return vScissors;
            case ROCK:return vRock;
        }
    }
}

 public static void main(String[] args) {
        OutCome compete = RoShamBo2.PAPER.compete(RoShamBo2.ROCK);
        System.out.println(compete);
    }

使用常量相关方法

public enum RoShamBo3 implements Competitor<RoShamBo3>{
    // 定义了所有的类型
    PAPER{
        @Override
        public OutCome compete(RoShamBo3 it) {
            switch (it){
                default:
                case PAPER:return OutCome.DRAW;
                case SCISSORS:return OutCome.WIN;
                case ROCK:return OutCome.LOSE;
            }
        }
    },
    SCISSORS{
        @Override
        public OutCome compete(RoShamBo3 it) {
            switch (it){
                default:
                case PAPER:return OutCome.LOSE;
                case SCISSORS:return OutCome.DRAW;
                case ROCK:return OutCome.WIN;
            }
        }
    },
    ROCK{
        @Override
        public OutCome compete(RoShamBo3 it) {
            switch (it){
                default:
                case PAPER:return OutCome.WIN;
                case SCISSORS:return OutCome.LOSE;
                case ROCK:return OutCome.DRAW;
            }
        }
    }
    ;
    // 定义抽象方法
    @Override
    public abstract OutCome compete(RoShamBo3 it) ;
}

使用EnumMap

public enum RoShamBo5 implements Competitor<RoShamBo5> {
    // 定义了所有的类型
    PAPER,
    SCISSORS,
    ROCK;
    // 使用EnumMap储存所有结果
    static EnumMap<RoShamBo5, EnumMap<RoShamBo5, OutCome>> table = new EnumMap<>(RoShamBo5.class);

    // 初始化table,和每个实例
    static {
        for (RoShamBo5 it : RoShamBo5.values()) {
            table.put(it, new EnumMap<>(RoShamBo5.class));
        }
        initRow(PAPER, OutCome.DRAW, OutCome.WIN, OutCome.LOSE);
        initRow(SCISSORS, OutCome.LOSE, OutCome.DRAW, OutCome.WIN);
        initRow(ROCK, OutCome.WIN, OutCome.LOSE, OutCome.DRAW);
    }

    // 初始化每个实例的结果
    static void initRow(RoShamBo5 it, OutCome paper, OutCome scissors, OutCome rock) {
        EnumMap<RoShamBo5, OutCome> map = table.get(it);
        map.put(PAPER, paper);
        map.put(SCISSORS, scissors);
        map.put(ROCK, rock);
    }

    @Override
    public OutCome compete(RoShamBo5 it) {
        // 注意:第一步get到该实例,第二部get比较的实例
        return table.get(this).get(it);
    }

    public static void main(String[] args) {
        System.out.println(RoShamBo5.PAPER.compete(RoShamBo5.SCISSORS));
    }
}

使用二维数组

// 可以看到,这应该是最简单的,同时也是效率最高的
public enum RoShamBo6 implements Competitor<RoShamBo6>{
    PAPER,
    SCISSORS,
    ROCK;
    private static OutCome[][] table = {
            {OutCome.DRAW,OutCome.WIN,OutCome.LOSE},
            {OutCome.LOSE,OutCome.DRAW,OutCome.WIN},
            {OutCome.WIN,OutCome.LOSE,OutCome.DRAW}
    };
    @Override
    public OutCome compete(RoShamBo6 it) {
        return table[this.ordinal()][it.ordinal()];
    }
}

19.12 总结

枚举在Java中是一个小功能,它所带来的价值是在某些方面可以优雅的解决某些问题。

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值