原创

Java8新特性

Java8新特性之函数式接口以及default方法

函数式接口

  • jdk8中新增了函数式接口,在一个接口中只有一个抽象方法的接口被称为函数式接口。
  • 例如java.lang.Runnable,java.util.concurrent.Callable。
  • jdk8中新增了@FunctionalInterface注解来标注一个函数式接口。

default方法

  • jdk8中新增了default方法,jdk8之前接口中的方法必须都是抽象的,在jdk8中允许接口中定义非抽象方法,
  • 在接口中的非抽象方法上使用default修饰即可,比如在jdk8中新增了一个函数式接口:java.util.function。
  • java8中打破了接口中的方法必须都是抽象的这一规范,有一个好处就是可以提高程序的兼容性,
  • 在java.lang.Iterable接口中新增了forEach方法,该方法就是使用default修饰的。
  • 在接口中定义default方法可以变相的让java支持"多继承"。
package com.lzhpo.functioninterface;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
@FunctionalInterface    //自定义函数接口
public interface MyInterface {

    void m1(int a,int b);

    default String m2(String s){
        return null;
    }

    default void m3(){
    }
}

Java8新特性之Lambda表达式

Lambda语法

lambda 表达式的语法格式如下:

(parameters) -> expression 或 (parameters) ->{ statements; }

以下是lambda表达式的重要特征:

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。

可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。

可选的大括号:如果主体包含了一个语句,就不需要使用大括号。

可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

  • 可以将lambda看做是一个匿名方法,lambda只能用于函数式接口。

    (类型 参数, 类型 参数, …, 类型 参数) -> {
        代码块;
        return 结果;
    }
    
  • 主要由这几个符合构成:

    () -> {}
    
  • lambda优点:使用lambda表达式可以编写出比匿名内部类更简洁的代码。

  • lambda表达式为什么只能用于函数式接口中?

    lambda表达式实际上就是重写了接口中的抽象方法,在函数式接口中只有一个抽象方法,此时编译器会认定重写的就是该唯一的抽象方法。

    倘若一个接口中有多个抽象方法,而lambda表达式是没有匿名的,编译器就无法判断其重写的是哪个抽象方法了。

简单示例:

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  

// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  

// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  

// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

使用Lambda表达式和不使用Lambda表达式比较

package com.lzhpo.lambda;

import com.lzhpo.functioninterface.MyInterface;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class LambdaDemo1 {
    public static void main(String[] args) {

        /**
         * 不使用Lambda表达式
         */
        System.out.println("======不使用Lambda表达式======");
        MyInterface oldType = new MyInterface() {
            @Override
            public void m1(int a, int b) {
                System.out.println(a + b);
            }
        };
        oldType.m1(99,66);


        /**
         * 使用Lambda表达式
         */
        System.out.println("======使用Lambda表达式======");
        MyInterface newType = (int a,int b) -> {
            System.out.println(a + b);
        };
        // 编译器都可以从上下文环境中推断出lambda表达式的参数类型,因此可以省略参数类型
        /* MyInterface newType = (a, b) -> {
            System.out.println(a + b);
        }; */
        newType.m1(99,66);
    }
}

Lambda在多线程中的写法

package com.lzhpo.lambda;

/**
 * Lambda在多线程中的写法:
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class LambdaDemo2 {
    public static void main(String[] args) {

        /**
         * 不使用Lambda的写法
         */
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("no lambda");
            }
        }).start();

        /**
         * lambda写法
         *
         * 因为Thread类中接收Runnable类型的对象,所以编译器会识别出lambda表达式是Runnable对象
         */
        new Thread(() -> {
            System.out.println("lambda");
        }).start();
    }
}

foreach方法

在jdk8中的java.lang.Iterable接口中新增了非抽象的forEach方法。

可以使用该方法配合lambda来遍历集合。

package com.lzhpo.foreach;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
//使用Lambda遍历集合
public class Demo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);

        /**
         * java8之前
         */
        System.out.println("======java8之前======");
        for (Integer i:list) {
            System.out.print(i + " ");
        }
        System.out.println();

        /**
         * java8之后
         *
         * 在Iterator接口中新增了非抽象的foreach方法。
         */
        System.out.println("======java8之后======");
        list.forEach((Integer n) -> {
            System.out.print(n + " ");
        });
        System.out.println();

        //如果在lambda表达式中只有一个参数一行语句的时候,可以简写为下面格式
        list.forEach(n -> System.out.print(n + " "));
        System.out.println();

        //Java 8新增方法引用,方法引用由::双冒号操作符标示
        list.forEach(System.out::println);
    }
}

方法引用

方法引用主要是用来简写lambda表达式,

有些lambda表达式里面仅仅是执行一个方法调用,

这时使用方法引用可以简写lambda表达式。

package com.lzhpo.usefunction;

import java.util.Arrays;
import java.util.Comparator;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Demo {
    public static void main(String[] args) {

        /**
         * 一共有四种类型的方法引用:
         *
         *      静态方法引用,类名::方法名
         *      某个对象的引用,对象变量名::方法名
         *      特定类的任意对象的方法引用,类名::方法名
         *      构造方法,类名::new
         */


        Integer[] num = new Integer[]{66,23,99,56};

        /**
         * 不使用Lambda
         */
        Arrays.sort(num, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        });

        /**
         * 使用Lambda
         */
        Arrays.sort(num,(x,y) -> Integer.compare(x,y));

        /**
         * 方法引用
         */
        Arrays.sort(num,Integer::compare);

    }
}

构造方法的引用

Car:

package com.lzhpo.usefunction.useconstructionmethod;

import java.util.function.Supplier;

/**
 * 构造方法的引用:
 * 先创建一个Car类型,添加一个buy方法,
 * 为了能够使用lambda表达式,这里使用java.util.function.supplier接口,
 * 这个接口是java8新增的一个函数式接口,里面只有一个抽象的get方法。
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Car {
    public static Car buy(Supplier<Car> s){
        //通过get方法传入的Car类型的对象
        return s.get();
    }
}

TestCar:

package com.lzhpo.usefunction.useconstructionmethod;

/**
 * 构造方法引用
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class TestCar {
    public static void main(String[] args) {
        Car car = Car.buy(Car::new);
        System.out.println(car);
    }
}

Java8新特性之Stream

Stream简介

  • jdk8中新增stream API,需要注意的是该stream跟之前学习的IO流没有关系,
  • 这个stream主要是用来处理集合数据的,可以将其看做是一个高级迭代器。
  • 在Collection接口中新增了非抽象的stream方法来获取集合的流。
  • 使用stream后可以写出更简洁的代码来处理集合中的数据。

使用Stream示例

Student:

package com.lzhpo.stream.useStream;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Student {
    private String name;
    private int score;

    public Student(){

    }

    public Student(String name, int score) {
        super();
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "[姓名=" + name + ", 分数=" + score + "]";
    }


}

TestStudent:

package com.lzhpo.stream.useStream;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class TestStudent {
    public static void main(String[] args) {
        List<Student> stuList = new ArrayList<>(10);

        stuList.add(new Student("刘一", 85));
        stuList.add(new Student("陈二", 90));
        stuList.add(new Student("张三", 98));
        stuList.add(new Student("李四", 88));
        stuList.add(new Student("王五", 83));
        stuList.add(new Student("赵六", 95));
        stuList.add(new Student("孙七", 87));
        stuList.add(new Student("周八", 84));
        stuList.add(new Student("吴九", 100));
        stuList.add(new Student("郑十", 95));

        /**
         * 需求:列出90分以上的学生姓名,并按照分数降序排序
         */


        /**
         * 以前的写法,代码较多,每个操作都需要遍历集合
         */
        List<Student> result1 = new ArrayList<>(10);

        System.out.println("======以前的写法======");
        //遍历集合获取分数大于90分以上的学生并存放到新的List中
        for (Student s : stuList) {
            if (s.getScore() >= 90){
                result1.add(s);
            }
        }
        //对List进行降序排序
        result1.sort(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Integer.compare(o2.getScore(),o1.getScore());
            }
        });
        //打印结果
        System.out.println(result1);

        /**
         * 使用Stream的写法:
         *
         * 1.获取集合的stream对象
         * 2.使用filter方法完成过滤
         * 3.使用sort方法完成排序
         * 4.使用collect方法将处理好的stream对象转换为集合对象
         */
        System.out.println("======使用Stream======");
        result1 = stuList.stream()
                // filter:Predicate是一个函数式接口(@FunctionalInterface)
                .filter(s -> s.getScore() >= 90)
                .sorted(Comparator.comparing(Student::getScore).reversed())
                .collect(Collectors.toList());
        System.out.println(result1);
    }
}

Map和Reduce

学过大数据的就知道,对MapReduce很敏感。

  • map用来归类,结果一般是一组数据,比如可以将list中的学生分数映射到一个新的stream中。
  • reduce用来计算值,结果是一个值,比如计算最高分。

简单示例

Student:

package com.lzhpo.stream.useStream;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Student {
    private String name;
    private int score;

    public Student(){

    }

    public Student(String name, int score) {
        super();
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "[姓名=" + name + ", 分数=" + score + "]";
    }


}

InitData:

package com.lzhpo.stream.mapreduce;

import com.lzhpo.stream.useStream.Student;

import java.util.ArrayList;
import java.util.List;

/**
 * 初始化数据
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class InitData {

    public static List<Student> getStudent(){
        List<Student> stuList = new ArrayList<>(10);

        stuList.add(new Student("刘一", 85));
        stuList.add(new Student("陈二", 90));
        stuList.add(new Student("张三", 98));
        stuList.add(new Student("李四", 88));
        stuList.add(new Student("王五", 83));
        stuList.add(new Student("赵六", 95));
        stuList.add(new Student("孙七", 87));
        stuList.add(new Student("周八", 84));
        stuList.add(new Student("吴九", 100));
        stuList.add(new Student("郑十", 95));

        return stuList;
    }
}

Demo:

package com.lzhpo.stream.mapreduce;

import com.lzhpo.stream.useStream.Student;

import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Demo {
    public static void main(String[] args) {
        //初始化List数据同上
        List<Student> list = InitData.getStudent();

        /**
         * 打印初始数据,方便对比。
         */
        List<Integer> scoreInit = list.stream()
                .map(Student::getScore)
                .collect(Collectors.toList());
        System.out.println("初始分数:" + scoreInit);

        /**
         * 使用map方法获取list数据中的name
         */
        List<String> names = list.stream()
                .map(Student::getName)
                .collect(Collectors.toList());
        System.out.println("使用map方法获取list数据中的name:" + names);

        /**
         * 使用map方法获取list数据中的names的长度
         */
        List<Integer> length = list.stream()
                .map(Student::getName)
                .map(String::length)
                .collect(Collectors.toList());
        System.out.println("使用map方法获取list数据中的names的长度:" + length);

        /**
         * 将每人的分数减10
         */
        List<Integer> score = list.stream()
                .map(Student::getScore)
                .map(i -> i - 10)
                .collect(Collectors.toList());
        System.out.println("将每人的分数减10:" + score);

        /**
         * 计算学生总分
         */
        Integer totalScore1 = list.stream()
                .map(Student::getScore)
                .reduce(0,(a,b) -> a + b);
        System.out.println("计算学生总分:" + totalScore1);

        /**
         * 计算最高分和最低分
         *
         * 注意:
         * Optional:
         *      - Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
         *      - Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
         *      - Optional 类的引入很好的解决空指针异常。
         */
        Optional<Integer> max = list.stream()
                .map(Student::getScore)
                .reduce(Integer::max);
        Optional<Integer> min = list.stream()
                .map(Student::getScore)
                .reduce(Integer::min);
        System.out.println("最高分:" + max.get());
        System.out.println("最低分:" + min.get());

        /**
         * 计算平均分
         */
        OptionalDouble avgScore = list.stream()
                .mapToInt(Student::getScore)
                .average();
        System.out.println("平均分:" + avgScore.getAsDouble());

        /**
         * 其它操作:
         */
        //生成随机数(1,100)
        IntStream num = IntStream.rangeClosed(1,100);
        //计算1-100之间的数字中偶数的个数
        long count = IntStream.rangeClosed(1,100)
                .filter(n -> n%2 == 0)
                .count();
        System.out.println("计算1-100之间的数字中偶数的个数:" + count);
    }
}

运行结果:

初始分数:[85, 90, 98, 88, 83, 95, 87, 84, 100, 95]
使用map方法获取list数据中的name:[刘一, 陈二, 张三, 李四, 王五, 赵六, 孙七, 周八, 吴九, 郑十]
使用map方法获取list数据中的names的长度:[2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
将每人的分数减10:[75, 80, 88, 78, 73, 85, 77, 74, 90, 85]
计算学生总分:905
最高分:100
最低分:83
平均分:90.5
计算1-100之间的数字中偶数的个数:50

创建流

package com.lzhpo.stream.createflow;

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * 创建流
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Demo {
    public static void main(String[] args) {
        /**
         * 使用Stream.of创建流
         */
        Stream<String> stringStream = Stream.of("i","love","you");
        stringStream.map(String::toUpperCase).forEach(System.out::println);

        /**
         * 使用数组创建流
         */
        int[] arr = new int[]{99,66,55,77,88,100};
        IntStream intStream = Arrays.stream(arr);
        int sum = intStream.sum();//求和
        System.out.println("求和:" + sum);

        /**
         * 由函数生成流,创建无限流
         */
        Stream.iterate(0,n -> n + 2)
                .limit(10)
                .forEach(System.out::println);
    }
}

Java8新特性之Optional类

Optional简介

  • 空指针异常是在学习和开发中最常见的问题之一,
  • 为了解决这个问题,在java8中新增了Optional类。
  • 这个类在java.util包下,使用这个类可以更好的支持函数式编程,
  • 并且可以简化以前对null的判断。

简单示例

package com.lzhpo.optional;

import com.lzhpo.stream.mapreduce.InitData;
import com.lzhpo.stream.useStream.Student;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Demo {
    public static void main(String[] args) {

        /**
         * 初始数据
         */
        List<Student> list = InitData.getStudent();
        List<Integer> core = list.stream()
                .map(Student::getScore)
                .collect(Collectors.toList());
        System.out.println("初始分数:" + core);

        /**
         *  例1:计算成绩小于60分的学生分数总和。
         *  因为没有成绩小于60分的。
         */
        List<Student> studentList = InitData.getStudent();
        Optional<Integer> count = studentList.stream()
                .filter(s -> s.getScore() < 60)
                .map(Student::getScore)
                .reduce((a,b) -> a + b);
        //如果要是空(无),返回0
        System.out.println("成绩小于60分的学生成绩总和:" + count.orElse(0));

        /**
         * 例2:
         */
        Map<Integer,String> map = new HashMap<>();
        map.put(1001, "篮球");
        map.put(1002, "足球");
        map.put(1003, "羽毛球");
        map.put(1004, "乒乓球");
        //没有1005,返回无
        String sport = Optional.ofNullable(map.get(1005))
                .orElse("无");
        System.out.println("获取编号为1005的运动项目:" + sport);
    }
}

Java8新特性之日期处理

LocalDate

package com.lzhpo.data;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

/**
 * LocalDate:
 *
 * LocalDate类只能操作日期相关的数据,不包含时间。
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class LocalDateDemo {
    public static void main(String[] args) {

        //今天的日期,不包括时间
        LocalDate date1 = LocalDate.now();
        System.out.println("今天的日期,不包括时间:" + date1);

        int year = date1.getYear();
        int month = date1.getMonthValue();
        int day = date1.getDayOfMonth();

        //格式化日期
        String date2 = date1.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
        System.out.println("格式化日期:" + date2);

        //判断闰年
        boolean leap = date1.isLeapYear();
        System.out.println("今年是否是闰年:" + leap);

        //获取该月份有多少天
        int len = date1.lengthOfMonth();

        //设置日期
        LocalDate date3 = LocalDate.parse("2016-06-15");
        LocalDate date4 = LocalDate.of(216,06,15);

        //判断两个日期是否相等
        if (date4.equals(date3)){
            System.out.println("日期相等");
        }

        //设置一周后的日期
        LocalDate nextWeek = date1.plus(1, ChronoUnit.WEEKS);
        System.out.println("今天:" + date1);
        System.out.println("一周后:" + nextWeek);
    }
}

LocalTime

package com.lzhpo.data;

import java.time.LocalTime;

/**
 * LocalTime:
 *
 * LocalTime类只能操作时间相关的数据,不包含日期。
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class LocalTimeDEmo {
    public static void main(String[] args) {
        //获取当前时间
        LocalTime time1 = LocalTime.now();
        LocalTime time2 = LocalTime.now().withNano(0);//去除毫秒
        System.out.println("当前时间:" + time2);

        //2小时后的时间
        LocalTime newTime = time1.plusHours(2);
        System.out.println("两小时后:" + newTime);

        //设置时间
        LocalTime time3 = LocalTime.of(6, 30, 28);
        LocalTime time4 = LocalTime.parse("18:20:36");
    }
}

LocalDateTime

package com.lzhpo.data;

import java.time.LocalDateTime;

/**
 * LocalDateTime:
 *
 * LocalDateTime类可以处理日期和时间的数据。
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class LocalDateTimeDemo {
    public static void main(String[] args) {
        //获取当前日期和时间
        LocalDateTime date1 = LocalDateTime.now();
        System.out.println(date1);

        LocalDateTime date2 = LocalDateTime.of(2017, 7, 11, 12, 30, 20);
        System.out.println(date2);
    }
}

Duration和Period

  • Duration类用来获取两个LocalTime相差的时间。
  • Period类用来获取两个LocalDate相差的日期。

示例1

package com.lzhpo.data.durationperiod;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;

/**
 * Duration和Period
 *
 * Duration类用来获取两个LocalTime相差的时间。
 * Period类用来获取两个LocalDate相差的日期。
 *
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Demo {
    public static void main(String[] args) {
        //计算两个LocalTime相差的时间
        LocalTime time1 = LocalTime.of(7, 20, 00);
        LocalTime time2 = LocalTime.of(7, 30, 10);
        Duration duration = Duration.between(time1, time2);
        System.out.println(duration.getSeconds());

        //计算两个LocalDate相差的日期
        LocalDate date1 = LocalDate.of(2017, 7, 11);
        LocalDate date2 = LocalDate.of(2017, 7, 13);
        Period period = Period.between(date1, date2);
        System.out.println(period.getDays());
    }
}

示例2

package com.lzhpo.data.durationperiod;

import java.time.*;

/**
 * <p>
 * Create By IntelliJ IDEA
 * Author:lzhpo
 * </p>
 */
public class Example1 {
    public static void main(String[] args) {

        /**
         * 计算祖国从建国到现在过了多少年多少月多少日了
         */
        System.out.println("================计算祖国从建国到现在过了多少年多少月多少日================");
        // 建国日期
        LocalDate begin1 = LocalDate.of(1949, 10, 1);
        // 今天日期
        LocalDate end1 = LocalDate.now();

        Period period = Period.between(begin1, end1);
        System.out.println("祖国已成立:");
        System.out.println(period.getYears() + "年");
        System.out.println(period.getMonths() + "月");
        System.out.println(period.getDays() + "天");
        System.out.println();

        /**
         * 计算祖国成立多少天
         */
        System.out.println("================计算祖国成立多少天================");
        LocalDate date = LocalDate.of(1949, 10, 1);
        LocalTime time = LocalTime.of(7, 20, 00);

        LocalDateTime begin2 = LocalDateTime.of(date, time);
        LocalDateTime end2 = LocalDateTime.now();

        Duration between = Duration.between(begin2, end2);

        System.out.println("祖国成立:" + between.toDays() + "天");
        System.out.println();

        /**
         * 设定时区
         */
        System.out.println("================设定时区================");
        //获取本地的时区时间
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println(now);

        //设定时区为美国洛杉矶
        ZoneId zone = ZoneId.of("America/Los_Angeles");
        //获取指定的时区时间
        ZonedDateTime usa = ZonedDateTime.now(zone);
        System.out.println(usa);

    }
}
↓↓↓ 打开微信关注我的微信公众号 ↓↓↓

会打篮球的程序猿

领取精品免费学习资料哦~
正文到此结束
本文目录