为啥放着Java16不用,都在用Java8?看完Java8的新特

2021-03-24  本文已影响0人  Java架构大仙

函数式接口

java内置四大核心函数式接口


image.png

核心接口子接口


image.png
//Consumer<T>消费型接口
    public static void test1(){
        cost(8888, (m) -> System.out.println("共消费:" + m + "元"));
    }

    public static void cost(double money,Consumer<Double> con){
        con.accept(money);
    }
    
   //Supplier<T> 供给型接口
    public static void test2(){
        List<Integer> list = getNumList(8, () -> (int)(Math.random() * 100));
        for (Integer integer : list) {
            System.out.println(integer);
        }
    }

    //产生指定数量的整数,放入集合中
    public static List<Integer> getNumList(int num,Supplier<Integer> sup){
        List<Integer> list = new ArrayList<>();

        for (int i = 0; i < num; i++) {
            Integer n = sup.get();
            list.add(n);
        }

        return list;
    }
    
    //Function<T,R> 函数型接口
    public static void test3(){
        String string = strHandler("函数型接口测试 ", (str) -> str.trim().substring(0, 5));
        System.out.println(string);
    }

    //用于处理字符串
    public static String strHandler(String str,Function<String, String> fun){
        return fun.apply(str);
    }
    
  //Predicate<T> 断言型接口
    public static void test4(){
        List<String> list = Arrays.asList("hello","Lambda","ok");
        List<String> strList = filterStr(list, (s) -> s.length() > 3);
        for (String string : strList) {
            System.out.println(string);
        }
    }

    //将满足条件的字符串,放入集合中
    public static List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> strList = new ArrayList<>();

        for (String str : list) {
            if (pre.test(str)) {
                strList.add(str);
            }
        }
        return strList;
    }

方法引用与构造器引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:

使用注意事项:

1.Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
2.若Lambda 参数列表中第一个参数是实例方法调用者,第二个参数是实例方法的参数 可以使用 ClassName :: method

//对象的引用 :: 实例方法名    
    @Test
    public void test1(){
        PrintStream ps = System.out;
        Consumer<String> con = (str) -> ps.println(str);
        con.accept("Hello World!");
        
        Consumer<String> con2 = ps::println;
        con2.accept("Hello World!");
        
        Consumer<String> con3 = System.out::println;
    }

//类名 :: 静态方法名
    @Test
    public void test2(){
        Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
        Comparator<Integer> com2 = Integer::compare;
    }
//类名 :: 实例方法名
    @Test
    public void test3(){
        BiPredicate<String, String> bp = (x, y) -> x.equals(y);
        System.out.println(bp.test("abcde", "abcde"));
        
        BiPredicate<String, String> bp2 = String::equals;
        System.out.println(bp2.test("abc", "abc"));
        
        Function<Employee, String> fun = (e) -> e.show();
        System.out.println(fun.apply(new Employee()));
        
        Function<Employee, String> fun2 = Employee::show;
        System.out.println(fun2.apply(new Employee()));
    }

构造器引用:

public void test1(){
        Supplier<Employee> sup = () -> new Employee();
        System.out.println(sup.get());
        
        Supplier<Employee> sup1 = Employee::new;
        System.out.println(sup1.get());
    }

数组引用

public void test1(){
        Function<Integer, String[]> fun = (args) -> new String[args];
        String[] strs = fun.apply(10);
        System.out.println(strs.length);
        
        Function<Integer, String[]> fun1 = String[] :: new;
        String[] strs = fun1.apply(20);
        System.out.println(strs.length);
    }

Stream API

Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列Stream 自己不会存储元素,不会改变源对象且返回一个持有结果的新Stream,操作是延迟执行的Stream API 提供了一种高效且易于使用的处理数据的方式

Stream API 的操作步骤:

1.创建Stream数据源
2.中间操作
3.终止操作(终端操作)

创建Stream

default Stream stream() : 返回一个顺序流

default Stream parallelStream() : 返回一个并行流

注:Collection接口下的所有实现类或者子接口都可以通过 对象.stream() 的方法返回一个流给Stream对象

static Stream stream(T[] array)

注:从数组中获取流可以使用 Arrays.stream(数组名) 来进行获取

public static Stream of(T… values) : 返回一个流

使用 Stream stream = Stream.of(“a”,“b”,“c”) 获取流

迭代 public static Stream iterate(final T seed, final UnaryOperator f)

生成 public static Stream generate(Supplier s)

public void test1(){
        //1.可以通过Collection系列集合提供的stream() 或parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        //2.通过Arrays中静态方法 stream() 获取数组流
        Person[] persons = new Person[10];
        Stream<Person> stream2 = Arrays.stream(persons);

        //3.通过Stream类中的静态方法 of()
        Stream<String> stream3 = Stream.of("a","b","c");

        //4.创建无限流
        //迭代
        Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
        stream4.limit(8).forEach(System.out :: println);

        //生成
        Stream.generate(() -> Math.random()).limit(6)
            .forEach(System.out :: println);
    }    

中间操作

筛选与切片方法

映射方法

排序方法

/**
     * Stream API的中间操作
     */
    public class TestSteamAPI2 {

        List<Person> persons = Arrays.asList(
                new Person(2, "钱四", 24),
                new Person(1, "张三", 33),
                new Person(2, "李四", 24),
                new Person(3, "王五", 65),
                new Person(4, "赵六", 26),
                new Person(4, "赵六", 26),
                new Person(5, "陈七", 27)
        );

        //内部迭代,由Stream API完成
        @Test
        public void test1(){
            //中间操作,不会执行任何操作
            Stream<Person> stream = persons.stream()
                                        .filter((e) -> {
                                            System.out.println("Stream的中间操作");
                                            return e.getAge() > 25;
                                        });
            //终止操作,一次性执行全部内容,即"惰性求值"
            stream.forEach(System.out :: println);
        }

        //外部迭代
        @Test
        public void test2(){
            Iterator<Person> iterator = persons.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
            }
        }

        //limit,截断
        @Test
        public void test3(){
            persons.stream()
                .filter((e) -> {
                    System.out.println("迭代操作"); //短路
                    return e.getAge() > 24;
                })
                .limit(2)
                .forEach(System.out :: println);
        }

        //跳过skip,distinct去重(要重写equals和hashcode)
        @Test
        public void test4(){
            persons.stream()
                    .filter((e) -> e.getAge() > 23)
                    .skip(2)
                    .distinct()
                    .forEach(System.out :: println);
        }

        //映射
        @Test
        public void test5(){
            List<String> list = Arrays.asList("a","bb","c","d","e");

            list.stream().map((str) -> str.toUpperCase())
                .forEach(System.out :: println);
            System.out.println("---------------");

            persons.stream().map((Person :: getName)).forEach(System.out :: println);
            System.out.println("---------------");

            Stream<Stream<Character>> stream = list.stream()
                .map(TestSteamAPI2 :: filterCharacter);

            stream.forEach((s) -> {
                s.forEach(System.out :: println);
            });
            System.out.println("-----------------");

            //flatMap
            Stream<Character> stream2 = list.stream()
                .flatMap(TestSteamAPI2 :: filterCharacter);
            stream2.forEach(System.out :: println);
        }

        //处理字符串
        public static Stream<Character> filterCharacter(String str){
            List<Character> list = new ArrayList<>();

            for (Character character : str.toCharArray()) {
                list.add(character);
            }
            return list.stream();
        }

        //排序
        @Test
        public void test6(){
            List<String> list = Arrays.asList("bb","c","aa","ee","ddd");

            list.stream()
                .sorted() //自然排序
                .forEach(System.out :: println);
            System.out.println("------------");

            persons.stream()
                    .sorted((p1,p2) -> {
                        if (p1.getAge() == p2.getAge()) {
                            return p1.getName().compareTo(p2.getName());
                        } else {
                            return p1.getAge() - p2.getAge();
                        }
                    }).forEach(System.out :: println);

        }

    }

终止操作

查找与匹配方法

归约方法

收集方法

collect(Collector c)——将流转换为其他形式,接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法

/**
     * Stream API的终止操作
     */
    public class TestSteamAPI3 {

        List<Person> persons = Arrays.asList(
                new Person(2, "钱四", 24,Status.YOUNG),
                new Person(1, "张三", 23,Status.YOUNG),
                new Person(2, "李四", 24,Status.YOUNG),
                new Person(3, "王五", 65,Status.OLD),
                new Person(4, "赵六", 26,Status.MIDDLE),
                new Person(4, "赵六", 56,Status.OLD),
                new Person(5, "陈七", 27,Status.MIDDLE)
        );

        //查找与匹配
        @Test
        public void test1(){
            boolean b = persons.stream()
                                .allMatch((e) -> e.getStatus().equals(Status.YOUNG));
            System.out.println(b);

            boolean b2 = persons.stream()
                    .anyMatch((e) -> e.getStatus().equals(Status.YOUNG));
            System.out.println(b2);

            boolean b3 = persons.stream()
                    .noneMatch((e) -> e.getStatus().equals(Status.MIDDLE));
            System.out.println(b3);

            Optional<Person> op = persons.stream()
                    .sorted((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge()))
                    .findFirst();
            System.out.println(op.get());

            Optional<Person> optional = persons.stream()
                    .filter((e) -> e.getStatus().equals(Status.OLD))
                    .findAny();
            System.out.println(optional.get());
        }

        //最大,最小
        @Test
        public void test2(){
            long count = persons.stream()
                    .count();
            System.out.println(count);

            Optional<Person> optional = persons.stream()
                    .max((e1,e2) -> Integer.compare(e1.getId(), e2.getId()));
            System.out.println(optional.get());

            Optional<Integer> op = persons.stream()
                    .map(Person :: getAge)
                    .min(Integer :: compare);
            System.out.println(op.get());
        }

        //归约
        @Test
        public void test3(){
            List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8);

            Integer sum = list.stream()
                .reduce(0, (x,y) -> x + y);
            System.out.println(sum);
            System.out.println("------------");

            Optional<Integer> optional = persons.stream()
                    .map(Person :: getAge)
                    .reduce(Integer :: sum);
            System.out.println(optional.get());
        }

        //收集
        @Test
        public void test4(){
            List<String> list = persons.stream()
                    .map(Person :: getName)
                    .collect(Collectors.toList());
            list.forEach(System.out :: println);
            System.out.println("------------");

            Set<String> set = persons.stream()
                    .map(Person :: getName)
                    .collect(Collectors.toSet());
            set.forEach(System.out :: println);
            System.out.println("------------");

            HashSet<String> hashSet = persons.stream()
                    .map(Person :: getName)
                    .collect(Collectors.toCollection(HashSet :: new));
            hashSet.forEach(System.out :: println);
        }

        @Test
        public void test5(){
            Long count = persons.stream()
                    .collect(Collectors.counting());
            System.out.println("总人数="+count);
            System.out.println("----------------");

            //平均值
            Double avg = persons.stream()
                    .collect(Collectors.averagingInt(Person :: getAge));
            System.out.println("平均年龄="+avg);
            System.out.println("---------------");

            //总和
            Integer sum = persons.stream()
                    .collect(Collectors.summingInt(Person :: getAge));
            System.out.println("年龄总和="+sum);
            System.out.println("----------------");

            //最大值
            Optional<Person> max = persons.stream()
                    .collect(Collectors.maxBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
            System.out.println("最大年龄是"+max.get());
            System.out.println("----------------");

            //最小值
            Optional<Person> min = persons.stream()
                    .collect(Collectors.minBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
            System.out.println("最小年龄是"+min.get());
        }

        //分组
        @Test
        public void test6(){
            Map<Status, List<Person>> map = persons.stream()
                    .collect(Collectors.groupingBy(Person :: getStatus));//根据年龄层分组
            System.out.println(map);
        }

        //多级分组
        @Test
        public void test7(){
            Map<Status, Map<String, List<Person>>> map = persons.stream()
                    .collect(Collectors.groupingBy(Person :: getStatus ,Collectors.groupingBy((e) -> {
                        if (e.getId()%2 == 1) {
                            return "单号";
                        } else {
                            return "双号";
                        } 
                    })));
            System.out.println(map);
        }

        //分区
        @Test
        public void test8(){
            Map<Boolean, List<Person>> map = persons.stream()
                    .collect(Collectors.partitioningBy((e) -> e.getAge() > 30));
            System.out.println(map);
        }

        //IntSummaryStatistics
        @Test
        public void test9(){
            IntSummaryStatistics iss = persons.stream()
                    .collect(Collectors.summarizingInt(Person :: getAge));
            System.out.println(iss.getSum());
            System.out.println(iss.getAverage());
            System.out.println(iss.getMax());
        }

        @Test
        public void test10(){
            String str = persons.stream()
                    .map(Person :: getName)
                    .collect(Collectors.joining(",","人员名单:","等"));
            System.out.println(str);
        }
    }

并行流与串行

为了适应目前多核机器的时代,提高系统CPU、内存的利用率,在jdk1.8新的stream包中针对集合的操作也提供了并行操作流和串行操作流。并行流就是把内容切割成多个数据块,并且使用多个线程分别处理每个数据块的内容。Stream api中声明可以通过parallel()与sequential()方法在并行流和串行流之间进行切换,jdk1.8并行流使用的是fork/join框架进行并行操作
注:使用并行流并不是一定会提高效率,因为jvm对数据进行切片和切换线程也是需要时间的。所以数据量越小,串行操作越快;数据量越大,并行操作效果越好

/* FrokJoin框架
     */
    public class ForkJoinCalculate extends RecursiveTask<Long>{
        private static final long serialVersionUID = 1L;

        private long start;
        private long end;

        private static final long THRESHOLD = 10000;

        public ForkJoinCalculate() {

        }

        public ForkJoinCalculate(long start, long end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Long compute() {
            long length = end - start ;
            if (length <= THRESHOLD) {
                long sum = 0;
                for (long i = start; i <= end; i++) {
                    sum += i;
                }
                return sum;
            }else {
                long middle = (start + end) / 2; 
                ForkJoinCalculate left = new ForkJoinCalculate();
                left.fork();//拆分子任务,同时压入线程队列

                ForkJoinCalculate right = new ForkJoinCalculate();
                right.fork();
                return left.join() + right.join();
            }
        }

    }

public class TestForkJoin {

        /**
         * FrokJoin框架
         */
        @Test
        public void test1(){
            Instant start = Instant.now();

            ForkJoinPool pool = new ForkJoinPool();
            ForkJoinTask<Long> task = new ForkJoinCalculate(0,10000000000L);
            Long sum = pool.invoke(task);
            System.out.println(sum);

            Instant end = Instant.now();
            System.out.println(Duration.between(start,end).toMillis());
        }

        /**
         * for循环
         */
        @Test
        public void test2(){
            Instant start = Instant.now();
            long sum = 0L;

            for (long i = 0; i <= 10000000000L; i++) {
                sum += i;
            }
            System.out.println(sum);

            Instant end = Instant.now();
            System.out.println(Duration.between(start, end).toMillis());
        }

        /**
         * Java8并行流
         */
        @Test
        public void test3(){
            Instant start = Instant.now();
            LongStream.rangeClosed(0, 10000000000L)
                        .parallel()
                        .reduce(0,Long :: sum);
            Instant end = Instant.now();
            System.out.println(Duration.between(start, end).toMillis());
        }
    }

接口中的默认方法与静态方法

//jdk1.8以前接口中的变量必须是public static final的,方法也必须是public的,所以下面的定义是等价的
public interface MyService {
    public static final String KEY = "hello world";
    String key = "hello world";
    
    public abstract void sayHello();
    void sayHi();
}
//但是从jdk1.8开始,这种现象有了改变,jdk添加了接口的默认方法和静态方法,使用如下方式定义
public interface MyService {

    /* 静态方法 */
    static void method1(){
        System.out.println("这个是静态方法,调用方式为:MyService.method1()");
    }
    /* 默认方法 */
    default void method2(){
        System.out.println("这个是默认方法,调用方式为MyService实例.method2()");
    }
}

注:由于Java支持一个实现类可以实现多个接口,如果多个接口中存在同样的static和default方法会怎么样呢?
如果有两个接口中的静态方法一模一样,并且一个实现类同时实现了这两个接口,此时并不会产生错误,因为jdk8只能通过接口类调用接口中的静态方法,所以对编译器来说是可以区分的。但是如果两个接口中定义了一模一样的默认方法,并且一个实现类同时实现了这两个接口,那么必须在实现类中重写默认方法,否则编译失败

新时间日期API

原有日期api的缺点

新日期api简介

优势新日期api是线程安全的,统一放在java.time及其子包中,关注点分离,对于机器使用的时间戳和人可读的日期进行了类的分类

java.time及其子包说明

  • java.time包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求
  • java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统
  • java.time.format包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法
  • java.time.temporal包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式
  • java.time.zone包:这个包包含支持不同时区以及相关规则的类
public class LocalDateTimeTest {
    /* localDate/localtime/localdateTime */
    @Test
    public void test1(){
        //获取当前日期时间
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);
        //按照指定时间生成日期
        LocalDateTime ldt1 = LocalDateTime.of(2020,12,30,1,2,3);
        System.out.println(ldt1);
        //指定时间+2年
        System.out.println(ldt.plusYears(2));
        //指定时间-3月
        LocalDateTime ldt2 = ldt.minusMonths(3);
        System.out.println(ldt2);
        System.out.println(ldt.getYear());//获取年份

        System.out.println(LocalDate.now());//获取日期
    }

    /* 时间戳 (使用Unix元年 1970年1月1日 00:00:00到现在的毫秒数 ) */
    @Test
    public void test2(){
        Instant ins = Instant.now();  //默认使用UTC时区
        System.out.println(ins+",,,"+ins.getEpochSecond());

        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));//中国在东八区
        System.out.println(odt);

        Instant ins1 = Instant.ofEpochSecond(5);
        System.out.println(ins1);  //1970-01-01T00:00:05Z 从Unix元年偏移5s
    }

    /**
     * Duration : 用于计算两个“时间”间隔
     * Period : 用于计算两个“日期”间隔
    * */
    @Test
    public void test3() throws InterruptedException {
        Instant ins1 = Instant.now();
        Thread.sleep(1000);
        Instant ins2 = Instant.now();
        System.out.println("两个时间的间隔为--》"+Duration.between(ins1,ins2)); //两个时间的间隔为--》PT1S

        LocalDate date1 = LocalDate.of(2011,3,5);
        LocalDate date2 = LocalDate.now();
        Period pe = Period.between(date1,date2);
        System.out.println("两个日期的间隔为--》"+ pe+",,间隔的年为--》"+pe.getYears()); //两个日期的间隔为--》P6Y18D,,间隔的年为--》6
    }

    /* 时间校正器   TemporalAdjuster       */
    @Test
    public void test4(){
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println("今天几号--> "+ldt.getDayOfMonth()); //今天几号--> 23
        System.out.println("下个星期天是几号--》"+ ldt.with(TemporalAdjusters.next(DayOfWeek.SUNDAY))); //下个星期天是几号--》2017-03-26T18:36:30.477

        // 自定义下一个工作日
        LocalDateTime dateTime = ldt.with((temporal)->{
            LocalDateTime lt = (LocalDateTime)temporal;
            DayOfWeek dow = lt.getDayOfWeek();
            if(DayOfWeek.FRIDAY.equals(dow)){
                return lt.plusDays(3);
            }else if(DayOfWeek.SATURDAY.equals(dow)){
                return lt.plusDays(2);
            }else{
                return lt.plusDays(1);
            }
        });
        System.out.println("下个工作日是--》"+dateTime);  //下个工作日是--》2017-03-24T22:42:31.789
    }

    /* 格式化日期 */
    @Test
    public void test5(){
        // 自定义格式,当然也可以使用默认指定的格式
        DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(format.format(ldt));  //2017-03-23 22:49:37

        //字符串转日期
        LocalDateTime ldt2 = LocalDateTime.parse("2017-11-12 23:10:05",format);
        System.out.println(ldt2); //2017-11-12T23:10:05
    }

    /* 带时区的日期  ZonedDate、ZonedTime、ZonedDateTime */
    @Test
    public void test6(){
        LocalDateTime ldt1 = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); //Asia/shanghai time ->2017-03-23T22:57:21.084
        System.out.println("Asia/shanghai time ->"+ldt1);

        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("America/Marigot"));
        System.out.println(zdt); //2017-03-23T10:59:43.708-04:00[America/Marigot]
        System.out.println(zdt.toLocalDateTime());//2017-03-23T11:00:49.177 转换为当前时区时间

        System.out.println("------------------------------------------------");
        //获取时区ID列表
        //ZoneId.getAvailableZoneIds().stream().forEach(System.out::println);
    }
}

Optional

最大化减少空指针异常,Java 8引入了一个新的Optional类
Optional类是一个容器类,它可以保存类型T的值,代表这个值存在,或者保存为null,代表这个值不存在,
这是一个可以为null的容器对象,如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象

创造Optional类对象的API

//调用工厂方法创建Optional实例
Optional<String> name = Optional.of("tom");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);

Optional.ofNullable(T t):较常用

//下面创建了一个不包含任何值的Optional实例
Optional empty = Optional.ofNullable(null);

Optional.empty():

判断Optional容器是否包含对象

void ifPresent(Consumer<? super T> ):

//ifPresent方法接受lambda表达式作为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
    System.out.println("The length of the value is: " + value.length());
});

T orElse(T other):较常用

T orElseGet(Suppliper<? entend X> other):

//orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
//orElseGet可以接受一个lambda表达式生成默认值。
System.out.println(empty.orElseGet(() -> "Default Value"));
System.out.println(name.orElseGet(() -> "Default Value"));

T orElseThrow(Suppliper<? entend X> exceptionSuppliper):

try {
        //orElseThrow与orElse方法类似。与返回默认值不同,
        //orElseThrow会抛出lambda表达式或方法生成的异常 
        empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
        //输出: No value present in the Optional instance
        System.out.println(ex.getMessage());
}

总结

篇幅有限!篇幅有限!关于Java8新特性,就聊到这儿啦..啦..啦..

上一篇下一篇

猜你喜欢

热点阅读