Java 8使用Stream API进行集合处理实例(Lambda表达式)_stream api 字段中的集合-程序员宅基地

技术标签: lambda  java  stream  Java编程及开发  

 

Stream API简介

官方对Stream API给出的定义:A sequence of elemets supporting sequential and parallel aggregate operations。所以Java 8中引入的Stream API是一个用来处理数组和集合的API。Stream API具有如下的一些特性: 

  • Stream API不是数据结构,没有相关的内存存储。
  • 不支持索引访问,只是对序列化的元素(数组和集合)进行处理
  • 延迟计算,Stream API在进行终止操作之前不会开始计算。
  • 方便的执行并行运行。
  • 便捷的生成数组或集合。
  • 支持过滤、查找、转换、聚合、汇总等针对流的操作。

Stream的运行机制包含三个部分,分别是:数据源source,中间操作终止操作。流的数据源可以是一个数组、一个集合、一个生成器或一个I/O通道等。并且一个流可以由0个或者多个中间操作,每个中间操作都会返回一个新的流,以供下一个操作使用,一个流只能有一个终止操作。由于StreamAPI延迟计算的特点,因此stream只有在遇到终止操作时,才会执行stream的相关操作,在此之前并不会开始计算。

下面开始从Stream包含的数据源、中间操作和终止操作三个方面来对Stream API的使用进行举例说明。

Stream API使用详解

创建Stream源

Stream数据源(source)可以通过如下几种方式进行创建:

  • 通过数据创建。
  • 使用集合创建。
  • 使用Stream.generate方法创建无限流。
  • 使用Stream.iterate()方法创建无限流。
  • 使用其他API创建,例如文件I/O等。

通过数组,使用Stream.of(array)创建流:

    // 通过数组创建,使用Stream.of()方法
    @Test
    void generateDemo1() {
        String[] array = {"a", "b", "1", "2"};
        Stream<String> stream = Stream.of(array);
        stream.forEach(System.out::println);
    }

通过集合,使用.stream()方法创建流:

    // 通过集合,list.stream()
    @Test
    void generateDemo2() {
        List<String> list = Arrays.asList("a", "b", "1", "2");
        Stream<String> stream = list.stream();
        stream.forEach(System.out::println);
    }

通过Stream.generate()方法创建流:(因为该方法创建的为无限流,因此在输出时,需要使用limit方法来对流进行截取)

    // 通过Stream.generate()方法
    @Test
    void generateDemo3() {
        Stream<Integer> stream = Stream.generate(() -> 1);  // 这里是无限流,需要使用limit进行截取
        stream.limit(10)                                    // 使用limit方法获取最前面的10个数字
                .forEach(System.out::println);

    }

使用Stream.iterate()方法创建无限流,并使用limit方法截取流:

    // 通过Stream.iterate()方法
    @Test
    void generateDemo4() {
        Stream<Integer> stream = Stream.iterate(1, x -> x + 1); // 同样是无限流,需要使用limit进行截取
        stream.limit(10)                                              // 使用limit方法获取最前面的10个数字
                .forEach(System.out::println);

    }

通过其他API创建流,例如如下使用str.chars()方法可以创建String对象的IntStream流:

    // 通过其他的API
    @Test
    void generateDemo5() {
        String str = "abcd1234567890";
        IntStream stream = str.chars(); // 返回int类型的stream
        // 使用方法的引用(终止操作进行输出)
        stream.forEach(System.out::println); // 等价于stream.forEach(x -> System.out.println(x));
    }

通过文件I/O的方式来创建流,这里输出项目中pom.xml文件中的内容: 

    // 通过其他API(通过文件流)
    @Test
    void generateDemo6() throws IOException {
        Files.lines(Paths.get("/Users/yitian/Documents/IDEAWorkspaces/LocalProjects/learning-project/pom.xml"))
                .forEach(System.out::println);
    }

流的终止操作

Stream流的终止操作常见的有如下几个:

  • 循环:forEach
  • 计算:min、max、count、average
  • 查找:anyMatch、allMatch、noneMatch、findFirst、findAny
  • 汇聚:reduce
  • 收集:toArray、collect

当stream执行到终止操作时,才会真正的开始计算过程。下面为具体的方法实例:

    // 终止操作forEach
    @Test
    void demo1() {
        // 返回集合流中所有的偶数
        Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> {
            System.out.println("----");
            return x % 2 == 0;
        }).forEach(System.out::println); // 如果没有终止操作,filter中间操作并不会进行,stream有延迟运行的特点
    }

    // 终止操作:map(), sum(), count(), get(), findAny(), findFirst()
    @Test
    void demo2() {
        // 对集合中的元素,找到偶数然后进行求和
        int sum = Arrays.asList(1, 2, 3, 4, 5).stream()
                .filter(x -> x % 2 == 0)
                .mapToInt(x -> x)
                .sum();
        System.out.println(sum);

        // 计算集合中最大元素
        int max = Arrays.asList(1, 2, 3, 4, 5).stream()
                .max((a, b) -> a - b)
                .get();
        System.out.println(max);

        // 计算集合中最小元素
        int min = Arrays.asList(1, 2, 3, 4, 5).stream()
                .min((a, b) -> a - b)
                .get();
        System.out.println(min);

        // 查找偶数并计数
        long count = Arrays.asList(1, 2, 3, 4, 5).stream()
                .filter(x -> x % 2 == 0)
                .count();
        System.out.println(count);

        // 查找偶数并返回任意一个
        Optional<Integer> op1 = Arrays.asList(1, 2, 3, 4, 5).stream()
                .filter(x -> x % 2 == 0)
                .findAny();
        System.out.println(op1.get());

        // 查找偶数并返回第一个元素
        Optional<Integer> op2 = Arrays.asList(1, 2, 3, 4, 5).stream()
                .filter(x -> x % 2 == 0)
                .findFirst();
        System.out.println(op2.get());
    }

    // 终止操作:collect
    @Test
    void demo3() {
        // 从1到50里面的所有偶数,放到一个list中
//        Stream.iterate(1, x -> x + 1).limit(50).map(x -> x + " ").forEach(System.out::print);
        List<Integer> list = Stream.iterate(1, x -> x + 1)
                .limit(50)
                .filter(x -> x % 2 == 0)
                .collect(Collectors.toList()); // 操作后生成一个List集合
        list.stream()
                .map(x -> x + " ")
                .forEach(System.out::print);
    }

流的中间操作

Stream API允许0个或多个流的中间操作,常用的中间操作如下:

  • 过滤:filter
  • 去重:distinct
  • 排序:sorted
  • 截取:limit,skip
  • 转换:map/flatMap
  • 其他:peek

元素去重distinct或使用set:

    // 中间操作:distinct
    @Test
    void demo4() {
        // 去重
        Arrays.asList(1, 3, 4, 2, 2, 2, 5, 6, 7).stream()
                .distinct()
                .map(x -> x + " ")
                .forEach(System.out::print);
        System.out.println();

        // 使用set去重
        Set<Integer> set = Arrays.asList(1, 3, 4, 2, 2, 2, 5, 6, 7).stream()
                .collect(Collectors.toSet());
        set.stream().map(x -> x + " ").forEach(System.out::print);
    }

元素排序sort:

    // 中间操作:sort()排序
    @Test
    void demo5() {
        // 排序操作,默认为正序
        Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
                .sorted()
                .forEach(System.out::print);
        System.out.println();

        // 这种也为正序
        Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
                .sorted((a, b) -> a - b)
                .forEach(System.out::print);
        System.out.println();

        // 改为倒叙
        Arrays.asList(11, 2, 5, 1, 6, 8, 7).stream()
                .sorted((a, b) -> b - a)
                .forEach(System.out::print);
        System.out.println();

        // 字符串排序(按字符长度排序),并且为每个单词设置中间一个空格显示
        Arrays.asList("cn", "admin", "net", "io").stream()
                .map(x -> x + " ")
                .sorted((a, b) -> a.length() - b.length())
                .forEach(System.out::print);
        System.out.println();
    }

skip和limit可以实现分页的功能:

    // 中间操作:skip
    @Test
    void demo6() {
        // skip
        List<Integer> list = Stream.iterate(1, x -> x + 1)
                .limit(50)
                .sorted((a, b) -> b - a) // 从大到小排序
                .skip(10) // skip为忽略前十个
                .limit(10)
                .collect(Collectors.toList());
        list.stream()
                .map(x -> x + " ")
                .forEach(System.out::print);
//        40 39 38 37 36 35 34 33 32 31

        // 使用使用skip实现分页
        Stream.iterate(1, x -> x + 1)
                .limit(50)
                .skip(0) // 第一页: 40 39 38 37 36 35 34 33 32 31
                .limit(10)
                .map(x -> x + " ")
                .forEach(System.out::print);

        Stream.iterate(1, x -> x + 1)
                .limit(50)
                .skip(10) // 第二页:11 12 13 14 15 16 17 18 19 20
                .limit(10)
                .map(x -> x + " ")
                .forEach(System.out::print);
    }

转换map操作:

    // 中间操作:map转换,mapToInt
    @Test
    void demo7() {
        // 转换:将str进行分割,转换为整数并求和
        String str = "11,22,33,44,55,66";
        int sum = Stream.of(str.split(","))
                .map(Integer::valueOf)
                .mapToInt(x -> x)
                .sum();
        System.out.println(sum);

        sum = Stream.of(str.split(","))
                .mapToInt(Integer::valueOf)
                .sum();
        System.out.println(sum);
    }

    // 中间操作:map转换为自定义对象
    @Test
    void demo8() {
        String str = "tomcat, nginx, apache, jetty";
        // 将上面的字符串转换为4个User对象,下面三种方法等价
        Stream.of(str.split(", "))
                .map(x -> new User(x))
                .forEach(System.out::println);
        Stream.of(str.split(", "))
                .map(User::new)
                .forEach(System.out::println);
        Stream.of(str.split(", "))
                .map(User::build)
                .forEach(System.out::println);
    }

    static class User {
        private String name;
        public User(String name) {
            this.name = name;
        }

        public static User build(String name) {
            User user = new User(name);
            return user;
        }

        public String getName() {
            return name;
        }

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

        @Override
        public String toString() {
            return "User [name: "+name+"]";
        }
    }

中间操作peek,用于返回中间的计算结果:

    // 中间操作:peek
    @Test
    void demo9() {
        // peek方法
        String str = "11,22,33,44,55,66";
        int sum = Stream.of(str.split(","))
                .peek(x -> System.out.print(x + " "))
                .mapToInt(x -> Integer.valueOf(x))
                .sum();
        System.out.println(sum);
        // 可以输出中间结果的计算值: 11 22 33 44 55 66 231
    }

Stream的并行处理

Stream API除了上述具有的数据源、中间操作和终止操作外,在对集合或数据进行处理的过程中,还提供了并行计算的一个特性。使用Stream API可以很方便的对集合的处理使用多线程的方式进行,从而提高大型数据集合的处理效率。

在没有设置Stream API的并行计算时,其默认使用单线程的方式来进行运行,例如如下代码实例:

    // 默认为一个main线程进行(同步)
    @Test
    void demo1() {
        Optional<Integer> max = Stream.iterate(1, x -> x + 1)
                .limit(200)
                .peek(x -> {
                    System.out.println(Thread.currentThread().getName()); // 输出中间结果
                }).max(Integer::compare);
        System.out.println(max);
    }

在对stream中元素求最大值时,使用peek方法输出中间结果(用于计算的线程名称),从输出可以看到所有的线程都是main线程一个线程在执行。

在Stream API中开启并行处理比较简单,直接使用.parallel()中间操作就就可以实现集合的并行处理,例如如下:

    // 多线程计算
    @Test
    void demo2() {
        Optional<Integer> max = Stream.iterate(1, x -> x + 1)
                .limit(200)
                .peek(x -> {
                    System.out.println(Thread.currentThread().getName()); // 输出中间结果
                })
                .parallel() // 使用并行计算
                .max(Integer::compare);
        System.out.println(max);
    }

此时在观察输出的线程名称,可以看到除main线程之外还包含很多其他的线程在运行,如下。可以看到默认使用了8个线程来进行集合的并行处理,这里是因为运行该程序的物理机的CPU为8个core,因此这里默认的线程并行数和CPU核心数保持一致。

ForkJoinPool.commonPool-worker-7
ForkJoinPool.commonPool-worker-6
ForkJoinPool.commonPool-worker-1
ForkJoinPool.commonPool-worker-2
ForkJoinPool.commonPool-worker-5
ForkJoinPool.commonPool-worker-4
ForkJoinPool.commonPool-worker-3
main

上面加入parallel()中间操作时将流变成了并行流进行处理,其实并行流和序列流(sequence)是可以相互转换的,例如如下。此时在进行输出时,实际上还是只使用一个线程进行的集合处理:

    // 顺序流和并行流之间的转换,因为stream为延迟计算,因此谁在最后面,则为优先级较高
    @Test
    void demo3() {
        Optional<Integer> max = Stream.iterate(1, x -> x + 1)
                .limit(200)
                .peek(x -> {
                    System.out.println(Thread.currentThread().getName()); // 输出中间结果
                })
                .parallel()     // 设置为并行流
                .sequential()   // 设置为顺序流
                .max(Integer::compare);
        System.out.println(max);
    }

上面提到,加入parallel之后默认使用的并行线程数和CPU核心数保持一致,那么如果需要更改Stream处理的并行线程数,可以进行如下的设置:

  • 一是在方法中设置系统变量来进行设置:System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5");
  • 或者使用-D的方式来配置JVM的启动参数也可以设置。

但这里的线程数建议还是不要超过CPU和核心数为宜,或者直接保持默认即可。

    // Stream API内部是使用ForkJoinPool来实现的并行运行
    // 设置并行运行的线程数时,一般和所在物理机的CPU核心数相一致
    @Test
    void demo4() {
        // 使用设置启动参数的方式设置:-Djava.util.concurrent.ForkJoinPool.common.parallelism=5
        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5");
        Optional<Integer> max = Stream.iterate(1, x -> x + 1)
                .limit(200)
                .peek(x -> {
                    System.out.println(Thread.currentThread().getName());
                }).parallel()
                .max(Integer::compare);
        System.out.println(max);
    }

Java 8 Lambda表达式和Stream API使用实例

实例1:URL参数转换

对于类似于URL参数的字符串:"itemId=1&userId=10000&type=20&token=111111111111111&key=index"。将其根据参数名和参数值转换为Map集合:

    @Test
    void demo1() {
        String queryString = "itemId=1&userId=10000&type=20&token=111111111111111&key=index";
        Map<String, String> paras = Stream.of(queryString.split("&")) // 得到string[],每个元素为key=value
                .map(x -> x.split("="))                               // 对每个数组元素来进行分割,每个元素变为String[] = [key, value]
                .collect(Collectors.toMap(s -> s[0], s -> s[1]));            // toMap方法将第1步转化的数组转化为map
        System.out.println(paras);
        // 输出结果:{itemId=1, type=20, userId=10000, key=index, token=111111111111111}
    }

实例2:Book集合处理

Book对象类型如下:

public class Book {
    private int id;
    private String name;
    private double price;
    private String type;
    private LocalDate publishDate;

    public Book() {
    }

    public Book(int id, String name, double price, String type, LocalDate publishDate) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.type = type;
        this.publishDate = publishDate;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", type='" + type + '\'' +
                ", publishDate=" + publishDate +
                '}';
    }

    // getter and setter
}

构建Book集合:

    private List<Book> books() {
        List<Book> books = new ArrayList<>();
        books.add(new Book(1, "tomcat", 70d, "服务器", LocalDate.parse("2014-05-17")));
        books.add(new Book(2, "jetty", 60d, "服务器", LocalDate.parse("2015-12-01")));
        books.add(new Book(3, "nginx", 65d, "服务器", LocalDate.parse("2016-10-17")));
        books.add(new Book(4, "java", 66d, "编程语言", LocalDate.parse("2011-04-09")));
        books.add(new Book(5, "ruby", 80d, "编程语言", LocalDate.parse("2013-05-09")));
        books.add(new Book(6, "php", 40d, "编程语言", LocalDate.parse("2014-08-06")));
        books.add(new Book(7, "html", 44d, "编程语言", LocalDate.parse("2011-01-06")));
        books.add(new Book(8, "oracle", 150d, "数据库", LocalDate.parse("2013-08-09")));
        books.add(new Book(9, "mysql", 66d, "数据库", LocalDate.parse("2015-04-06")));
        books.add(new Book(10, "ssh", 70d, "编程语言", LocalDate.parse("2016-12-04")));
        books.add(new Book(11, "design pattern", 81d, "软件工程", LocalDate.parse("2017-04-08")));
        books.add(new Book(12, "refactoring", 62d, "软件工程", LocalDate.parse("2011-04-19")));
        books.add(new Book(13, "agile", 72d, "软件工程", LocalDate.parse("2016-02-18")));
        books.add(new Book(14, "managing", 42d, "软件工程", LocalDate.parse("2016-01-19")));
        books.add(new Book(15, "algorithm", 66d, "软件工程", LocalDate.parse("2010-05-08")));
        books.add(new Book(16, "oracle 12c", 150d, "数据库", LocalDate.parse("2016-05-08")));
        return books;
    }

处理1:将book集合中的所有的id取出来放到list的集合中

    @Test
    void demo2() {
        List<Integer> ids1 = books().stream()
                .map(book -> book.getId())
                .collect(Collectors.toList());
        System.out.println(ids1);
        //    输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

        // 使用方法引用
        List<Integer> ids2 = books().stream()
                .map(Book::getId)
                .collect(Collectors.toList());
        System.out.println(ids2);
        //    输出:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
    }

处理2:将book集合中的所有id取出来,使用逗号拼成一个字符串

@Test
    void demo3() {
        String str = books().stream()
                .map(book -> book.getId() + "")
                .collect(Collectors.joining(","));
        System.out.println(str);
        // 输出:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

        str = books().stream()
                .map(book -> book.getId() + "")
                .collect(Collectors.joining(",", "(", ")")); // 逗号隔开并使用()括起来
        System.out.println(str);
        // 输出:(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15)

        str = books().stream()
                .map(book -> "'"+book.getId()+"'")
                .collect(Collectors.joining(","));
        System.out.println(str);
        // 输出:'1','2','3','4','5','6','7','8','9','10','11','12','13','14','15'
    }

处理3:查找books中的所有类型

    // 处理3:查找books中的所有类型
    @Test
    void demo4() {
        // 输出所有类型
        List<String> types = books().stream()
                .map(book -> book.getType())
                .collect(Collectors.toList());
        System.out.println(types);
        // 输出:[服务器, 服务器, 服务器, 编程语言, 编程语言, 编程语言, 编程语言, 数据库, 数据库, 编程语言, 软件工程, 软件工程, 软件工程, 软件工程, 软件工程]

        types = books().stream()
                .map(book -> book.getType())
                .distinct()                     // 使用distinct去重
                .collect(Collectors.toList());
        System.out.println(types);
        // 输出:[服务器, 编程语言, 数据库, 软件工程]

        Set<String> typeSet = books().stream()
                .map(book -> book.getType())
                .collect(Collectors.toSet());   // 使用Set集合去重
        System.out.println(typeSet);
        // 输出:[编程语言, 服务器, 软件工程, 数据库]
    }

处理4: 对books集合进行排序(根据不同字段)

@Test
    void demo5() {
        // 1. 根据价格进行排序(升序排序)
        books().stream()
                .sorted((book1, book2) -> Double.compare(book1.getPrice(), book2.getPrice()))
                .forEach(System.out::println);
        System.out.println("---------------------");
        // 等价于
        Comparator<Book> comparator1 = (book1, book2) -> Double.compare(book1.getPrice(), book2.getPrice());
        books().stream()
                .sorted(comparator1)
                .forEach(System.out::println);
        System.out.println("---------------------");
//        输出:
//        Book{id=6, name='php', price=40.0, type='编程语言', publishDate=2014-08-06}
//        Book{id=14, name='managing', price=42.0, type='软件工程', publishDate=2016-01-19}
//        Book{id=7, name='html', price=44.0, type='编程语言', publishDate=2011-01-06}
//        Book{id=2, name='jetty', price=60.0, type='服务器', publishDate=2015-12-01}
//        Book{id=12, name='refactoring', price=62.0, type='软件工程', publishDate=2011-04-19}
//        Book{id=3, name='nginx', price=65.0, type='服务器', publishDate=2016-10-17}
//        Book{id=4, name='java', price=66.0, type='编程语言', publishDate=2011-04-09}
//        Book{id=9, name='mysql', price=66.0, type='数据库', publishDate=2015-04-06}
//        Book{id=15, name='algorithm', price=66.0, type='软件工程', publishDate=2010-05-08}
//        Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17}
//        Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04}
//        Book{id=13, name='agile', price=72.0, type='软件工程', publishDate=2016-02-18}
//        Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09}
//        Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}
//        Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}
//        Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}

        // 2. 降序排序
        Comparator<Book> comparator2 = (book1, book2) -> Double.compare(book2.getPrice(), book1.getPrice());
        books().stream()
                .sorted(comparator2)
                .forEach(System.out::println);
        System.out.println("---------------------");
        // 等价于
        books().stream()
                .sorted(comparator1.reversed()) // 按照正序的烦序排序
                .forEach(System.out::println);
        System.out.println("---------------------");
//        输出
//        Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}
//        Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}
//        Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}
//        Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09}
//        Book{id=13, name='agile', price=72.0, type='软件工程', publishDate=2016-02-18}
//        Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17}
//        Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04}
//        Book{id=4, name='java', price=66.0, type='编程语言', publishDate=2011-04-09}
//        Book{id=9, name='mysql', price=66.0, type='数据库', publishDate=2015-04-06}
//        Book{id=15, name='algorithm', price=66.0, type='软件工程', publishDate=2010-05-08}
//        Book{id=3, name='nginx', price=65.0, type='服务器', publishDate=2016-10-17}
//        Book{id=12, name='refactoring', price=62.0, type='软件工程', publishDate=2011-04-19}
//        Book{id=2, name='jetty', price=60.0, type='服务器', publishDate=2015-12-01}
//        Book{id=7, name='html', price=44.0, type='编程语言', publishDate=2011-01-06}
//        Book{id=14, name='managing', price=42.0, type='软件工程', publishDate=2016-01-19}
//        Book{id=6, name='php', price=40.0, type='编程语言', publishDate=2014-08-06}


        // 3. 多个属性值进行排序:先根据price排序(升序),price相同时根据publishDate进行排序(降序)
        books().stream()
                .sorted(comparator1.thenComparing(
                        (book1, book2) -> book1.getPublishDate().isAfter(book2.getPublishDate()) ? -1 : 1)
                )
                .forEach(System.out::println);
//        输出
//        Book{id=9, name='mysql', price=66.0, type='数据库', publishDate=2015-04-06}
//        Book{id=4, name='java', price=66.0, type='编程语言', publishDate=2011-04-09}
//        Book{id=15, name='algorithm', price=66.0, type='软件工程', publishDate=2010-05-08}

//        Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04}
//        Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17}
    }

对上述排序代码可以使用方法引用的方式进行简化:

    @Test
    void demo6() {
        // 1. 根据价格进行排序
        books().stream()
                .sorted(Comparator.comparing(Book::getPrice))
                .forEach(System.out::println);

        // 2. 根据价格进行降序排序
        books().stream()
                .sorted(Comparator.comparing(Book::getPrice).reversed())
                .forEach(System.out::println);

        // 3. 先根据价格进行排序(升序),然后在根据publishDate进行排序(降序)
        books().stream()
                .sorted(Comparator.comparing(Book::getPrice).thenComparing(Comparator.comparing(Book::getPublishDate)).reversed())
                .forEach(System.out::println);
    }

处理5:将books集合转换为map<bookId, Book Object>

    @Test
    void demo7() {
        Map<Integer, Book> bookMap = books().stream()
                .collect(Collectors.toMap(Book::getId, book -> book)); // 直接使用了方法的引用
        System.out.println(bookMap);
//        输出
//        {
//            1=Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17},
//            2=Book{id=2, name='jetty', price=60.0, type='服务器', publishDate=2015-12-01},
//            3=Book{id=3, name='nginx', price=65.0, type='服务器', publishDate=2016-10-17},
//            4=Book{id=4, name='java', price=66.0, type='编程语言', publishDate=2011-04-09},
//            5=Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09},
//            6=Book{id=6, name='php', price=40.0, type='编程语言', publishDate=2014-08-06},
//            7=Book{id=7, name='html', price=44.0, type='编程语言', publishDate=2011-01-06},
//            8=Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09},
//            9=Book{id=9, name='mysql', price=66.0, type='数据库', publishDate=2015-04-06},
//            10=Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04},
//            11=Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08},
//            12=Book{id=12, name='refactoring', price=62.0, type='软件工程', publishDate=2011-04-19},
//            13=Book{id=13, name='agile', price=72.0, type='软件工程', publishDate=2016-02-18},
//            14=Book{id=14, name='managing', price=42.0, type='软件工程', publishDate=2016-01-19},
//            15=Book{id=15, name='algorithm', price=66.0, type='软件工程', publishDate=2010-05-08},
//            16=Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}
//        }
    }

处理6:统计集合中的平均值、最大值、和最小值

    @Test
    void demo8() {
        // 1. 统计list中所有书的平均价格
        Double averagePrice = books().stream()
                .collect(Collectors.averagingDouble(Book::getPrice));
        System.out.println(averagePrice);
        // 输出:74.0

        // 2. 找出价格最大或最小的书 (这里默认是找到最大的)
        Optional<Book> book = books().stream()
                .max(Comparator.comparing(Book::getPrice));
        System.out.println(book);
        // 等价于
        book = books().stream()
                .collect(Collectors.maxBy(Comparator.comparing(Book::getPrice)));
        System.out.println(book);
        // 输出:Optional[Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}]

        // 3. 找到价格最小的Book
        book = books().stream()
                .collect(Collectors.maxBy(Comparator.comparing(Book::getPrice).reversed()));
        System.out.println(book);
        // 等价于
        book = books().stream()
                .collect(Collectors.minBy(Comparator.comparing(Book::getPrice)));
        System.out.println(book);
        // 输出:Optional[Book{id=6, name='php', price=40.0, type='编程语言', publishDate=2014-08-06}]

        // 4. 根据发布时间进行查找发布最晚的Book
        book = books().stream()
                .max(Comparator.comparing(Book::getPublishDate));
        System.out.println(book);
        // 输出:Optional[Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}]

        // 5. 找到价格最贵的,但出版时间最晚的一本书
        Comparator<Book> comparator = Comparator.comparing(Book::getPrice);
        book = books().stream()
                .collect(Collectors.maxBy(comparator.thenComparing(Comparator.comparing(Book::getPublishDate))));
        System.out.println(book);
        // 输出:Optional[Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}]
    }

处理7:对集合中的元素进行分组统计

@Test
    void demo9() {
        // 1. 根据type进行分组,统计每个类别有多少本书
        Map<String, List<Book>> booksMap = books().stream()
                .collect(Collectors.groupingBy(Book::getType));
        booksMap.keySet().forEach(type -> {
            System.out.println(type);
            System.out.println(booksMap.get(type));
            System.out.println("--------------------");
        });
//        编程语言
//        [Book{id=4, name='java', price=66.0, type='编程语言', publishDate=2011-04-09}, Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09}, Book{id=6, name='php', price=40.0, type='编程语言', publishDate=2014-08-06}, Book{id=7, name='html', price=44.0, type='编程语言', publishDate=2011-01-06}, Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04}]
//        --------------------
//        服务器
//        [Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17}, Book{id=2, name='jetty', price=60.0, type='服务器', publishDate=2015-12-01}, Book{id=3, name='nginx', price=65.0, type='服务器', publishDate=2016-10-17}]
//        --------------------
//        软件工程
//        [Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}, Book{id=12, name='refactoring', price=62.0, type='软件工程', publishDate=2011-04-19}, Book{id=13, name='agile', price=72.0, type='软件工程', publishDate=2016-02-18}, Book{id=14, name='managing', price=42.0, type='软件工程', publishDate=2016-01-19}, Book{id=15, name='algorithm', price=66.0, type='软件工程', publishDate=2010-05-08}]
//        --------------------
//        数据库
//        [Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}, Book{id=9, name='mysql', price=66.0, type='数据库', publishDate=2015-04-06}, Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}]
//        --------------------

        // 2. 统计每种类型数的数量
        Map<String, Long> booksCount = books().stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.counting()));
        System.out.println(booksCount);
//        {编程语言=5, 服务器=3, 软件工程=5, 数据库=3}

        // 3. 统计每种类型书的总价格
        Map<String, Double> priceMap = books().stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.summingDouble(Book::getPrice)));
        System.out.println(priceMap);
//        {编程语言=300.0, 服务器=195.0, 软件工程=323.0, 数据库=366.0}

        // 4. 统计每种类型的平均价格
        Map<String, Double> averagePrice = books().stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.averagingDouble(Book::getPrice)));
        System.out.println(averagePrice);
//        {编程语言=60.0, 服务器=65.0, 软件工程=64.6, 数据库=122.0}

        // 5. 找到每种类型中最贵的书
        Map<String, Optional<Book>> maxBooks = books().stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.maxBy(Comparator.comparing(Book::getPrice))));
        maxBooks.keySet().forEach(type -> {
            System.out.println(type);
            System.out.println(maxBooks.get(type));
            System.out.println("--------------------");
        });
//        编程语言
//        Optional[Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09}]
//        --------------------
//        服务器
//        Optional[Book{id=1, name='tomcat', price=70.0, type='服务器', publishDate=2014-05-17}]
//        --------------------
//        软件工程
//        Optional[Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}]
//        --------------------
//        数据库
//        Optional[Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}]
//        --------------------

        // 6. 按类型查找每种类型中出版时间最晚的书
        Map<String, Optional<Book>> publishBooks = books().stream()
                .collect(Collectors.groupingBy(Book::getType, Collectors.maxBy(Comparator.comparing(Book::getPublishDate))));
        publishBooks.keySet().forEach(type -> {
            System.out.println(type);
            System.out.println(publishBooks.get(type));
            System.out.println("-------------------");
        });
//        编程语言
//        Optional[Book{id=10, name='ssh', price=70.0, type='编程语言', publishDate=2016-12-04}]
//        -------------------
//        服务器
//        Optional[Book{id=3, name='nginx', price=65.0, type='服务器', publishDate=2016-10-17}]
//        -------------------
//        软件工程
//        Optional[Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}]
//        -------------------
//        数据库
//        Optional[Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}]
//        -------------------
    }

处理8:筛选book集合中price>90的书籍,并根据出版日期从近到远排序

    @Test
    void demo10() {
        // 取出price>80,出版时间从近到远
        books().stream()
                .filter(book -> book.getPrice() >= 80)
                .sorted(Comparator.comparing(Book::getPublishDate).reversed())
                .forEach(System.out::println);
//        Book{id=11, name='design pattern', price=81.0, type='软件工程', publishDate=2017-04-08}
//        Book{id=16, name='oracle 12c', price=150.0, type='数据库', publishDate=2016-05-08}
//        Book{id=8, name='oracle', price=150.0, type='数据库', publishDate=2013-08-09}
//        Book{id=5, name='ruby', price=80.0, type='编程语言', publishDate=2013-05-09}
    }

实例3:交易记录数据处理

基础类结构

交易人员类对象:

public class Trader {
    private String name;
    private String city;

    public Trader(String name, String city) {
        this.name = name;
        this.city = city;
    }
    
    // getter and setter
}

交易对象结构:

public class Transaction {
    private Trader trader;
    private int year;
    private int value;

    public Transaction(Trader trader, int year, int value) {
        this.trader = trader;
        this.year = year;
        this.value = value;
    } 
}

数据构建和处理

根据上述的类来构建数据集合:

    // build transaction list for query
    private List<Transaction> transactions() {
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        List<Transaction> transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
        return transactions;
    }

Query1: Find all transactions from year 2011 and sort them by value

    @Test
    public void query1() {
        List<Transaction> tr2011 = transactions().stream()
                .filter(transaction -> transaction.getYear() == 2011)
                .sorted(Comparator.comparing(Transaction::getValue))
                .collect(Collectors.toList());
        System.out.println(tr2011);
        // [Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300},
        // Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400}]
    }

Query2:What are all the unique cities where the traders work?

    @Test
    public void query2() {
        List<String> cities = transactions().stream()
                .map(transaction -> transaction.getTrader().getCity())
                .distinct()
                .collect(Collectors.toList());
        System.out.println(cities);
        // [Cambridge, Milan]
    }

Query3: Find all traders from Cambridge and sort them by name

@Test
    public void query3() {
        List<Trader> traders = transactions().stream()
                .map(Transaction::getTrader)
                .filter(trader -> "Cambridge".equals(trader.getCity()))
                .distinct()
                .sorted(Comparator.comparing(Trader::getName))
                .collect(Collectors.toList());
        System.out.println(traders);
        // [Trader{name='Alan', city='Cambridge'}, Trader{name='Brian', city='Cambridge'}, Trader{name='Raoul', city='Cambridge'}]
    }

Query4: Return a string of all traders' names sorted alphabetically (按照字母顺序排列的).

    @Test
    public void query4() {
        String traderStr = transactions().stream()
                .map(Transaction::getTrader)
                .map(Trader::getName)
                .distinct()
                .sorted()
                .reduce("", (name1, name2) -> name1 + name2);
        System.out.println(traderStr);
        // AlanBrianMarioRaoul
    }

Query5: Are there any trader based in Milan?

    @Test
    public void query5() {
        boolean milanBased = transactions().stream()
                .anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
        System.out.println(milanBased);
        // true
    }

Query6: Update all transactions so that the traders from Milan are set to Cambridge

    public void query6() {
        List<Transaction> transactions = transactions();
        transactions.stream()
                .map(Transaction::getTrader)
                .filter(trader -> trader.getCity().equals("Milan"))
                .forEach(trader -> trader.setCity("Cambridge"));
        System.out.println(transactions);
        // [Transaction{trader=Trader{name='Brian', city='Cambridge'}, year=2011, value=300},
        // Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2012, value=1000},
        // Transaction{trader=Trader{name='Raoul', city='Cambridge'}, year=2011, value=400},
        // Transaction{trader=Trader{name='Mario', city='Cambridge'}, year=2012, value=710},
        // Transaction{trader=Trader{name='Mario', city='Cambridge'}, year=2012, value=700},
        // Transaction{trader=Trader{name='Alan', city='Cambridge'}, year=2012, value=950}]
    }

Query7: What's the highest value in all the transactions?

    @Test
    public void query7() {
        int highestValue1 = transactions().stream()
                .map(Transaction::getValue)
                .reduce(0, Integer::max);

        int highestValue2 = transactions().stream()
                .map(Transaction::getValue)
                .max(Comparator.comparingInt(a -> a))
                .get();
        System.out.println(highestValue1);
        System.out.println(highestValue2);
//        1000
//        1000
    }

THE END.

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yitian_z/article/details/104680964

智能推荐

什么是内部类?成员内部类、静态内部类、局部内部类和匿名内部类的区别及作用?_成员内部类和局部内部类的区别-程序员宅基地

文章浏览阅读3.4k次,点赞8次,收藏42次。一、什么是内部类?or 内部类的概念内部类是定义在另一个类中的类;下面类TestB是类TestA的内部类。即内部类对象引用了实例化该内部对象的外围类对象。public class TestA{ class TestB {}}二、 为什么需要内部类?or 内部类有什么作用?1、 内部类方法可以访问该类定义所在的作用域中的数据,包括私有数据。2、内部类可以对同一个包中的其他类隐藏起来。3、 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。三、 内部类的分类成员内部_成员内部类和局部内部类的区别

分布式系统_分布式系统运维工具-程序员宅基地

文章浏览阅读118次。分布式系统要求拆分分布式思想的实质搭配要求分布式系统要求按照某些特定的规则将项目进行拆分。如果将一个项目的所有模板功能都写到一起,当某个模块出现问题时将直接导致整个服务器出现问题。拆分按照业务拆分为不同的服务器,有效的降低系统架构的耦合性在业务拆分的基础上可按照代码层级进行拆分(view、controller、service、pojo)分布式思想的实质分布式思想的实质是为了系统的..._分布式系统运维工具

用Exce分析l数据极简入门_exce l趋势分析数据量-程序员宅基地

文章浏览阅读174次。1.数据源准备2.数据处理step1:数据表处理应用函数:①VLOOKUP函数; ② CONCATENATE函数终表:step2:数据透视表统计分析(1) 透视表汇总不同渠道用户数, 金额(2)透视表汇总不同日期购买用户数,金额(3)透视表汇总不同用户购买订单数,金额step3:讲第二步结果可视化, 比如, 柱形图(1)不同渠道用户数, 金额(2)不同日期..._exce l趋势分析数据量

宁盾堡垒机双因素认证方案_horizon宁盾双因素配置-程序员宅基地

文章浏览阅读3.3k次。堡垒机可以为企业实现服务器、网络设备、数据库、安全设备等的集中管控和安全可靠运行,帮助IT运维人员提高工作效率。通俗来说,就是用来控制哪些人可以登录哪些资产(事先防范和事中控制),以及录像记录登录资产后做了什么事情(事后溯源)。由于堡垒机内部保存着企业所有的设备资产和权限关系,是企业内部信息安全的重要一环。但目前出现的以下问题产生了很大安全隐患:密码设置过于简单,容易被暴力破解;为方便记忆,设置统一的密码,一旦单点被破,极易引发全面危机。在单一的静态密码验证机制下,登录密码是堡垒机安全的唯一_horizon宁盾双因素配置

谷歌浏览器安装(Win、Linux、离线安装)_chrome linux debian离线安装依赖-程序员宅基地

文章浏览阅读7.7k次,点赞4次,收藏16次。Chrome作为一款挺不错的浏览器,其有着诸多的优良特性,并且支持跨平台。其支持(Windows、Linux、Mac OS X、BSD、Android),在绝大多数情况下,其的安装都很简单,但有时会由于网络原因,无法安装,所以在这里总结下Chrome的安装。Windows下的安装:在线安装:离线安装:Linux下的安装:在线安装:离线安装:..._chrome linux debian离线安装依赖

烤仔TVの尚书房 | 逃离北上广?不如押宝越南“北上广”-程序员宅基地

文章浏览阅读153次。中国发达城市榜单每天都在刷新,但无非是北上广轮流坐庄。北京拥有最顶尖的文化资源,上海是“摩登”的国际化大都市,广州是活力四射的千年商都。GDP和发展潜力是衡量城市的数字指...

随便推点

java spark的使用和配置_使用java调用spark注册进去的程序-程序员宅基地

文章浏览阅读3.3k次。前言spark在java使用比较少,多是scala的用法,我这里介绍一下我在项目中使用的代码配置详细算法的使用请点击我主页列表查看版本jar版本说明spark3.0.1scala2.12这个版本注意和spark版本对应,只是为了引jar包springboot版本2.3.2.RELEASEmaven<!-- spark --> <dependency> <gro_使用java调用spark注册进去的程序

汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用_uds协议栈 源代码-程序员宅基地

文章浏览阅读4.8k次。汽车零部件开发工具巨头V公司全套bootloader中UDS协议栈源代码,自己完成底层外设驱动开发后,集成即可使用,代码精简高效,大厂出品有量产保证。:139800617636213023darcy169_uds协议栈 源代码

AUTOSAR基础篇之OS(下)_autosar 定义了 5 种多核支持类型-程序员宅基地

文章浏览阅读4.6k次,点赞20次,收藏148次。AUTOSAR基础篇之OS(下)前言首先,请问大家几个小小的问题,你清楚:你知道多核OS在什么场景下使用吗?多核系统OS又是如何协同启动或者关闭的呢?AUTOSAR OS存在哪些功能安全等方面的要求呢?多核OS之间的启动关闭与单核相比又存在哪些异同呢?。。。。。。今天,我们来一起探索并回答这些问题。为了便于大家理解,以下是本文的主题大纲:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JCXrdI0k-1636287756923)(https://gite_autosar 定义了 5 种多核支持类型

VS报错无法打开自己写的头文件_vs2013打不开自己定义的头文件-程序员宅基地

文章浏览阅读2.2k次,点赞6次,收藏14次。原因:自己写的头文件没有被加入到方案的包含目录中去,无法被检索到,也就无法打开。将自己写的头文件都放入header files。然后在VS界面上,右键方案名,点击属性。将自己头文件夹的目录添加进去。_vs2013打不开自己定义的头文件

【Redis】Redis基础命令集详解_redis命令-程序员宅基地

文章浏览阅读3.3w次,点赞80次,收藏342次。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。此时,可以将系统中所有用户的 Session 数据全部保存到 Redis 中,用户在提交新的请求后,系统先从Redis 中查找相应的Session 数据,如果存在,则再进行相关操作,否则跳转到登录页面。当数据量很大时,count 的数量的指定可能会不起作用,Redis 会自动调整每次的遍历数目。_redis命令

URP渲染管线简介-程序员宅基地

文章浏览阅读449次,点赞3次,收藏3次。URP的设计目标是在保持高性能的同时,提供更多的渲染功能和自定义选项。与普通项目相比,会多出Presets文件夹,里面包含着一些设置,包括本色,声音,法线,贴图等设置。全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,主光源和附加光源在一次Pass中可以一起着色。URP:全局只有主光源和附加光源,主光源只支持平行光,附加光源数量有限制,一次Pass可以计算多个光源。可编程渲染管线:渲染策略是可以供程序员定制的,可以定制的有:光照计算和光源,深度测试,摄像机光照烘焙,后期处理策略等等。_urp渲染管线

推荐文章

热门文章

相关标签