JDK8新特性讲解

1. JDK8之default关键字

在jdk1.8之前接口里面是只能有抽象方法,不能有任何方法的实现,在jdk1.8里面打破了这个定义,引入了default关键字,使用default修饰方法,可以在接口里面定义具体的方法实现,即接口的默认方法,这个接口的实现类实现了接口之后,不用管这个default修饰的方法就可以直接调用,即接口方法的默认实现

public interface Animal {

    void run();

    void eat();

    default void breath() {
        System.out.println("呼吸氧气");
    }
}

public class Dog implements Animal {
    @Override
    public void run() {
        System.out.println("跑起来");
    }

    @Override
    public void eat() {
        System.out.println("吃饭");
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.run();
        dog.eat();
        dog.breath();
    }
}
  • 使用场景:接口里面定义公用的业务逻辑,抽取出来,每个子类都必须具备

2. JDK8之新增base64加密

Base64是⽹络上最常⻅的⽤于传输8Bit字节码的编码⽅式之⼀,Base64就是
⼀种基于64个可打印字符来表示⼆进制数据的方法 基于64个字符A-Z,a-z,0-9,+,/的编码方式,是⼀种能将任意⼆进制数据用64种字元组合成字符串的方法,而这个⼆进制数据和字符串资料之间是可以互相转换的,在实际应用上,Base64除了能将⼆进制数据可视化之外,也常⽤来表示字符串加密过后的内容

2.1 jdk1.8之前实现方式

早起base64使用jdk里sun.misc包下的BASE64Encoder和BASE64Decoder这两个类

BASE64Encoder encoder = new BASE64Encoder();
BASE64Decoder decoder = new BASE64Decoder();
String text = "测试字符串";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
// 编码
String encode = encoder.encode(bytes);
System.out.println(encode);
// 解密
System.out.println(new String(decoder.decodeBuffer(encode), "UTF-8"));

2.2 jdk1.8之后实现方式

Base64.Encoder encoder = Base64.getEncoder();
Base64.Decoder decoder = Base64.getDecoder();
String text = "测试字符串";
byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
// 编码
String encode = encoder.encodeToString(bytes);
System.out.println(encode);
// 解码
System.out.println(new String(decoder.decode(encode), "UTF-8"));

区别:1.8之前base64编码和解码效率比较差,公开信息说以后的版本会取消这个方法。jdk1.8之后在java.util包中新增了base64的类,编解码效率远大于sun.msic

3. JDK8之时间日期处理类

时间处理再熟悉不过,SimpleDateFormat, Calendar等类,旧版日期API缺点很严重,java.util.Date是非线程安全的,API设计比较差,日期/时间转换,加减,比较等操作很麻烦。Java8通过发布新的Date-Time API来进一步加强日期与时间的处理。

  • 新增了很多场景API,如日期/时间的比较,加减,格式化等
  • 包所在位置java.time

核心类:

  • LocalDate : 不包含具体时间的日期
  • LocalTime : 不含日期的时间
  • LocalDateTime :包含了日期及时间

LocalDate代码示例

public class Main {

    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        System.out.println("今天日期" + today);
        // 获取年,月,日,周几
        System.out.println("现在是哪年:" + today.getYear());
        System.out.println("现在是哪月:" + today.getMonth());
        System.out.println("现在是哪月(数字):" + today.getMonthValue());
        System.out.println("现在是几号:" + today.getDayOfMonth());
        System.out.println("现在是周几:" + today.getDayOfWeek());

        // 加减年份, 加后返回的对象是修改后的, 旧的依旧还是旧的
        LocalDate changeDate = today.plusYears(10);
        System.out.println("加后的日期" + changeDate);
        System.out.println("加后是哪年:" + changeDate.getYear());
        System.out.println("现在是哪年:" + today.getYear());

        // 日期比较
        System.out.println("isAfter:" + changeDate.isAfter(today));
        //getYear() int 获取当前⽇期的年份
        //getMonth() Month 获取当前⽇期的⽉份对象
        //getMonthValue() int 获取当前⽇期是第⼏⽉
        //getDayOfWeek() DayOfWeek 表示该对象表示的⽇期是星期⼏
        //getDayOfMonth() int 表示该对象表示的⽇期是这个⽉第⼏天
        //getDayOfYear() int 表示该对象表示的⽇期是今年第⼏天
        //withYear(int year) LocalDate 修改当前对象的年份
        //withMonth(int month) LocalDate 修改当前对象的⽉份
        //withDayOfMonth(int dayOfMonth) LocalDate 修改当前对象在当⽉的⽇期
        //plusYears(long yearsToAdd) LocalDate 当前对象增加指定的年份数
        //plusMonths(long monthsToAdd) LocalDate 当前对象增加指定的⽉份数
        //plusWeeks(long weeksToAdd) LocalDate 当前对象增加指定的周数
        //plusDays(long daysToAdd) LocalDate 当前对象增加指定的天数
        //minusYears(long yearsToSubtract) LocalDate 当前对象减去指定的年数
        //minusMonths(long monthsToSubtract) LocalDate 当前对象减去注定的⽉数
        //minusWeeks(long weeksToSubtract) LocalDate 当前对象减去指定的周数
        //minusDays(long daysToSubtract) LocalDate 当前对象减去指定的天数
        //compareTo(ChronoLocalDate other) int ⽐较当前对象和other对象在时间上的⼤⼩,返回值如果为正,则当前对象时间较晚,
        //isBefore(ChronoLocalDate other) boolean ⽐较当前对象⽇期是否在other对象⽇期之前
        //isAfter(ChronoLocalDate other) boolean ⽐较当前对象⽇期是否在other对象⽇期之后
        //isEqual(ChronoLocalDate other) boolean ⽐较两个⽇期对象是否相等
    }
}

LocalTime 和 LocalDateTime 可以自行查找相关API,都是类似语法。

4. JDK8之时间日期格式化与比较

JDK8之前使用SimpleDateFormat来进行格式化,但SimpleDateFormat并不是线程安全的。JDK8之后引入线程安全的DateTimeFormat

时间格式转换

LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String ldtStr = dtf.format(ldt);
System.out.println(ldtStr);

获取指定的日期时间对象

LocalDateTime of = LocalDateTime.of(2021, 11, 8, 22, 44, 36);
System.out.println(of);

计算日期时间差

LocalDateTime today = LocalDateTime.now();
System.out.println(today);
LocalDateTime changeDate = LocalDateTime.of(2020, 11, 11, 8, 12, 15);
System.out.println(changeDate);
// 第二个参数减第一个参数
Duration duration = Duration.between(today, changeDate);
// 两个时间差的天数
System.out.println(duration.toDays());
// 两个时间差的小时数
System.out.println(duration.toHours());
// 两个时间差的分钟数
System.out.println(duration.toMinutes());
// 两个时间差的毫秒数
System.out.println(duration.toMillis());
// 两个时间差的纳秒数
System.out.println(duration.toNanos());

5. JDK8之Optional类

Optional类主要用来处理空指针异常问题(NullPointerException)。
这个类本质是一个包含有可选值的包装类,这意味着Optional类既可以含有对象也可以为空

of()方法

of()方法当null值作为参数传递进去时,会抛出异常

public class Main {
    public static void main(String[] args) {
        Student student = null;
        Optional<Student> optional = Optional.of(student);
    }
}

执行结果
在这里插入图片描述

ofNullable()方法

如果对象即可能是null也可能是非null,应该使用ofNullable()方法

public class Main {
    public static void main(String[] args) {
        Student student = null;
        Optional<Student> optional = Optional.ofNullable(student);
    }
}

访问Optinal对象的值

public class Main {

    public static void main(String[] args) {

        Student student = null;
        Optional<Student> optional = Optional.ofNullable(student);
        Student s = optional.get();
    }
}

如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象一般使用get之前需要先验证是否有值,不然还会报错

public class Main {

    public static void main(String[] args) {
        Student student = null;
        Optional<Student> optional = Optional.ofNullable(student);
        if (optional.isPresent()){
            System.out.println("value is not null");
            Student s = optional.get();
            System.out.println(s);
        } else {
            System.out.println("value is null");
        }
        
    }
}

orElse()方法

orElse()如果有值返回该值,否则返回传递给它的参数值

Student student1 = null;
Student student2 = new Student(3);
Student result = Optional.ofNullable(student1).orElse(student2);
System.out.println(result.getAge());

执行结果为3,将student2的值赋值给了student1

另一种Lambda表达式方式写法

Student student3 = null;
Integer rs = Optional.ofNullable(student3).map(obj -> obj.getAge()).orElse(4);
System.out.println(rs);
System.out.println(student3.getAge());

rs返回值为4,但是最后一行打印会报出空指针异常

6. JDK8之Lambda表达式

在JDK8之前,Java是不支持函数式编程的,所谓的函数式编程,即可理解是将一个函数(也称为“行为”)作为一个参数进行传递,面向对象编程是对数据的抽象(各种各类的POJO),而函数式编程则是对行为的抽象(将行为作为一个参数进行传递)

jdk8之前线程:

		new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("执行线程任务");
            }
        }).start();

jdk8之后用lambda表达式创建线程只需要一句话

new Thread(()-> System.out.println("执行线程任务")).start();

jdk8之前对集合里的字符串排序

List<String> list = Arrays.asList("aaa", "fsdfsd", "eqwe", "asd", "tre");
Collections.sort(list, new Comparator<String>() {
     @Override
     public int compare(String a, String b) {
          return a.compareTo(b);
     }
});

jdk8之后对使用lambda表达式对集合里的字符串进行排序

List<String> list = Arrays.asList("aaa", "fsdfsd", "eqwe", "asd", "tre");
Collections.sort(list, (a,b)->a.compareTo(b));

lambda表达式使用场景(前提): 一个接口中只包含一个方法,则可以使用Lambda表达式,这样的接口称之为"函数接口"语法:(params)->expression

第一部分为括号内逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:-> ; 第三部分为方法体,可以是表达式和代码块

参数列表:

  1. 括号中参数列表的数据类型可以省略不写
  2. 括号中的参数只有一个,那么参数类型和()都可以省略

方法体:
如果{}中的代码只有一行,无论有返回值,都可以省略{} , return, 分号, 要一起省略,其他则需要加上

好处:Lambda表达式的实现方式在本质是以匿名内部类的方式进行实现

7. JDK8之自定义函数式编程实战

自定义lambda接口流程

  • 定义一个函数式接口,需要标注此接口@FunctionalInterface,否则万一团队成员在接口上加了其他方法则容易出故障
  • 编写一个方法,输入需要操作的数据和接口
  • 在调用方法时传入数据和lambda表达式,用来操作数据

比如我们定义一个加减乘除的接口,以前需要定义4个方法

@FunctionalInterface
public interface OperFunction<R,T> {

    R operator(T t1, T t2);
}
public class Oper {


    public static Integer operator(int x, int y, OperFunction<Integer, Integer> of) {
        return of.operator(x, y);
    }
}
public class Main {

    public static void main(String[] args) {
        System.out.println(Oper.operator(20, 4, (x, y)-> x * y));
        System.out.println(Oper.operator(20, 4, (a, b) -> a + b));
        System.out.println(Oper.operator(20, 4, (a, b) -> a - b));
        System.out.println(Oper.operator(20, 4, (a, b) -> a / b));
		System.out.println(Oper.operator(20, 4, (x, y)-> x * y * x + y));
    }
}

8. JDK8提供的函数式编程实战

Lambda表达式必须先定义接口,创建相关方法之后才可使用,这样做十分不便,java8已经内置了许多接口,例如下面四个功能型接口,所以一般很少会由用户定义新的函数式接口。java8最大特性就是函数式接口,所有标注了@FunctionalInterface注解的接口都是函数式接口

Java8 内置的四⼤核⼼函数式接⼝

Consumer<T> : 消费型接⼝:有⼊参,⽆返回值

 void accept(T t);
 
Supplier<T> : 供给型接⼝:⽆⼊参,有返回值

 T get();
 
Function<T, R> : 函数型接⼝:有⼊参,有返回值

 R apply(T t);
 
Predicate<T> : 断⾔型接⼝:有⼊参,有返回值,返回值类型确定是boolean

 boolean test(T t);

8.1 Function

Function传入一个值经过函数的计算返回另一个值
T:入参类型 R:出参类型
调用方法:R apply(T t);

实现:

public class FunctionObj implements Function {
    
    @Override
    public Object apply(Object o) {
        return o + "拼接哈哈哈";
    }
}
public class Oper  {
	public static String operator(String s, FunctionObj obj) {
        return obj.apply(s).toString();
    }
}
public class Main {
    public static void main(String[] args) {
		String csdn = Oper.operator("CSDN", new FunctionObj());
        System.out.println(csdn);
	}
}

另一种常规用法

public class Main {

    public static void main(String[] args) {
		Function<Integer, Integer> function = p->{
            return p*100;
        };
        Integer apply = function.apply(100);
        System.out.println(apply);
	}
}

8.2 BIFunction

Function只能传递一个参数,如果要传递两个参数,则用BIFunction

public class BOper {
    public static Integer operator(Integer a, Integer b, BiFunction<Integer, Integer, Integer> of) {
        return (Integer) of.apply(a, b);
    }
}
public class Main {
    public static void main(String[] args) {
    	System.out.println(BOper.operator(10 ,3, (a,b)->a*b));
        System.out.println(BOper.operator(10 ,3, (a,b)->a+b));
        System.out.println(BOper.operator(10 ,3, (a,b)->a-b));
        System.out.println(BOper.operator(10 ,3, (a,b)->a/b));
    }
}

8.3 Consumer

Consumer消费型接口,有入参,无返回值
将T作为输入,不返回任何内容
调用方法:void accept(T t);

由于没有出参,常用于打印、发送短信等消费动作

实现

public class Main {

    public static void main(String[] args) {
    	Consumer<String> consumer = obj -> {
            System.out.println(obj);
            System.out.println("调用短信接口发送短信");
        };
        System.out.println("start");
        consumer.accept("17712345678");

		sendMsg("11234214", obj->{
            System.out.println(obj);
            System.out.println("调用短信接口发送短信");
        });
	}
	public static void sendMsg(String phone, Consumer<String> consumer) {
        consumer.accept(phone);
    }
}

典型应用

public class Main {
    public static void main(String[] args) {
    	List<String> list = Arrays.asList("asda", "rterter");
        list.forEach(obj -> {
            System.out.println(obj);
        });
    }
 }

8.4 Supplier

Supplier供给型接口:无入参,有返回值
T:出参类型,没有入参
调用方法 :T get();

示例:

public class Main {

    public static void main(String[] args) {
    	System.out.println(getStudent());
    }
	
	public static User getStudent() {
        Supplier<User> supplier = ()->{
            User user = new User();
            user.setName("张三");
            user.setAge(18);
            return user;
        };
        return supplier.get();
    }
}

class User{
    private String name;
    private int age;

    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;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

8.5 Predicate

Predicate : 断言行接口:有入参,有返回值,返回值类型确定是boolean
T : 入参类型; 出参类型必须是Boolean
调用方法 : boolean test(T t);

用途:用于接收一个参数,用于判断是否满足一定的条件,过滤数据

public class Main {

    public static void main(String[] args) {
		List<String> arr = Arrays.asList("afwrwerw", "rwerwer", "awtertert", "Bwrtert", "utyu");
        List<String> results = filter(arr, obj -> obj.startsWith("a"));
        System.out.println(results);
	}
}
public static List<String> filter(List<String> list, Predicate<String> predicate) {
        List<String> results = new ArrayList<>();
        list.forEach(obj -> {
            if (predicate.test(obj)) {
                results.add(obj);
            }
        });
        return results;
    }

9. JDK8之流Stream的使用

Stream中文称为,通过将集合转换为这么一种叫做流的元素队列,通过声明性方式,能够对集合中的每个元素进行一系列并行或串行的流水线操作。
元素是特定类型的对象,所以元素集合看作一种流,流在管道中传输,且可以在管道的节点上进行处理,比如排序,聚合,过滤等操作。

在这里插入图片描述
操作详情

  • 数据元素便是原始集合,如List、Set、Map等
  • 生成流,可以是串行流stream()或者是并行流parallelStream(),建议使用串行流
  • 中间操作,可以是排序,聚合,过滤,转换等
  • 终端操作,很多流操作本身就返回一个流,所以多个操作可以直接连接起来,最后统一进行收集

示例

public class Main {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("JAVA", "PHP", "Go", "C++", "Python");

        List<String> result = list.stream().map(obj -> "今天学习" + obj).collect(Collectors.toList());
        System.out.println(result);
    }
}

10. JDK8之map和filter函数的使用

10.1 map函数

  • 将流中每一个元素T映射为R (类似类型转换)

示例场景:转换对象

public class Main {
	public static void main(String[] args) {
    	List<String> userList = Arrays.asList("eqeqwe", "sgdfgdf", "eryrete", "sfsdfsdfs", "wrwrw");
    	 List<UserDto> list = userList.stream().map(obj -> {
            return new UserDto(obj);
        }).collect(Collectors.toList());

        System.out.println(list);
    }
}

public class UserDto {

    private String username;

    public UserDto(){

    }
    public UserDto(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "UserDto{" +
                "username='" + username + '\'' +
                '}';
    }
}

10.2 filter函数

  • 用于通过设置的条件过滤出元素

示例:

public class Main {

    public static void main(String[] args) {
    	List<String> userList = Arrays.asList("eqeqwe", "sgdfgdf", "eryrete", "sfsdfsdfs", "wrwrw");
    	List<String> stringList = userList.stream().filter(obj -> {
            if (obj.length() > 6) {
                return true;
            } else {
                return false;
            }
        }).collect(Collectors.toList());
        System.out.println(stringList);
    }
}

11. JDK8之limit和sorted函数

11.1 sorted函数

sorted()对流进行自然排序,其中的元素必须实现Comparable接口

sorted(Comparator<? super T> comparator) ⽤来⾃定义升降序

public class Main {

    public static void main(String[] args) {
    	List<String> list = Arrays.asList("springCloud", "springBoot", "redis", "dubbo", "rocketmq", "spring", "mybatis");

        // 根据字符串长度升序排序
        List<String> result = list.stream().sorted(Comparator.comparing(obj -> obj.length())).collect(Collectors.toList());
        System.out.println(result);
        // 根据字符串长度降序排序
        List<String> result1 = list.stream().sorted(Comparator.comparing(obj -> obj.length(), Comparator.reverseOrder())).collect(Collectors.toList());
        System.out.println(result1);
    }
}

11.2 limit函数

截断流使其最多包含指定数量的元素

public class Main {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("springCloud", "springBoot", "redis", "dubbo", "rocketmq", "spring", "mybatis");
        List<String> result2 = list.stream().sorted(Comparator.comparing(obj -> obj.length())).limit(3).collect(Collectors.toList());
        System.out.println(result2);
	}
}

12. JDK8之allMatch和anyMatch函数

12.1 allMatch函数

检查是否匹配所有元素,只有全部符合才返回true

public class Main {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("springboot", "springcloud", "redis",
                "git", "netty", "java", "html", "docker");

        boolean result = list.stream().allMatch(obj -> obj.length() > 5);
        System.out.println(result);
	}
}

12.2 anyMatch函数

检查是否⾄少匹配⼀个元素

public class Main {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("springboot", "springcloud", "redis",
                "git", "netty", "java", "html", "docker");
        boolean result2 = list.stream().anyMatch(obj -> obj.length() > 10);
        System.out.println(result2);
    }
}

13. JDK8之max和min函数

max函数和min函数取集合里的最大值和最小值

public class Main {

    public static void main(String[] args) {
        List<User> list = Arrays.asList(new User(10), new User(8), new User(25), new User(32), new User(7));
        Optional<User> max = list.stream().max((a, b) -> Integer.compare(a.getAge(), b.getAge()));
        System.out.println(max.get().getAge());

        Optional<User> min = list.stream().min((a, b) -> Integer.compare(a.getAge(), b.getAge()));
        System.out.println(min.get().getAge());
    }

}
public class User {

    private int age;

    public User(){

    }
    public User(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

14. JDK8之foreach操作

集合便利的方式有for循环或者迭代器,JDK8里面新增了foreach接口

public class Main {

    public static void main(String[] args) {
        List<User> list = Arrays.asList(new User(10), new User(8), new User(25), new User(32), new User(7));
        list.forEach(obj -> {
            System.out.println(obj.getAge());
        });
    }

}

注意:

  • 不能修改包含外部的变量的值
  • 不能⽤break或者return或者continue等关键词结束或者跳过循环

15. JDK8之collector收集器

collect()方法的作用

  • 一个终端操作,用于对流中的数据进行归集操作,collect()方法接收的参数是一个Collector
  • 有两个重载方法,在Stream接口里面
//重载⽅法⼀
 <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T>
accumulator, BiConsumer<R, R>combiner);
 //重载⽅法⼆
 <R, A> R collect(Collector<? super T, A, R> collector);

常用第二个重载方法,JDK提供了很多常用方法,无需自己自定义之后使用第一种

常用的方法有

  • Collectors.toList()
  • Collectors.toMap()
  • Collectors.toSet()
  • Collectors.toCollection() :⽤⾃定义的实现Collection的数据结构收集
    • Collectors.toCollection(LinkedList::new)
    • Collectors.toCollection(CopyOnWriteArrayList::new)
    • Collectors.toCollection(TreeSet::new)

toMap方法示例

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("springCloud", "springBoot", "redis", "dubbo", "rocketmq", "spring", "mybatis");
        Map<String, String> map = list.stream().collect(Collectors.toMap(a -> a+"asd", a -> a));
        System.out.println(map);
    }
}

toSet方法示例

public class Main {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("springCloud", "springBoot", "redis", "dubbo", "rocketmq", "spring", "mybatis");
        Set<String> result = list.stream().collect(Collectors.toSet());
        System.out.println(result);
    }
}

16. JDK8之joining函数

joining函数用于拼接字符串

joining函数有三种重载方法

Collectors.joining()
Collectors.joining("分隔符")
Collectors.joining("分隔符", "前缀", "后缀")

示例

public class Main {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("springCloud", "springBoot", "redis", "dubbo", "rocketmq", "spring", "mybatis");
        String result1 = list.stream().collect(Collectors.joining());
        System.out.println(result1);

        String result2 = list.stream().collect(Collectors.joining(", "));
        System.out.println(result2);

        String result3 = list.stream().collect(Collectors.joining(",", "[", "]"));
        System.out.println(result3);

        String result4 = Stream.of("java", "php", "Go", "Python").collect(Collectors.joining(",", "[", "]"));
        System.out.println(result4);
    }

}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值