Java8——Stream API及其操作

Stream简介

 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
 Stream 用来操作集合或数组,但不会改变集合或数组,也就是说流操作的数据源不会受影响,Stream 相当于在数据源和操作后的新流之间搭起一个数据传输管道,在这个传输管道中通过流计算进行一系列流水线式的中间操作,产生一个新的流。
 也就是说 Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”。
 注意:
  ①Stream 自己不会存储元素
  ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
  ③Stream 操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

Stream操作的三个步骤

 1、创建 Stream:一个数据源(如:集合、数组),获取一个流
 2、中间操作:一个中间操作链,对数据源的数据进行处理
 3、终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果
Java8——Stream API及其操作_第1张图片

创建Stream

 1、通过Collection系列集合提供的stream()或parallelStream(),stream()创建串行流,parallelStream()创建并行流

List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();

 2、通过 Arrays 中的静态方法 stream() 获取数组流程

Employee[] emps = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(emps);

 3、通过 Stream 类的静态方法 of()

Stream<String> of = Stream.of("aa","bb","cc");

 4、创建无限流
  ①迭代

//迭代
Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
//遍历打印前10个数据
iterate.limit(10).forEach(System.out::println);

  ②生成

Stream.generate(() -> Math.random())//生成无限流:随机数
	.limit(5)//取前5个值
	.forEach(System.out::println);//遍历打印
Stream中间操作

 多个中间操作可以连接起来形成一个流水线,除非在流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,也称为“惰性求值”。
 1、筛选与切片
  filter(Lambda)——接收Lambda,从流中排除某些元素

employees.stream()
		.filter((e) -> e.getAge() > 30)//过滤
		.forEach(System.out::println);//终止操作:遍历打印

  limit(n)——截断流,使其元素不超过给定数量,该操作是一个短路操作,就是说一旦数据量达到要求后就不再迭代流中的数据,提升效率

employees.stream()
		.filter((e) -> e.getAge() > 30)//过滤
		.limit(2)//截断
		.forEach(System.out::println);

  skip(n)——跳过流中的前n个元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补

employees.stream()
		.filter((e) -> e.getAge() > 30)//过滤
		.skip(2)//跳过
		.forEach(System.out::println);

  distinct——筛选,通过流所生成元素的hashCode和equals去除重复元素,因此需要流中的元素对象重写hashCode和equals方法

employees.stream()
		.filter((e) -> e.getAge() > 30)//过滤
		.skip(2)//跳过
		.distinct()//去重,需要流中的元素对象重写hashCode和equals方法
		.forEach(System.out::println);

 2、映射
  ①map(Function f)——接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素,再将新元素放在一个新流中

List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
list.stream()
	.map(str -> str.toUpperCase())
	.forEach(System.out::println);

   可以理解为:map会遍历流中的每一个元素,并将元素作为实参传入map中的函数中进行处理,并将返回的值放在一个新流中,最后终止操作时操作的是这个新流。
  ②mapToDouble(ToDoubleFunction f) ——接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream
  ③mapToInt(ToIntFunction f)—— 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream
  ④mapToLong(ToLongFunction f) ——接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream
  ⑤flatMap(Function f) ——接收一个函数作为参数,该函数的返回值必须是一个流(Stream),将原流中的每个值通过函数都转换成另一个流,然后把转换后的所有流连接成一个流,并实现流的扁平化,即扁平化后的流中的元素不再是一个个的流,而是一个个流中的具体的元素,与map的不同之处在于map是将函数的返回值整个作为新的元素放在一个新的流中,不会扁平化处理,类似于集合中的add(Object obj)和addAll(Collection c)
   flatMap接口:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

   示例:

public void test2() {
	List<String> list = Arrays.asList("aaa", "bbb", "ccc", "ddd");
	list.stream()
		.flatMap(TestStreamAPI::filterCharacter)//需要一个返回Stream的函数
		.forEach(System.out::println);
}

/**
 * 该方法必须返回一个Stream
 * @param str
 * @return
 */
public static Stream<Character> filterCharacter(String str) {
	List<Character> list = new ArrayList<>();
	for (Character ch : str.toCharArray()) {
		list.add(ch);
	}
	return list.stream();
}

 3、排序
  ①自然排序:sorted(),元素对象需实现Comparable接口

List<String> list = Arrays.asList("ccc", "aaa", "ddd", "bbb");
list.stream()
	.sorted()//自然排序
	.forEach(System.out::println);

   因为String实现了Comparable接口,因此可以使用自然排序
  ②定制排序:sorted(Comparator com),需要传入Comparator的实现

employees.stream().sorted((e1, e2) -> {
	if (e1.getAge() == e2.getAge())
		return e1.getName().compareTo(e2.getName());
	else
		return e1.getAge().compareTo(e2.getAge());
}).forEach(System.out::println);
Stream终止操作

 终止操作将不再返回流,而是对中间操作产生的流进行具体操作,可能有返回值,也可能没有。如果需要返回值但返回值有可能为空时,终止操作一般会返回一个Optional容器对象,把返回值封装在该容器对象中,可以通过调用Optional.get()获取具体的返回值,以避免出现空指针异常等。
 1、查找与匹配
  ①allMatch(Predicate p)——检查是否匹配所有元素,流中的所有元素都满足条件

//注意返回值是boolean,而不再是一个Stream
boolean allMatch = employees.stream().allMatch((e) -> e.getStatus().equals(Status.BUSY));

  ②anyMatch(Predicate p)——检查是否至少匹配一个元素,只要流中有一个元素满足条件就返回true

boolean anyMatch = employees.stream().anyMatch((e) -> e.getStatus().equals(Status.BUSY));

  ③noneMatch(Predicate p)——检查是否没有匹配任何元素,当至少有一个元素匹配时就返回false,当所有元素都不匹配时才返回true,通过该方法得出有匹配的元素的结论

boolean noneMatch = employees.stream().noneMatch((e) -> e.getStatus().equals(Status.BUSY));

  ④findFirst()——返回第一个元素,调用该方法后会返回一个Optional容器对象

Optional<Employee> findFirst = employees.stream().findFirst();
Employee employee = findFirst.get();
System.out.println(employee);

  ⑤findAny()——返回当前流中的任意元素

Optional<Employee> findAny = employees.parallelStream()//并行流
		.filter(e -> e.getStatus().equals(Status.FREE))//过滤
		.findAny();
System.out.println(findAny.get());

  ⑥count()——返回流中元素总数

long count = employees.stream().count();

  ⑦max(Comparator c)——返回流中最大值
   获取工资最高的员工信息:

Optional<Employee> max = employees.stream().max((e1,e2)->Double.compare(e1.getSalary(), e2.getSalary()));

  ⑧min(Comparator c)——返回流中最小值
   获取员工的最低工资的值:

Optional<Double> min = employees.stream()
		.map(Employee::getSalary)//将流转化为工资数据的流
		.min(Double::compareTo);//获取工资的最小值
System.out.println(min.get());

  ⑨forEach(Consumer c)——内部迭代(使用 Collection 接口需要用户去做迭
代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)

 2、归约与收集
  ①reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回值类型为 T

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);//55

   说明:reduce(0,(x,y) -> x+y)中的第一个参数0表示归约的起始值,(x,y) -> x+y是对BinaryOperator接口的实现,表示归约的规则。在这个示例中一开始会把起始值0作为x、把流中的第一个元素1作为y传入到接口函数中,再把通过规则计算出来的结果1赋值给第一个参数x,然后把流中的第二个元素2作为y再次通过规则进行计算,再将计算来的结果3赋值给第一个参数x,以此类推,直到将流中的所有元素都进行过累加操作,最后将结果返回。有些类似于递归调用。
  ②reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional,和①的区别在于没有起始值,因此结果可能为null,所以返回Optional

Optional<Double> reduce = employees.stream()
		.map(Employee::getSalary)
		.reduce(Double::sum);
System.out.println(reduce.get());

  map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名,通过map提取数据,通过reduce对提取的数据进行归约操作
  ③collect(Collector c) 将流转换为其他形式。接收一个 Collector 接口的实现,指明按照哪种方式搜集结果,用于给Stream中元素做汇总,功能十分强大。

List<String> collect = employees.stream()
		.map(Employee::getName)//提取
		.collect(Collectors.toList());//将结果搜集至list
collect.forEach(System.out::println);

Set<String> collect = employees.stream()
		.map(Employee::getName)// 提取
		.collect(Collectors.toCollection(HashSet::new));// 将结果搜集至HashSet

   集合也有forEach方法,这是因为集合继承了Iterable:

public interface Collection<E> extends Iterable<E>

   在Iterable接口中有forEach方法的实现:

public interface Iterable<T> {
    
    Iterator<T> iterator();
    
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

  Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。Collectors 工具类提供了很多静态方法,可以方便地创建常见收集器的实例,具体方法与示例如下表:

方法 返回类型 用途 示例
toList List 把流中元素搜集到List List emps = list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set Set emps = list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到创建的集合(可为任意集合类型) Collection emps = list.stream().collect(Collectors.toCollection(ArrayList::new);
counting Long 计算流中元素的总数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和等 int total = list.stream().collect(Collectors.summingInt(Employee::getSalary);
averagingInt Double 计算流中元素Integer属性的平均值 double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值(如平均值、最大值、最小值、总和等) IntSummaryStatistics iss = list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串 String str = list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional 根据比较器选择最大值 Optional max = list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optional 根据比较器选择最小值 Optional collect = employees.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compare));
reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 int total = list.stream().collect(Collectors.reducing(0,Employee::getSalary,Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果转换函数 int how = list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map> 根据某属性值对流分组,key为K,value为List Map> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map> 根据true或false进行分区 Map> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

  多级分组示例:先按照status分组,再按照年龄段分组

Map<Status, Map<String, List<Employee>>> collect = employees.stream()
		.collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy((Employee e) -> {
			if (e.getAge() <= 35)
				return "青年";
			else if (e.getAge() <= 50)
				return "中年";
			else
				return "老年";
		})));

   在groupingBy中可以传入分组逻辑。
  分区示例:满足条件的一个区,不满足条件的一个区,以工资是否大于5000分区

Map<Boolean, List<Employee>> collect = employees.stream()
		.collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));

  统计示例:统计员工工资信息的平均值、员工数量、工资总和、最大和最小值

DoubleSummaryStatistics collect = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
double average = collect.getAverage();
long count = collect.getCount();
double sum = collect.getSum();
double max = collect.getMax();
double min = collect.getMin();

  字符连接:joining的第一个参数是分隔符,第二个参数是连接好的字符串的前缀,第三个是连接好的字符串的后缀

String collect = employees.stream().map(Employee::getName).collect(Collectors.joining(",",">>>","<<<"));

你可能感兴趣的:(Java8)