Stream流

192 阅读18分钟

什么是Stream?

stream它并不是一个容器,它只是对容器的功能进行了增强,添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作。并且有串行、并行两种执行模式,并行模式充分的利用了多核处理器的优势,使用fork/join框架进行了任务拆分,同时提高了执行速度。简而言之,Stream就是提供了一种高效且易于使用的处理数据的方式。

图解:

clipboard.png

特性:

  1. stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。

  2. stream不会改变数据源,通常情况下会产生一个新的集合或一个值。

  3. stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

Stream的创建

1、通过 java.util.Collection.stream() 方法用集合创建流

List<String> list = Arrays.asList("a""b""c"); 
// 创建一个顺序流 
Stream<String> stream = list.stream(); 
// 创建一个并行流 
Stream<String> parallelStream = list.parallelStream();

2、使用java.util.Arrays.stream(T[] array)方法用数组创建流

int[] array={1,3,5,6,8}; 
IntStream stream = Arrays.stream(array);

3、使用Stream的静态方法:of()、iterate()、generate()

Stream<Integer> stream = Stream.of(123456); 
//iterate,generate这两个操作可以创建所谓的无限流,这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。
//使用无限流一定要配合limit截断,不然会无限制创建下去。
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

//输出结果:
//0 3 6 9
//0.6796156909271994
//0.1914314208854283
//0.8116932592396652

顺序流和并行流的区别

stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处: 640.webp 如果流中的数据量足够大,并行流可以加快处速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流,例如:

Optional findFirst = list.stream().parallel().filter(x->x>6).findFirst();

Stream中间操作

anyMatch/allMatch/noneMatch

判断集合中的元素是否满足这个条件,这个方法是一个短路的中间操作,既然是短路操作,那就不需要执行完流中全>部元素,只要达到要求就中断操作。

public static void main(String [] args) {
    List<Integer> list = Arrays.asList(1, 2, 1, 1, 1);
    boolean anyMatch = list.stream().anyMatch(f -> f == (1));//集合当中的元素有一个满足该条件就返回true
    boolean allMatch = list.stream().allMatch(f -> f == (1));//集合当中的元素全部满足该条件才返回true
    boolean noneMatch = list.stream().noneMatch(f -> f == (1));//集合当中的元素全部不满足该条件才会返回true
    
    System.out.println(anyMatch);  // true
    System.out.println(allMatch);  // false
    System.out.println(noneMatch); // false
}

foreach

Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的

Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
Arrays.stream(array).forEach(System.out::println);

findFirst

该方法会取出流当中第一个元素并且返回Optional类,可作为兜底数据或者抛出异常。

Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};

//筛选出数值大于8的并且取出第一个元素,如果没有找见则会返回0
Integer integer1 = Arrays.stream(array).filter(l -> l > 8).findFirst().orElse(0);
System.out.println(integer1);// 结果: 0

//筛选出数值大于5的并且取出第一个元素,如果没有找见则会返回0
Integer integer2 = Arrays.stream(array).filter(l -> l > 5).findFirst().orElse(0);
System.out.println(integer2);// 结果: 6

findAny

就是从流当中随机取出任意元素

public static void main(String[] args) 
    Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
    for (int i = 0; i < 10; i++) {
        testFindFirst(array);
        testFindAny(array);
    }
}

//测试FindFirst
public void testFindFirst(Integer... array) {
    Integer integer1 = Arrays.stream(array).findFirst().get();
    System.out.println(integer1);
}

//测试FindAny
public void testFindAny(Integer... array) {
    Integer integer1 = Arrays.stream(array).findAny().get();
    System.out.println(integer1);
}

执行结果: image.png 根据结果可以看出这两个方法执行的结果都是一样的,findFirst方法我们可以理解,就是找第一个元素。每次输出的内容一样没有问题。但是findAny不是说是找任意一个元素吗,怎么每次输出的也是一样啊。 这是因为对array这个集合做流化处理使用的是stream,这是串行流。如果我们的array是有序的,那findAny的任意一个都是第一个了。既然有串行流,那试试并行流吧。

public static void main(String[] args) {
    Integer[] array = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8};
    for (int i = 0; i < 20; i++) {
        testFindFirst(array);
        testFindAny(array);
    }

}
//测试FindFirst
public static void testFindFirst(Integer... array) {
    Integer integer1 = Arrays.stream(array).findFirst().get();
    System.out.println("测试FindFirst的执行结果为:" + integer1);
}

//测试FindAny
public static void testFindAny(Integer... array) {
    //更改为并行流
    Integer integer1 = Arrays.asList(array).parallelStream().findAny().get();
    System.out.println("测试FindAny的执行结果为:" + integer1);
}

执行结果:(执行结果需要多执行几次,在这里加大了循环次数,并且需要多运行几次,否则由于多线程的原因可能导致数据显示没变化) image.png 这次的执行结果输出的不再都是第一个元素了,而是任意的一个元素了。

筛选(filter)

筛选是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。 640.webp

 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"));
            personList.add(new Person("Anni", 8200, 24, "female", "New York"));
            personList.add(new Person("Owen", 9500, 25, "male", "New York"));
            personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

            List<String> fiterList = personList.stream()
            .filter(x -> x.getSalary() > 8000)
            .map(Person::getName)
            .collect(Collectors.toList());
            System.out.print("高于8000的员工姓名:" + fiterList);
        }
    }
//运行结果:
//高于8000的员工姓名:[Tom, Anni, Owen]

聚合(max/min/count)

方便了我们对集合、数组的数据统计工作。

640.webp

//案例一:获取String集合中最长的元素。
    public class StreamTest {
        public static void main(String[] args) {
            List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");

            Optional<String> max = list.stream().max(Comparator.comparing(String::length));
            System.out.println("最长的字符串:" + max.get());
        }
    }
//输出结果:最长的字符串:weoujgsd

//案例二:获取集合的最大值
    public class StreamTest {
        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);

            // 自然排序
            Optional<Integer> max = list.stream().max(Integer::compareTo);
            // 自定义排序
            Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1.compareTo(o2);
                }
            });
            System.out.println("自然排序的最大值:" + max.get());
            System.out.println("自定义排序的最大值:" + max2.get());
        }
    }
//输出结果:
    自然排序的最大值:11
    自定义排序的最大值:11
    
    
//案例三:获取集合对象当中员工的工资的最大值
    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"));
            personList.add(new Person("Anni", 8200, 24, "female", "New York"));
            personList.add(new Person("Owen", 9500, 25, "male", "New York"));
            personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

            Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
            System.out.println("员工工资最大值:" + max.get().getSalary());
        }
    }
//输出结果:员工工资最大值:9500

//案例四:获取集合当中元素个数
    public class StreamTest {
        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);
            long count = list.stream().filter(x -> x > 6).count();
            System.out.println("list中大于6的元素个数:" + count);
        }
    }
//输出结果:list中大于6的元素个数:4

(map/flatMap)

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap:

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流,可合并两个相同类型的集合。

640.webp

public class StreamTest {
    public static void main(String[] args) {
        String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
        List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());

        List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
        List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());

        System.out.println("每个元素大写:" + strList);
        System.out.println("每个元素+3:" + intListNew);
    }
}
//输出结果:
//每个元素大写:[ABCD, BCDD, DEFDE, FTR]
//每个元素+3:[4, 6, 8, 10, 12, 14]


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"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        // 不改变原来员工集合的方式
        List<Person> personListNew = personList.stream().map(person -> {
            Person personNew = new Person(person.getName(), 0, 0, null, null);
            personNew.setSalary(person.getSalary() + 10000);
            return personNew;
        }).collect(Collectors.toList());
        System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
        System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());

        // 改变原来员工集合的方式
        List<Person> personListNew2 = personList.stream().map(person -> {
            person.setSalary(person.getSalary() + 10000);
            return person;
        }).collect(Collectors.toList());
        System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
        System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
    }
}
//输出结果:
//一次改动前:Tom–>8900
//一次改动后:Tom–>18900
//二次改动前:Tom–>18900
//二次改动后:Tom–>18900

clipboard.png

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
        List<String> listNew = list.stream().flatMap(s -> {
            // 将每个元素转换成一个stream
            String[] split = s.split(",");
            Stream<String> s2 = Arrays.stream(split);
            return s2;
        }).collect(Collectors.toList());

        System.out.println("处理前的集合:" + list);
        System.out.println("处理后的集合:" + listNew);
    }
}
//输出结果:
//处理前的集合:[m-k-l-a, 1-3-5]
//处理后的集合:[m, k, l, a, 1, 3, 5]

peek

peek方法主要用于Stream流中的调试

peek和map的异同之处

  • 两个函数都是中间操作,都非常的‘懒’,没有对Stream的终止操作,两个函数都不会工作。
  • peek函数的存在仅仅是为了debug,而map是Stream的一个核心函数,两个函数的地位不同。
  • 两个函数的返回值都是一个新的Stream,但是两个函数的参数(peek是Consumer,map是Function)起作用的时机不同。map的Function在生成新的Stream之前被执行,新Stream中的元素是上游Stream中元素经Function作用后的值。peek函数的Consumer工作在生成Stream之后,下一节详细讲解两个函数执行时机。

样例

以下面一段函数(来自peek函数的官方注释)为例解释peek和map两个函数工作机制的不同:


List<String> list = Stream.of("one", "two", "three", "four")
        .filter(e -> e.length() > 3)
        .peek(e -> System.out.println("第一次Peek得到的值: " + e))
        .map(String::toUpperCase)
        .peek(e -> System.out.println("第二次Peek得到的值: " + e))
        .collect(Collectors.toList());
System.out.println("List集合:" + list);


//输出如下:
//第一次Peek得到的值: three
//第二次Peek得到的值: THREE
//第一次Peek得到的值: four
//第二次Peek得到的值: FOUR
//List集合:[THREE, FOUR]

工作示意图如下

image.png 工作流程概述:

1、初始Stream包含四个字符串:one,two,three和four

2、Stream遇到的第一个中间操作是filter,filter是只保留长度大于3的字符串,经过过滤后filter返回是一个仅包含两个字符串three和four的Stream1,谓词工作在Stream1生成之前。

3、Stream遇到的第二个中间操作是peek,peek的Consumer是打印Stream中的字符串,peek直接生成一个和上游Stream1包含相同元素的Stream2,peek函数的Consumer工作在生成Stream2之后。

4、Stream遇到的第三个中间操作是map,map的Function将字符串转换为全大写,Function作用于上游Stream2的每一个元素,并生成新的Stream3。

5、Stream遇到的第四个中间操作是peek,peek的Consumer依然只是打印Stream4中的字符串,Consumer依然工作在Stream4生成之后。

peek和map修改Stream的元素

map函数对Stream中元素执行的是映射操作,会以新的元素(map的结果)填充新的Stream,严格的讲map不是修改原来的元素。peek只能消费Stream中的元素,是否可以更该Stream中的元素,取决于Stream中的元素是否是不可变对象。如果是不可变对象,则不可修改Stream中的元素;如果是可变对象,则可以修改对象的值,但是无法修改对象的引用。

  • 不可变对象场景:

适当的修改上面的样例:


List<String> list = Stream.of("one", "two", "three", "four")
        .filter(e -> e.length() > 3)
        .peek(s -> {
            s = s + "-" + s;
            System.out.println("第一次peek得到的值:" + s);
        })
        .map(String::toUpperCase)
        .peek(e -> System.out.println("第二次peek得到的值:" + e))
        .collect(Collectors.toList());
System.out.println("该集合的值"+list);

//输出结果:
//第一次peek得到的值:three-three
//第二次peek得到的值:THREE
//第一次peek得到的值:four-four
//第二次peek得到的值:FOUR
//该集合的值[THREE, FOUR]

由输出结果可知peek并没有修改Stream的元素,list的值依然是:[THREE, FOUR]

  • 可变对象

定义一个简单的Java对象Company

class Company {
    private String name;
    private int age;
    // 省略构造函数,getter/setter和toString方法
}


public static void main(String[] args) {

    Company apple = new Company("apple", 44);
    Company huawei = new Company("huawei", 33);
    Company qualcomm = new Company("Qualcomm ", 35);
    List<Company> list = Stream.of(apple, huawei, qualcomm)
            .filter(company -> company.getAge() < 35)
            .peek(company -> company.setAge(company.getAge() - 10))
            .map(company -> new Company(company.getName().toUpperCase(),company.getAge()))
            .peek(e -> System.out.println("Peek得到的值为: " + e))
            .collect(Collectors.toList());
    System.out.println("list集合的最终结果:"+list);
}

//输出的结果如下:
//Peek得到的值为: Person{name='HUAWEI', age=23}
//list集合的最终结果:[Person{name='HUAWEI', age=23}]

排序(sorted)

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
public class StreamTest {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();

        personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
        personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
        personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
        personList.add(new Person("Lily", 8800, 26, "male", "New York"));
        personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

        // 按工资升序排序(自然排序)
        List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
                .collect(Collectors.toList());

        // 按工资倒序排序
        List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
                .map(Person::getName).collect(Collectors.toList());

        // 先按工资再按年龄升序排序
        List<String> newList3 = personList.stream()
                .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
                .collect(Collectors.toList());

        // 先按工资再按年龄自定义排序(降序)
        List<String> newList4 = personList.stream().sorted((p1, p2) -> {
            if (p1.getSalary() == p2.getSalary()) {
                return p2.getAge() - p1.getAge();
            } else {
                return p2.getSalary() - p1.getSalary();
            }
        }).map(Person::getName).collect(Collectors.toList());

        System.out.println("按工资升序排序:" + newList);
        System.out.println("按工资降序排序:" + newList2);
        System.out.println("先按工资再按年龄升序排序:" + newList3);
        System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
    }
}
运行结果:
按工资升序排序:[Lily, Tom, Sherry, Jack, Alisa]
按工资降序排序:[Sherry, Jack, Alisa, Tom, Lily]
先按工资再按年龄升序排序:[Lily, Tom, Sherry, Jack, Alisa]
先按工资再按年龄自定义降序排序:[Alisa, Jack, Sherry, Tom, Lily]

提取/组合

流也可以进行合并(concat)、去重(distinct)、限制(limit)、跳过(skip)等操作。 640.webp 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。 640.webp 截断流,使其元素不超过给定数量。如果元素的个数小于maxSize,那就获取所有元素。 clipboard.png

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]

Stream流终止操作

1.收集(collect):collect主要依赖java.util.stream.Collectors类内置的静态方法。

  • 返回List集合:toList
  • 返回Set集合:toSet
  • 返回Map集合:toMap
  • 返回自定义集合:toCollection
  • 返回线程安全,高效,并发的集合:toConcurrentMap
  • 返回不可修改的List集合:toUnmodifiableList //用于创建只读List集合。任何试图对此不可修改List集合进行更改的尝试都将导致UnsupportedOperationException。
  • 返回不可修改的Set集合:toUnmodifiableSet //用于创建只读Set集合。任何试图对此不可修改Set集合进行更改的尝试都将导致UnsupportedOperationException。它会删除重复元素。
public class StreamTest {
        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
            List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
            Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());

            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"));
            personList.add(new Person("Anni", 8200, 24, "female", "New York"));

            Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
                    .collect(Collectors.toMap(Person::getName, p -> p));
            System.out.println("toList:" + listNew);
            System.out.println("toSet:" + set);
            System.out.println("toMap:" + map);
        }
    }
    //运行结果:
    //toList:[6, 4, 6, 6, 20]
    //toSet:[4, 20, 6]
    //toMap:{Tom=mutest.Person@5fd0d5ae, Anni=mutest.Person@2d98a335}



    //案例二:转换为Map集合,Map集合的Key不可以重复,Value可以重复
    List<User> users = new ArrayList<>();
    users.add(new User(1,"小明","xiaoming",18,"西安"));
    users.add(new User(2,"小白","xiaobai",23,"北京"));
    users.add(new User(3,"小红","xiaohong",54,"上海"));
    users.add(new User(3,"小红","xiaohong",54,"上海"));
    users.add(new User(4,"小红","xiaohong11",54,"上海"));
    users.add(new User(5,"小红","xiaohong",54,"上海"));

    //使用toMap()函数之后,返回的就是一个Map,会需要key和value。
//toMap()的参数
    //第一个参数就是用来生成key值的,
    //第二个参数就是用来生成value值的。
    //第三个参数用在key值冲突的情况下:如果新元素产生的key在Map中已经出现过了,第三个参数就会定义解决的办法。
    Map<Integer, User> map = users.stream().collect(Collectors.toMap(User::getId, u -> u,(k,v)->k));
map.forEach((k,v)->{
        System.out.println("K:"+k);
        System.out.println("V:"+v);
    });

    //代码解释:
            //在users.stream().collect(Collectors.toMap(User::getId, u -> u,(k,v)->k));中,
    //User::getId   以User的id作为key
    //u -> u        User实体作为value
            //(k,v)->k      如果出现key值冲突,永远取第一个

    输出结果:
    K:1
    V:User(id=1, username=小明, password=xiaoming, age=18, address=西安)
    K:2
    V:User(id=2, username=小白, password=xiaobai, age=23, address=北京)
    K:3
    V:User(id=3, username=小红, password=xiaohong, age=54, address=上海)
    K:4
    V:User(id=4, username=小红, password=xiaohong11, age=54, address=上海)
    K:5
    V:User(id=5, username=小红, password=xiaohong, age=54, address=上海)

    //当你不想覆盖掉重复项数据,这个时候就可以用 Collectors.groupingBy这个方法了,此返回值为Map<Integer, List>,也就是
//变成了id依旧是key,而value则变成User对象的集合了。
    Map<Integer, List<User>> map1 = users.stream().collect(Collectors.groupingBy(User::getId));
        map1.forEach((k,v)->{
        System.out.println("K:"+k);
        System.out.println("V:"+v);
    });
    输出结果:
    K:1
    V:[User(id=1, username=小明, password=xiaoming, age=18, address=西安)]
    K:2
    V:[User(id=2, username=小白, password=xiaobai, age=23, address=北京)]
    K:3
    V:[User(id=3, username=小红, password=xiaohong, age=54, address=上海), User(id=3, username=小红, password=xiaohong, age=54, address=上海)]
    K:4
    V:[User(id=4, username=小红, password=xiaohong11, age=54, address=上海)]
    K:5
    V:[User(id=5, username=小红, password=xiaohong, age=54, address=上海)]


    //案例三:转换为自定义集合
    List<User> users = new ArrayList<>();
    users.add(new User(1, "小明", "xiaoming", 18, "西安"));
    users.add(new User(2, "小白", "xiaobai", 23, "北京"));
    users.add(new User(3, "小红", "xiaohong", 54, "上海"));
    users.add(new User(3, "小红", "xiaohong", 54, "上海"));
    users.add(new User(4, "小红", "xiaohong11", 54, "上海"));
    users.add(new User(5, "小红", "xiaohong", 54, "上海"));

    //转为LinkedList集合
    LinkedList<User> linkedList = users.stream().collect(Collectors.toCollection(LinkedList::new));

    //转为TreeSet集合
    TreeSet<User> treeSet = users.stream().collect(Collectors.toCollection(TreeSet::new));

    //转为CopyOnWriteArrayListResult集合:高并发List集合
    List<User> copyOnWriteArrayListResult = users.stream().collect(Collectors.toCollection(CopyOnWriteArrayList::new));

接合(joining):

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

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

统计(count/averaging)

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

  • 计数:count
  • 平均值: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}

归约(reduce):

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
        // 求和方式1
        Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
        // 求和方式2
        Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
        // 求和方式3
        Integer sum3 = list.stream().reduce(0, Integer::sum);
        // 求乘积
        Optional<Integer> product = list.stream().reduce((x, y) -> x * y);
        // 求最大值方式1
        Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
        // 求最大值写法2
        Integer max2 = list.stream().reduce(1, Integer::max);

        System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
        System.out.println("list求积:" + product.get());
        System.out.println("list求和:" + max.get() + "," + max2);
    }
}
//输出结果:
//list求和:29,29,29
//list求积:2112
//list求和:11,11


//案例二:求所有员工的工资之和和最高工资。
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"));
        personList.add(new Person("Anni", 8200, 24, "female", "New York"));
        personList.add(new Person("Owen", 9500, 25, "male", "New York"));
        personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

        // 求工资之和方式1:
        Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
        // 求工资之和方式2:
        Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
                (sum1, sum2) -> sum1 + sum2);
        // 求工资之和方式3:
        Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);
        // 求最高工资方式1:
        Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                Integer::max);
        // 求最高工资方式2:
        Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
                (max1, max2) -> max1 > max2 ? max1 : max2);

        System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
        System.out.println("最高工资:" + maxSalary + "," + maxSalary2);
    }
}
//输出结果:
//工资之和:49300,49300,49300
//最高工资:9500,9500

归约(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

分组(partitioningBy/groupingBy)

  • 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
  • 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

640.webp

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]}}