登录 |  注册
首页 >  编程语言 >  java8特性 >  Stream的使用-方法详解(下)

Stream的使用-方法详解(下)

9.统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

计数:count。返回流中元素个数,结果为 long 类型。

平均值:averagingInt、averagingLong、averagingDouble

最值:maxBy、minBy

求和:summingInt、summingLong、summingDouble

统计以上所有:summarizingInt、summarizingLong、summarizingDouble

案例:统计员工人数、平均工资、工资总额、最高工资。

public class StreamTest {
	public static void main(String[] args) {
		List<Person> personList = new ArrayList<Person>();
		personList.add(new Person("Tom", 8900, 23, "male", "New York"));
		personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
		personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
		// 求总数
		Long count = personList.stream().collect(Collectors.counting());
	        // 求平均工资
		Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
		// 求最高工资
		Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
		// 求工资之和
		Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
		// 一次性统计所有信息
		DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));
		System.out.println("员工总数:" + count);
		System.out.println("员工平均工资:" + average);
		System.out.println("员工工资总和:" + sum);
		System.out.println("员工工资所有统计:" + collect);
	}
}

运行结果:

员工总数:3
员工平均工资:7900.0
员工工资总和:23700
员工工资所有统计:DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}

10.groupingBy 分组

groupingBy 用于将数据分组,最终返回一个 Map 类型

Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));

例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组。

Map<String,List<Person>> result = list.stream()
                                    .collect(Collectors.groupingby((person)->{
        if(person.getAge()>60)
            return "老年人";
        else if(person.getAge()>40)
            return "中年人";
        else
            return "青年人";
                                    }));

另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List 类型)

10.1多级分组

groupingBy 可以接受一个第二参数实现多级分组:

Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupBy(...)));

其中返回的 Map 键为 Integer 类型,值为 Map

10.2按组收集数据

Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));

该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map

groupingBy(Person::getAge)

其实等同于:

groupingBy(Person::getAge, toList())

11.partitioningBy 分区

分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean。

//根据年龄是否小于等于20来分区
Map<Boolean, List<Person>> map = list.stream()
     .collect(partitioningBy(p -> p.getAge() <= 20));
//打印输出
{
    false=[Person{name='mike', age=25}, Person{name='tom', age=30}], 
    true=[Person{name='jack', age=20}]
}

同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。

11.1分组与分区对比

分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。

分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

案例:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组

public class StreamTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<Person>();
personList.add(new Person("Tom", 8900, "male", "New York"));
personList.add(new Person("Jack", 7000, "male", "Washington"));
personList.add(new Person("Lily", 7800, "female", "Washington"));
personList.add(new Person("Anni", 8200, "female", "New York"));
personList.add(new Person("Owen", 9500, "male", "New York"));
personList.add(new Person("Alisa", 7900, "female", "New York"));
// 将员工按薪资是否高于8000分组
        Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
        // 将员工按性别分组
        Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex));
        // 将员工先按性别分组,再按地区分组
        Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
        System.out.println("员工按薪资是否大于8000分组情况:" + part);
        System.out.println("员工按性别分组情况:" + group);
        System.out.println("员工按性别、地区:" + group2);
}
}

输出结果:

员工按薪资是否大于8000分组情况:{false=[mutest.Person@2d98a335, mutest.Person@16b98e56, mutest.Person@7ef20235], true=[mutest.Person@27d6c5e0, mutest.Person@4f3f5b24, mutest.Person@15aeb7ab]}
员工按性别分组情况:{female=[mutest.Person@16b98e56, mutest.Person@4f3f5b24, mutest.Person@7ef20235], male=[mutest.Person@27d6c5e0, mutest.Person@2d98a335, mutest.Person@15aeb7ab]}
员工按性别、地区:{female={New York=[mutest.Person@4f3f5b24, mutest.Person@7ef20235], Washington=[mutest.Person@16b98e56]}, male={New York=[mutest.Person@27d6c5e0, mutest.Person@15aeb7ab], Washington=[mutest.Person@2d98a335]}}

12.接合(joining)

joining 连接字符串,也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder。

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

String s = list.stream().map(Person::getName).collect(joining());
//结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));
//结果:jack,mike,tom
joining 还有一个比较特别的重载方法:
String s = list.stream().map(Person::getName).collect(joining(" and ", "Today ", " play games."));
//结果:Today jack and mike and tom play games.

即 Today 放开头,play games. 放结尾,and 在中间连接各个字符串。

案例:

public class StreamTest {
                public static void main(String[] args) {
                    List<Person> personList = new ArrayList<Person>();
                    personList.add(new Person("Tom", 8900, 23, "male", "New York"));
                    personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
                    personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
                    String names = personList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
                    System.out.println("所有员工的姓名:" + names);
                    List<String> list = Arrays.asList("A", "B", "C");
                    String string = list.stream().collect(Collectors.joining("-"));
                    System.out.println("拼接后的字符串:" + string);
                }
            }

运行结果:

所有员工的姓名:Tom,Jack,Lily
拼接后的字符串:A-B-C

13.归约(reducing)

Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。

public class StreamTest {
                public static void main(String[] args) {
                    List<Person> personList = new ArrayList<Person>();
                    personList.add(new Person("Tom", 8900, 23, "male", "New York"));
                    personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
                    personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
                    // 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
                    Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
                    System.out.println("员工扣税薪资总和:" + sum);
                    // stream的reduce
                    Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
                    System.out.println("员工薪资总和:" + sum2.get());
                }
            }

运行结果:

员工扣税薪资总和:8700
员工薪资总和:23700

14.排序(sorted)

sorted,中间操作。有两种排序:【sorted() / sorted((T, T) -> int)】

sorted():自然排序,流中元素需实现Comparable接口

sorted(Comparator com):Comparator排序器自定义排序

注意:如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream。反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口

首先我们先创建一个 Person 泛型的 List

List<Person> list = new ArrayList<>();
list.add(new Person("jack", 20));
list.add(new Person("mike", 25));
list.add(new Person("tom", 30));

Person 类包含年龄和姓名两个成员变量

private String name;
private int age;

比如:根据年龄大小来比较:

list = list.stream()
           .sorted((p1, p2) -> p1.getAge() - p2.getAge())
           .collect(Collectors.toList());
// private int age;

当然这个可以简化为

list = list.stream()
           .sorted(Comparator.comparingInt(Person::getAge))
           .collect(Collectors.toList());

14.1 数字排序

/**
     * 数字排序
     */
    public static void testIntegerSort() {
        List<Integer> list = Arrays.asList(4, 2, 5, 3, 1);
        System.out.println(list);//执行结果:[4, 2, 5, 3, 1]
        //升序
        list.sort((a, b) -> a.compareTo(b.intValue()));
        System.out.println(list);//执行结果:[1, 2, 3, 4, 5]
        //降序
        list.sort((a, b) -> b.compareTo(a.intValue()));
        System.out.println(list);//执行结果:[5, 4, 3, 2, 1]
    }

14.2 字符串排序

/**
     * 字符串排序
     */
    public static void testStringSort() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("cc");
        list.add("bb");
        list.add("ee");
        list.add("dd");
        System.out.println(list);//执行结果:aa, cc, bb, ee, dd
        //升序
        list.sort((a, b) -> a.compareTo(b.toString()));
        System.out.println(list);//执行结果:[aa, bb, cc, dd, ee]
        //降序
        list.sort((a, b) -> b.compareTo(a.toString()));
        System.out.println(list);//执行结果:[ee, dd, cc, bb, aa]
    }

14.3字符串排序

/**
     * 字符串排序
     */
    public static void testStringSort() {
        List<String> list = new ArrayList<>();
        list.add("aa");
        list.add("cc");
        list.add("bb");
        list.add("ee");
        list.add("dd");
        System.out.println(list);//执行结果:aa, cc, bb, ee, dd
        //升序
        list.sort((a, b) -> a.compareTo(b.toString()));
        System.out.println(list);//执行结果:[aa, bb, cc, dd, ee]
        //降序
        list.sort((a, b) -> b.compareTo(a.toString()));
        System.out.println(list);//执行结果:[ee, dd, cc, bb, aa]
    }

14.4 对象字段排序

class Person {
        private String name;
        private int age;
        public Person() {
        }
        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
/**
     * 对象串排序
     */
    public void testObjectSort() {
        List<Person> list = new ArrayList<>();
        list.add(new Person("三炮", 48));
        list.add(new Person("老王", 35));
        list.add(new Person("小明", 8));
        list.add(new Person("叫兽", 70));
        System.out.println(list); //执行结果:[Person{name='三炮', age=48}, Person{name='老王', age=35}, Person{name='小明', age=8}, Person{name='叫兽', age=70}]
        //按年龄升序
        list.sort((a, b) -> Integer.compare(a.age, b.getAge()));
        System.out.println(list);//执行结果:[Person{name='小明', age=8}, Person{name='老王', age=35}, Person{name='三炮', age=48}, Person{name='叫兽', age=70}]
        //按年龄降序
        list.sort((a, b) -> Integer.compare(b.age, a.getAge()));
        System.out.println(list);//执行结果:[Person{name='叫兽', age=70}, Person{name='三炮', age=48}, Person{name='老王', age=35}, Person{name='小明', age=8}]
        //如果按姓名排序,其实就是按字符串排序一样
    }

15.去重:distinct()

去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的。如例子中的 Person 类,需要先定义好 equals 方法,不然类似[Person{name='jack', age=20}, Person{name='jack', age=20}] 这样的情况是不会处理的。

16.限制:limit(long n)

返回前 n 个元素

list = list.stream()
            .limit(2)
            .collect(Collectors.toList());
//打印输出 [Person{name='jack', age=20}, Person{name='mike', age=25}]

17.去除(跳过)skip(long n)

去除前 n 个元素

list = list.stream()
            .skip(2)
            .collect(Collectors.toList());
//打印输出 [Person{name='tom', age=30}]

tips:

skip(m)用在 limit(n) 前面时,先去除前 m 个元素再返回剩余元素的前 n 个元素。

limit(n) 用在 skip(m) 前面时,先返回前 n 个元素再在剩余的 n 个元素中去除 m 个元素。

list = list.stream()
            .limit(2)
            .skip(1)
            .collect(Collectors.toList());
//打印输出 [Person{name='mike', age=25}]

案例:

public class StreamTest {
public static void main(String[] args) {
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// concat:合并两个流 distinct:去重
List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
        
// limit:限制从流中获得前n个数据
List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
        
// skip:跳过前n个数据
List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());
System.out.println("流合并:" + newList);
System.out.println("limit:" + collect);
System.out.println("skip:" + collect2);
}
}

运行结果:

流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7, 9, 11]

18.无序unordered()

还有这个比较不起眼的方法,返回一个等效的无序流,当然如果流本身就是无序的话,那可能就会直接返回其本身。

上一篇: Stream的使用-方法详解(上)
推荐文章
  • ASP.NET教程ASP.NET又称为ASP+,基于.NETFramework的Web开发平台,是微软公司推出的新一代脚本语言。ASP.NET是一个使用HTML、CSS、JavaScript和服务器脚本创建网页和网站的开发框架。ASP.NET支持三种不一样的开发模式:WebPages(Web页面)、
  • C# 判断判断结构要求程序员指定一个或多个要评估或测试的条件,以及条件为真时要执行的语句(必需的)和条件为假时要执行的语句(可选的)。下面是大多数编程语言中典型的判断结构的通常形式:判断语句C#提供了以下类型的判断语句。点击链接查看每个语句的细节。语句描述if语句一个 if语句 由一个布尔表达式后跟
  • C#循环有的时候,可能需要多次执行同一块代码。通常情况下,语句是顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。编程语言提供了允许更为复杂的执行路径的多种控制结构。循环语句允许我们多次执行一个语句或语句组,下面是大多数编程语言中循环语句的通常形式:循环类型C#提供了以下几种循环类型
  • C#数组(Array)数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,一般认为数组是一个同一类型变量的集合。声明数组变量并不是声明number0、number1、...、number99一个个单独的变量,而是声明一个就像numbers这样的变量,然后使用numbers[0]
  • ASP.NET是一个由微软公司开发的用于构建Web应用程序的框架,它是.NETFramework的一部分。它提供了一种模型-视图-控制器(MVC)架构、Web表单以及最新的ASP.NETCore中的RazorPages等多种开发模式,可以用来创建动态网页和Web服务。以下是一些基础的ASP.NET编
  • 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出java.lang.Arithmeti
学习大纲