Java8 - Stream

299 阅读9分钟

代码托管:github.com/stopping5/j…

一、什么是Stream

1、定义

​ Java 8引入了一套新的类库,位于包java.util.stream下,称为Stream API。这套API操作数据的思路不同于我们之前介绍的容器类API,它们是函数式的,非常简洁、灵活、易读。

A sequence of elements supporting sequential and parallel aggregate operations.

​ 支持顺序和并行聚合操作的元素序列。

​ 在java8中java.util.Collection新增了两个默认方法,它们可以返回一个Stream。顺序流就是由一个线程执行操作。而并行流背后可能有多个线程并行执行。

//通过该集合返回集合源的流
default Stream<E> stream()
//通过该集合返回并行的流
default Stream<E> parallelStream()

2、Stream特性

  • Stream不会存储元素
  • Stream不改变源对象,会根据相关操作返回一个持有结果的新对象
  • Stream 操作是延迟执行的,意味着等待结果的时候才执行。

二、创建Stream

​ 通过集合、数组创建一个数据源。

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

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

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

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

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

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println); // 0 2 4 6 8 10

Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

三、中间操作

​ 中间操作的目的主要是对数据进行操作,例如映射、统计、过滤等...

1、筛选操作

Filter

返回predicate匹配的流

Stream<T> filter(Predicate<? super T> predicate)

案例:筛选年龄大于18并且薪资大于8000的员工

private static void filterDemo(List<Employee> employees) {
    employees.stream()
        //年龄大于18
        .filter((e)->{
            System.out.println("筛选大于18");
            return  e.getAge() > 18;
        })
        //并且薪资大于8000
        .filter((e)->{
            System.out.println("筛选大于8000");
            return e.getSalary() > 8000;
        })
        .limit(2)//&& 操作
        .forEach(
            (e)->{
                System.out.println(e.toString());
            }
    );
    //筛选大于18
    //筛选大于18
    //筛选大于18
    //筛选大于8000
    //Employee{name='张5', age=28, salary=18000.0}
    //筛选大于18
    //筛选大于8000
    //Employee{name='张6', age=19, salary=8400.0}
}

2、映射操作

- Map

接收lambda,将元素转换成其他形式和提取信息。接收一个函数作为参数,该函数会被应用到每个元素上。相当于数据库中提取一列的数据。

<R> Stream<R> map(Function<? super T,? extends R> mapper)
------------------------------------------------------------
Type Parameters:
	R - The element type of the new stream
Parameters:
	mapper - a non-interfering, stateless function to apply to each element
Returns:
	the new stream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vY8vZVQi-1625380843937)(img.stopping.top/20210704123…)]

类似的还有:mapToInt、mapToLong、mapToDouble

案例:在Employee获取所有年龄组成新的流。

这里通过employee.getAge()函数应用到每个元素上的结果。

private static void mapDemo(List<Employee> employees) {
    employees.stream().map(Employee::getAge).forEach(System.out::println);
}
//18 - 13 - 28 - 19 - 38 - 15 - 22

- FlatMap

接收一个函数作为参数,将流中每个值都换成另外一个流,然后把所有流连接成一个流。

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)----------------------------------------------------------Type Parameters:	R - The element type of the new streamParameters:	mapper - a non-interfering, stateless function to apply to each element which produces a stream of new valuesReturns:	the new stream

案例

将流中的每个字符串输出为字符。在FlatMap中直接就可以将每个元素的值转化为一个流。在Map中通过调用flatMapStringToChar()将元素转化为流,通过Stream<Stream(Charcyer)>相当于{{a,a,a},{b,b,b},{c,c,c}},而flatMap{a,a,a,b,b,b,c,c,c}两者里面的数据结构不一样。

private static void flatMapDemo() {
    System.out.println("Map实现···");
    List<String> s = Arrays.asList("aaa","bbb","ccc");
    //map实现每个元素字符串转化为字符流输出
    Stream<Stream<Character>> ssc = s.stream().map(StreamTest::flatMapStringToChar);
    ssc.forEach((sc)->{
        sc.forEach(System.out::println);
    });
    System.out.println("flatMap实现···");
    //flatMap 实现每个元素字符串转化为字符流输出
    s.stream().flatMap(StreamTest::flatMapStringToChar).forEach(System.out::println);
}

public static Stream<Character> flatMapStringToChar(String s){
    List<Character> lc = new ArrayList<>();
    for (Character c : s.toCharArray()){
        lc.add(c);
    }
    return lc.stream();
}
//Map实现···
//a
//a
//a
//b
//b
//b
//c
//c
//c
//flatMap实现···
//a
//a
//a
//b
//b
//b
//c
//c
//c

3、切片操作

limit 截取

限制返回长度不超过maxSize的流

Stream<T> limit(long maxSize)
-------------------------------------------------------------------
Parameters:
	maxSize - the number of elements the stream should be limited to
Returns:
	the new stream

skip 丢弃

丢弃流中前 n 个元素,如果丢弃的元素大于流的本身的元素则返回一个空流。

Stream<T> skip(long n)--------------------------------------------------------------------Parameters:	n - the number of leading elements to skipReturns:	the new stream

distinct 去重

去重,返回流中不同的元素(根据 Object.equals(Object))流

Stream<T> distinct()-----------------------------------------------------------------Returns:	the new stream

案例

//传入数据static List<Employee> getEmployees(){    Employee e1 = new Employee("张3",18,8000.00);    Employee e2 = new Employee("张4",13,8011.00);    Employee e3 = new Employee("张5",28,18000.00);    Employee e4 = new Employee("张6",19,8400.00);    Employee e5 = new Employee("张7",38,7000.00);    Employee e6 = new Employee("张8",15,8200.00);    Employee e7 = new Employee("张9",22,9000.00);    Employee e9 = new Employee("张9",22,9000.00);    Employee e8 = new Employee("张9",22,9000.00);    return Arrays.asList(e1,e2,e3,e4,e5,e6,e7);}private static void limitAndSkipDemo(List<Employee> employees) {    System.out.println("获取前4条数据");    employees.stream().limit(4).forEach(System.out::println);    System.out.println("跳过前4条数据");    employees.stream().skip(4).forEach(System.out::println);    //去重-筛选员工姓名之后去重复        		    employees.stream().map(Employee::getName).distinct().forEach(System.out::println);    //张3    //张4    //张5    //张6	    //张7    //张8    //张9}//获取前4条数据//Employee{name='张3', age=18, salary=8000.0}//Employee{name='张4', age=13, salary=8011.0}//Employee{name='张5', age=28, salary=18000.0}//Employee{name='张6', age=19, salary=8400.0}//跳过前4条数据//Employee{name='张7', age=38, salary=7000.0}//Employee{name='张8', age=15, salary=8200.0}//Employee{name='张9', age=22, salary=9000.0}

4、收集操作

四、终端操作

​ 中间操作不触发实际的执行,返回值是Stream,而终端操作触发执行,返回一个具体的值,除了collect, Stream API的终端操作还有max、min、count、allMatch、anyMatch、noneMatch、findFirst、findAny、forEach、toArray、reduce等

MAX、MIN

Optional<T> max(Comparator<? super T> comparator)Optional<T> min(Comparator<? super T> comparator)-------------------------------------------------Parameters:	comparator - a non-interfering, stateless Comparator to compare elements of this streamReturns:	an Optional describing the maximum/minimum  element of this stream, or an empty Optional if the stream is empty

案例:获取最大/最小年龄的员工信息

private static void maxMinDemo(List<Employee> employees) {    Employee maxAgeEmployee = employees.stream().max(Comparator.comparing(Employee::getAge)).get();    System.out.println("maxAgeEmployee:"+maxAgeEmployee.toString());    Employee minAgeEmployee = employees.stream().min(Comparator.comparing(Employee::getAge)).get();    System.out.println("minAgeEmployee"+minAgeEmployee.toString());    //maxAgeEmployee:Employee{name='张7', age=38, salary=7000.0}    //minAgeEmployeeEmployee{name='张4', age=13, salary=8011.0}}

Count

返回流中元素的个数

    private static void countDemo(List<Employee> employees) {        System.out.println("employee个数:"+ employees.stream().count());        //employee个数:9    }

allMatch/anyMatch/noneMatch

这几个函数都接受一个谓词Predicate,返回一个boolean值,用于判定流中的元素是否满足一定的条件。

boolean anyMatch(Predicate<? super T> predicate)boolean allMatch(Predicate<? super T> predicate)boolean noneMatch(Predicate<? super T> predicate)

它们的区别是:

  1. allMatch:只有在流中所有元素都满足条件的情况下才返回true。
  2. anyMatch:只要流中有一个元素满足条件就返回true。
  3. noneMatch:只有流中所有元素都不满足条件才返回true。

如果流为空,那么这几个函数的返回值都是true。

    private static void match(List<Employee> employees) {        System.out.println("是否所有员工都大于18岁:"+ employees.stream().allMatch(e->e.getAge()>18));        System.out.println("是否所有员工都大于10岁:"+ employees.stream().allMatch(e->e.getAge()>10));        System.out.println("是否有员工22岁:"+ employees.stream().anyMatch(e->e.getAge()==22));        //是否所有员工都大于18岁:false        //是否所有员工都大于10岁:true        //是否有员工22岁:true    }

findFirst/findAny

Optional<T> findFirst()返回流的第一个元素的 Optional,如果流为空,则返回空的 Optional。Optional<T> findAny()返回流的某些元素的 Optional,如果流为空,则返回空的 Optional。-------------------------------------------------------------------Returns:	an Optional describing some element of this stream, or an empty Optional if the stream is emptyThrows:	NullPointerException - if the element selected is null
    private static void find(List<Employee> employees) {        Employee employee = employees.stream().findFirst().get();        System.out.println("first employee:"+employee);        Employee employee1 = employees.stream().findAny().get();        System.out.println("any employee:"+employee1);        //first employee:Employee{name='张3', age=18, salary=8000.0}        //any employee:Employee{name='张3', age=18, salary=8000.0}    }

foreach

​ 遍历流中的元素,foreach和佛reachOrdered的区别:在并行流中,forEach不保证处理的顺序,而forEachOrdered会保证按照流中元素的出现顺序进行处理。

void forEach(Consumer<? super T> action)void forEachOrdered(Consumer<? super T> action)

reduce

​ reduce代表归约或者叫折叠,它是max/min/count的更为通用的函数,将流中的元素归约为一个值。例如例如 sum()、max() 或 count()都是归约函数。

T reduce(T identity,         BinaryOperator<T> accumulator)//相当于T result = identity;for (T element : this stream)    result = accumulator.apply(result, element)    return result;---------------------------------------------------------------------Optional<T> reduce(BinaryOperator<T> accumulator)//相当于boolean foundAny = false;T result = null;for (T element : this stream) {    if (!foundAny) {        foundAny = true;        result = element;    }    else        result = accumulator.apply(result, element);}return foundAny ? Optional.of(result) : Optional.empty();-----------------------------------------------------------------------<U> U reduce(U identity,             BiFunction<U,? super T,U> accumulator,             BinaryOperator<U> combiner)//相当于U result = identity;for (T element : this stream)    result = accumulator.apply(result, element)    return result;

Collectors

​ java.util.stream.Collectors 实现了Collector接口,提供了很多有用的归约操作,例如将中间操作累积为集合、连接数据、分组、统计等。

list.steam().collction(Collectors)

用法示例:

// Accumulate names into a ListList<String> list = people.stream().map(Person::getName).collect(Collectors.toList());// Accumulate names into a TreeSetSet<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));// Convert elements to strings and concatenate them, separated by commasString joined = things.stream()    .map(Object::toString)    .collect(Collectors.joining(", "));// Compute sum of salaries of employeeint total = employees.stream()    .collect(Collectors.summingInt(Employee::getSalary)));// Group employees by departmentMap<Department, List<Employee>> byDept    = employees.stream()    .collect(Collectors.groupingBy(Employee::getDepartment));// Compute sum of salaries by departmentMap<Department, Integer> totalByDept    = employees.stream()    .collect(Collectors.groupingBy(Employee::getDepartment,                                   Collectors.summingInt(Employee::getSalary)));// Partition students into passing and failingMap<Boolean, List<Student>> passingFailing =    students.stream()    .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));

groupingBy 分组

​ 分组类似于数据库查询语言SQL中的group by语句,它将元素流中的每个元素分到一个组,可以针对分组再进行处理和收集。

public static <T,K> Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)----------------------------------------------------------------将数据分组,分组关键字存在在集合中,数据保存在Map<K,List<T>对象中。对于并行数据流操作性能比较差。The returned Collector is not concurrent. For parallel stream pipelines, the combinerfunction operates by merging the keys from one map into another, which can be an expensive operation. If preservation of the order in which elements appear in the resulting Map collector is not required, using groupingByConcurrent(Function) may offer better parallel performance.Type Parameters:    T - the type of the input elements    K - the type of the keysParameters:	classifier - the classifier function mapping input elements to keysReturns:	a Collector implementing the group-by operationSee Also:    groupingBy(Function, Collector), groupingBy(Function, Supplier, Collector), groupingByConcurrent(Function)

案例

​ 新建一个集合,里面包括每个用户的名称、年龄、手机号。现在通过年龄来实现分组。

User.java 实体类

public class User {    /**     * 用户名     */    private String username;    /**     * 年龄     */    private Integer age;    /**     * 手机号     */    private String phone;}
public static void main(String[] args) {    List<User> users = Arrays.asList(new User[]{            new User("tom",18,"13141234567"),            new User("job",20,"13141234567"),            new User("gogo",18,"13141234567")    });    Map<Integer,List<User>> userMap =     users.stream().collect(Collectors.groupingBy(User::getAge));    userMap.forEach((i,user)->{        System.out.println("age:"+i+","+"user:"+user.toString());    });}

结果

age:18,user:[User{username='tom', age=18, phone='13141234567'}, User{username='gogo', age=18, phone='13141234567'}]age:20,user:[User{username='job', age=20, phone='13141234567'}]

使用Stream group方法等同于下面方法实现,很显然Steam Group更加的简洁。

/** * 非Stream group 实现 * */private static void userGroup(List<User> users) {    Map<Integer,List<User>> userMap = new HashMap<>();    for (User u : users){        Integer age = u.getAge();        if (userMap.containsKey(age)){            userMap.get(age).add(u);        }else{            List<User> user = new ArrayList<>();            user.add(u);            userMap.put(age,user);        }    }    userMap.forEach((i,user)->{        System.out.println("age:"+i+","+"user:"+user.toString());    });}