• JDK8--06:Stream流


    一、描述

    Stream流提供了筛选与切片、映射、排序、匹配与查找、归约、收集等功能

    筛选与切片:

      filter:接收lambda,从流中排除某些元素

      limit(n):截断流,使其元素不超过n

      skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

      distinct:筛选,通过流所生成的元素的hashCode和equals去重

    映射:

      map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

      flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

    排序:

      sorted()自然排序:comparable

      sorted(Comparator com)定制排序:comparator

    查找与匹配:

      allMatch:检查是否匹配所有元素

      anyMatch:检查是否至少匹配一个元素

      noneMatch:检查是否所有都不匹配

      findFirst:返回第一个元素

      findAny:返回当前流中任意元素

      count:返回当前流中元素个数

      max:返回当前流中最大值

      min:返回流中最小值

    归约:

      reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

    收集:

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

    二、Strem语法

      Stream分为创建流、中间操作和终止操作三个部分

      (1)创建流有三种方式:

      第一种是使用通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象

            //1、通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象
            List list = new ArrayList();
            Stream stream = list.stream();
            Stream stream1 = list.parallelStream();

      第二种是通过数组中的静态方法stream()获取数组流

            //2、通过数组中的静态方法stream()获取数组流
            String[] arr = new String[0];
            Stream stream2 = Arrays.stream(arr);

      第三种是通过Stream中的静态方法of()创建

            List list = new ArrayList();//3、通过Stream中的静态方法of()创建
            Stream stream3 = Stream.of(list);

      除了上述三种,还有一种无限流的创建

      下面的迭代表示从0开始,每次加2,无限循环;生成就会无限生成随机数

            //无限流------迭代
            Stream stream4 = Stream.iterate(0, x->x+2);
            //无限流------生成
            Stream stream6 = Stream.generate(()->Math.random());

      (2)中间操作

      中间操作就是筛选切片、映射等都属于中间操作,操作后仍然返回一个流,以上面的无限流--迭代为例,可以使用截断流limit不让其无限循环,只要前10个

            Stream stream5 = stream4.limit(10);

      (3)终止操作,将流对象转换成数据对象的操作,以上述中间操作为例,加循环输出

            //加中间操作的终止操作
            stream4.limit(3).forEach(System.out::println);

      输出结果:0 2 4

    三、中间操作详解

      Stream的操作主要就是中间操作如何处理,接下来详细说明中间操作   

    1、筛选与切片

    为了代码演示需要,先创建一个Student类,和一个状态的枚举类

        public enum Status{
            FREE,
            BUSY,
            VOCATION
        }
    
    
    
    
        class Student{
            private String name;
            private Integer age;
            private Status status;
    
            public Student(String name,Integer age){
                this.name = name;
                this.age = age;
            }
    
            public Student(String name,Integer age, Status status){
                this.name = name;
                this.age = age;
                this.status = status;
            }
    
            public String getName() {
                return name;
            }
    
            public Integer getAge() {
                return age;
            }
    
            public Status getStatus(){
                return status;
            }
    
            public String toString(){
                return JSON.toJSONString(this);
            }
    
            /*public int hashCode(){
                int result = 1;
                result = this.getAge().hashCode() + this.getName().hashCode();
                return result;
            }
    
            @Override
            public boolean equals(Object obj) {
                if(obj instanceof Student){
                    if(this.name.equals(((Student) obj).getName()) && this.age == ((Student) obj).getAge()){
                        return true;
                    }
                }else {
                    return super.equals(obj);
                }
                return false;
            }*/
        }

    创建一个Student的集合

    List<Student> students1 = Arrays.asList(
                new Student("lcl1",18, Status.BUSY)
                ,new Student("lcl2",30, Status.VOCATION)
                ,new Student("lcl3",50, Status.FREE)
                ,new Student("lcl4",20, Status.FREE)
                ,new Student("lcl5",25, Status.BUSY)
                ,new Student("lcl6",8, Status.FREE)
                ,new Student("lcl2",30, Status.VOCATION));

    筛选与切片:

      filter:接收lambda,从流中排除某些元素

      limit(n):截断流,使其元素不超过n

      skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

      distinct:筛选,通过流所生成的元素的hashCode和equals去重

    下面四个例子,分别对应四个中间操作的处理,操作内容分别为:

      只获取年龄小于10的Student后,将集合中Student对象输出;期望输出对象为:lcl5

      返回年龄大于10的前三个Student对象并输出:期望输出对象为:lcl1,lcl2,lcl3

      跳过两个Student对象后取前三个:期望输出对象为:lcl3,lcl4,lcl5

      输出集合中去重后的list对象:期望输出对象为集合中所有,包括两个lcl2,因为这里的去重操作是根据对象的hashCode和equals去判断是否重复的,由于两个lcl2都是new出来的,因此自然非同一个对象,但是如果我们重新hashCode和equals方法,按照名字和年龄一样,就会返回一样的hash码,且equals返回true(上述Student类中注释了重新的hashCode和equals方法,放开即可),则distinct会输出一个lcl2,下面演示输出按照重写hashCode和equals方法验证

        /**
         * 筛选与切片
         * filter:接收lambda,从流中排除某些元素
         * limit(n):截断流,使其元素不超过n
         * skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补
         * distinct:筛选,通过流所生成的元素的hashCode和equals去重
         */
        @Test
        public void tst2(){
            log.info("filter测试==============================");
            students1.stream().filter((x)->x.getAge() < 10).forEach(System.out::println);
    
            //惰性求值:所有中间操作不会做任何处理,只有在终止操作时,才会处理
            //内部迭代,StreamAPI自己处理的迭代处理
            //短路,只要找到满足的数据后,后面的流程就不再处理
            log.info("limit测试==============================");
            students1.stream().filter((x)->{log.info("filter");return x.getAge() > 10;}).limit(3).forEach(System.out::println);
    
            log.info("skip测试==============================");
            students1.stream().skip(2).limit(3).forEach(System.out::println);
    
            log.info("distinct测试==============================");
            students1.stream().distinct().forEach(System.out::println);
        }

    测试结果:

    2020-05-29 10:59:57.835  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter测试==============================
    {"age":8,"name":"lcl6","status":"FREE"}
    2020-05-29 10:59:58.003  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : limit测试==============================
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":18,"name":"lcl1","status":"BUSY"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":30,"name":"lcl2","status":"VOCATION"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
    {"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : skip测试==============================
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    2020-05-29 10:59:58.006  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : distinct测试==============================
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":8,"name":"lcl6","status":"FREE"}

     可以发现,所有的输出都与我们期望的一致,根据测试结果,有两个概念需要说下,就是惰性求值和短路,其实在代码的注释中已经有了,这里结合测试结果再详细说明一下

      惰性求值:测试limit的时候,在中间操作中加了输出filter的操作,但是看测试结果,filter的输出和最终的结果输出一样,也是循环一次输出一次,而非通常想的会把所有的filter输出完之后,再循环输出测试结果,这就是惰性求值,即等输出结果时,在求值,而非过程中就求值

      短路:还是以limit测试为例,在使用filter过滤后,应该还有6个元素符合条件,但是在limit(3)之后,只输出了3个对象,最重要的是,连filter字符串都是只输出了三个,而非6个,这就是因为Stream中间操作的短路操作,即找到满足自己的数据后,就不再处理,这样其实也可以提升效率。

    2、映射:

      map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

      flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

    为了做测试,先创建一个方法,传入一个字符串,返回一个单个字符的Stream流

        public static Stream<Character> filterChaacter(String str){
            List<Character> list = new ArrayList<>();
            for (Character c : str.toCharArray()) {
                list.add(c);
            }
            return list.stream();
        }

    demo需求:

      将数据组中的字符串变为大写

      输出所有学生的名字

      将数组中所有的字符串输出为单个字符

        @Test
        public void test3(){
    
            log.info("模拟map操作");
            List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
            list.stream().map((str)->str.toUpperCase()).forEach(System.out::println);
            students1.stream().map(Student::getName).forEach(System.out::println);
    
    
            log.info("模拟多重流操作");
            Stream<Stream<Character>> stream = list.stream().map(Jdk8StreamDemoTest::filterChaacter);
            stream.forEach(sm -> sm.forEach(System.out::println));
    
            log.info("模拟flatMap操作");
            list.stream().flatMap(Jdk8StreamDemoTest::filterChaacter).forEach(System.out::println);
        }

      上述代码中,模拟map操作块,即是对前两个demo需求的测试,没什么特别可说的,就是使用map从原有对象中获取我们所需要的对象;这里主要说明一下第三个demo需求,由于新增的filterChaacter方法返回的是一个Stream流,那么使用map的话就会得到一个Stream<Stream<Character>>的多重流,所以输出是,就需要双重循环(如模拟多重流代码块),非常的麻烦,因此就有了flatMap,他直接将返回的Stream流拼接成一个Stream流,这样输出的时候就只需要循环一次即可(如模拟flatMap操作)

      测试结果

    AAA
    BBB
    CCC
    DDD
    lcl1
    lcl2
    lcl3
    lcl4
    lcl5
    lcl6
    lcl2
    2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟多重流操作
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d
    2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟flatMap操作
    a
    a
    a
    b
    b
    b
    c
    c
    c
    d
    d
    d

    3、排序:

      sorted()自然排序:comparable

      sorted(Comparator com)定制排序:comparator

        @Test
        public void test4(){
            //自然排序
            Arrays.asList("uio","fghf","dsf","ewre","gffjf","poiklk").stream().sorted().forEach(System.out::println);
            //定制排序
            students1.stream().sorted((s1,s2)->s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
            //倒序排序
            students1.stream().sorted((s1,s2)->-s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
        }

    测试结果:

    dsf
    ewre
    fghf
    gffjf
    poiklk
    uio
    {"age":8,"name":"lcl6","status":"FREE"}
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":30,"name":"lcl2","status":"FREE"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":50,"name":"lcl3","status":"FREE"}
    {"age":30,"name":"lcl2","status":"VOCATION"}
    {"age":30,"name":"lcl2","status":"FREE"}
    {"age":25,"name":"lcl5","status":"BUSY"}
    {"age":20,"name":"lcl4","status":"FREE"}
    {"age":18,"name":"lcl1","status":"BUSY"}
    {"age":8,"name":"lcl6","status":"FREE"}

      这个没什么特别可说的,就是一个排序,空参的方法使用自然排序,传入排序方法的,按照穿的比较器进行排序

    4、查找与匹配:

      allMatch:检查是否匹配所有元素

      anyMatch:检查是否至少匹配一个元素

      noneMatch:检查是否所有都不匹配

      findFirst:返回第一个元素

      findAny:返回当前流中任意元素

      count:返回当前流中元素个数

      max:返回当前流中最大值

      min:返回流中最小值

    demo需求:

      判断集合中是否状态全为BUSY

      判断集合中是否存在状态为BUSY的数据

      判断集合中是否没有一个状态为BUSY的数据

      按照年龄排序后取第一个

      取任意一个元素

      获取集合中数据总数

      获取年龄最大的Student

      获取年龄最小的Student

        @Test
        public void test5(){
            log.info("allMatch结果{}",students1.stream().allMatch((e)-> e.getStatus() == Status.BUSY));
            log.info("anyMatch结果{}",students1.stream().anyMatch((e)-> e.getStatus() == Status.BUSY));
            log.info("noneMatch结果{}",students1.stream().noneMatch((e)-> e.getStatus() == Status.BUSY));
    
            Optional<Student> optionalStudent = students1.stream().sorted((e1, e2)->e1.getAge().compareTo(e2.getAge())).findFirst();
            if(optionalStudent.isPresent()) log.info("findFirst结果{}",optionalStudent.get());
    
            Optional<Student> optionalStudent1 = students1.stream().filter((e)->e.getStatus()==Status.FREE).findAny();
            if(optionalStudent1.isPresent()) log.info("findAny结果{}",optionalStudent1.get());
    
            log.info("count结果{}",students1.stream().count());
    
            Optional<Student> optionalStudent2 = students1.stream().max((e1,e2)->e1.getAge().compareTo(e2.getAge()));
            if(optionalStudent2.isPresent()) log.info("max结果{}",optionalStudent2.get());
    
            Optional<Student> optionalStudent3 = students1.stream().min((e1,e2)->e1.getAge().compareTo(e2.getAge()));
            if(optionalStudent3.isPresent()) log.info("min结果{}",optionalStudent3.get());
        }

      输出结果:

    2020-05-29 10:56:39.830  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : allMatch结果false
    2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : anyMatch结果true
    2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : noneMatch结果false
    2020-05-29 10:56:39.833  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findFirst结果{"age":8,"name":"lcl6","status":"FREE"}
    2020-05-29 10:56:39.936  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findAny结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:56:40.025  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : count结果7
    2020-05-29 10:56:40.027  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : max结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:56:40.028  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : min结果{"age":8,"name":"lcl6","status":"FREE"}

       上述代码示例中,有的地方的返回是个Optional,这个下一篇会说明

    5、归约:

      reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

    demo需求:

      将一个集合中的数据,依次相加求和后再与100求和

      将集合中学生的年龄求和后再加10

        @Test
        public void test6(){
            List<Integer> list = Arrays.asList(1,2,3,4,5);
            log.info("reduce结果{}",list.stream().reduce(100, (x,y)->x+y));
            log.info("reduce计算年龄总和结果{}",students1.stream().map((x)->x.getAge()).reduce(10,(x,y)->x+y));
        }

      测试结果:

    2020-05-29 10:56:08.448  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce结果115
    2020-05-29 10:56:08.450  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce计算年龄总和结果191

    6、收集:

      collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,可以计算总和,最大值、最小值、平均值、分组、多重分组、分区和字符串操作等

      由于collect的内容比较多,所以分开一个一个说明

      (1)数据收集转换

      demo:

        将所有的名字获取,并添加到集合中

        将所有的名字获取,并添加到特殊集合中,例如HashSet

            List list = students1.stream().map(Student::getName).collect(Collectors.toList());
            log.info("收集:collect测试结果{}",JSON.toJSONString(list));
    
            Set set = students1.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
            log.info("收集:特殊集合collect测试结果{}",JSON.toJSONString(set));

      测试结果:

    2020-05-29 10:44:37.478  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect测试结果["lcl1","lcl2","lcl3","lcl4","lcl5","lcl6","lcl2"]
    2020-05-29 10:44:37.481  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:特殊集合collect测试结果["lcl5","lcl6","lcl3","lcl4","lcl1","lcl2"]

       (2)计算总数、总和、最大值、最小值、平均值等数据收集操作

            log.info("收集:collect总数测试结果{}",students1.stream().collect(Collectors.counting()));
    
            log.info("收集:collect计算年龄平均值测试结果{}",students1.stream().collect(Collectors.averagingInt(Student::getAge)));
    
            log.info("收集:collect计算年龄总和测试结果{}",students1.stream().collect(Collectors.summarizingInt(Student::getAge)));
    
            log.info("收集:collect获取年龄最大测试结果{}",students1.stream().collect(Collectors.maxBy((s1,s2)->s1.getAge().compareTo(s2.getAge()))).get());
    
            log.info("收集:collect获取最小的年龄值测试结果{}",students1.stream().map(Student::getAge).collect(Collectors.minBy((s1,s2)->s1.compareTo(s2))).get());

    测试结果:

    2020-05-29 10:44:37.483  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect总数测试结果7
    2020-05-29 10:44:37.484  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄平均值测试结果25.857142857142858
    2020-05-29 10:44:37.485  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄总和测试结果IntSummaryStatistics{count=7, sum=181, min=8, average=25.857143, max=50}
    2020-05-29 10:44:37.488  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取年龄最大测试结果{"age":50,"name":"lcl3","status":"FREE"}
    2020-05-29 10:44:37.589  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取最小的年龄值测试结果8

     (3)分组

      需求:

        单一分组:按照状态分组

        多重分组:先按照状态分组,再按照年龄分组(以26为界,大于26为中老年,小于26为青少年)

            Map map3 = students1.stream().collect(Collectors.groupingBy(Student::getStatus));
            log.info("收集:collect按照Status分组测试结果{}", JSON.toJSONString(map3));
    
    
            Map<Status,Map<String,List<Student>>> map = students1.stream().collect(Collectors.groupingBy(Student::getStatus
                    ,Collectors.groupingBy((x)->{
                        if(((Student)x).getAge() > 26){
                            return "中老年";
                        }else{
                            return "青少年";
                        }
                    })
            ));
            log.info("收集:collect按照Status分组后按照年龄二次分组测试结果{}", JSON.toJSONString(map));

      测试结果:

    2020-05-29 10:44:37.591  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组测试结果{"VOCATION":[{"age":30,"name":"lcl2","status":"VOCATION"}],"FREE":[{"age":50,"name":"lcl3","status":"FREE"},{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"BUSY":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}
    2020-05-29 10:44:37.592  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组后按照年龄二次分组测试结果{"VOCATION":{"中老年":[{"age":30,"name":"lcl2","status":"VOCATION"}]},"FREE":{"中老年":[{"age":50,"name":"lcl3","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"青少年":[{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"}]},"BUSY":{"青少年":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}}

      (4)使用IntSummaryStatistics计算总数、总和、最大值、最小值、平均值等操作

            IntSummaryStatistics intSummaryStatistics = students1.stream().collect(Collectors.summarizingInt(Student::getAge));
            log.info("收集:collect按照年龄平均值{},总数{},最大值{},最小值{},总和{}", intSummaryStatistics.getAverage(),intSummaryStatistics.getCount(),intSummaryStatistics.getMax(),intSummaryStatistics.getMin(),intSummaryStatistics.getSum());

      测试结果:

    2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照年龄平均值25.857142857142858,总数7,最大值50,最小值8,总和181

      (5)字符串操作

            String s = students1.stream().map(Student::getName).collect(Collectors.joining("=="));
            log.info("收集:collect连接字符串测试结果{}", s);

      测试结果:

    2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect连接字符串测试结果lcl1==lcl2==lcl3==lcl4==lcl5==lcl6==lcl2
  • 相关阅读:
    对技术的研究就是对世界的理解
    程序员的方向
    程序员的分类
    设置tomcat的catalina_home引发的问题
    关于用osgModeling批量生成管道
    Cocos2d-X学习之Ref类
    ApiDemos示例学习(2)——App->Activity->Animation
    ApiDemos示例学习(1)——ApiDemos示例的导入
    eclipse中调出android sdk manager和android virtual device manager图标
    Mono For Android中AlarmManager的使用
  • 原文地址:https://www.cnblogs.com/liconglong/p/12986255.html
Copyright © 2020-2023  润新知