欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JDK1.8新特性(2)Stream 详解

程序员文章站 2022-06-04 22:56:23
...

一、简介

流是 Java 8 的新成员,它允许你以声明式方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。此外,流还可以透明地并行处理,你无需写任何多线程代码了!

特性

  1. 不是数据结构,没有内部存储。
  2. 不支持索引访问。
  3. 延迟计算
  4. 支持并行
  5. 很容易生成数据或集合
  6. 支持过滤,查找,转换,汇总,聚合等操作。

对stream的操作可以分为两类,中间操作(intermediate operations)和结束操作(terminal operations):

  • 中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream。

  • 结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。
    虽然大部分情况下stream是容器调用Collection.stream()方法得到的,但stream和collections有以下不同:

  • 无存储。stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。

  • 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。

  • 惰式执行。stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。

  • 可消费性。stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

注意:和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。

总结:流的源可以是一个数组,集合,生成器方法,I/O通道等等,一个流可以有零个或多个中间操作,每一个中间操作都会返回一个新的流,供下一个操作使用,一个流只会有一个终止操作,Stream只有遇到终止操作,它的源才会开始执行遍历操作。

二、获取stream流

  • 由值创建流

使用Stream的静态方法of,通过显式值创建一个流

static <T> Stream<T> of(T... values)
Stream<String> stream = Stream.of("A","B","C");
stream.map(String::toLowerCase).forEach(System.out::print);
  • 由数组创建流

使用Arrays的静态方法stream可以从数组创建一个流。

public static <T> Stream<T> stream(T[] array)
public static <T> Stream<T> stream(T[] array,int startInclusive,int endExclusive)

注意:很多基本数据类型,都提供了对应的原始类型流特化的方法重载,如果是基本数据类型的的数组则会转换为对应的原始类型特化流,即IntStream,DoubleStream和 LongStream。

int[] numbers = {2, 3, 5, 7, 11, 13};
IntStream intStre= Arrays.stream(numbers);
int sum = intStre.sum();//总和是41
  • 由集合创建流

Collection 接口增加了默认方法stream 和 parallelStream()方法,因此任何集合的实现都可以调用这两个方法由集合创建流。区别在于,后者创建的是一个并行流。

default Stream<E> stream()
default Stream<E> parallelStream()
 List<String> list = Arrays.asList("ab", "cd", "ef");
 Stream<String> colStream = list.stream();
  • 由文件生成流

Java 中用于处理文件等 I/O 操作的 NIO API(非阻塞 I/O)已更新,以便利用 Stream API。java.nio.file.Files 中的很多静态方法都会返回一个流。
例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。使用你迄今所学的内容,你可以用这个方法看看一个文件中有多少各不相同的词:

long uniqueWords=0;
try(Stream<String> lines=Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){//流会自动关闭
    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))//生成单词流
            .distinct()//合并重复项
            .count();//统计各个不相同的单词的个数

}catch (IOException e) {
}
  • 由函数生成流:创建无限流

Stream API 提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate 。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用 limit(n) 来对这种流加以限制,以避免打印无穷多个值。

三、Stream常用的API

一、中间操作

  • filter 过滤:该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。说白了就是给一个条件,filter会根据这个条件截取流中得数据。
public static void testFilter(){
	List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
	//截取所有能被2整除得数据
	List<Integer> collect = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
	System.out.println("collect = " + collect);
}
  • distinct 去重:该操作会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。
public static void main(String[] args) {
	List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
	List<Integer> collect = numbers.stream().distinct().collect(Collectors.toList());
	System.out.println("collect = " + collect);
}
  • sorted 排序:对流中得数据进行排序,可以以自然序或着用Comparator 接口定义的排序规则来排序一个流。Comparator 能使用lambada表达式来初始化,还能够逆序一个已经排序的流。
public static void main(String[] args) {
	List<Integer> integers = Arrays.asList(5, 8, 2, 6, 41, 11);
	//排序默认为顺序  顺序 = [2, 5, 6, 8, 11, 41]
	List<Integer> sorted = integers.stream().sorted().collect(Collectors.toList());
	System.out.println("顺序 = " + sorted);
	//逆序    逆序 = [41, 11, 8, 6, 5, 2]
	List<Integer> reverseOrder = integers.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
	System.out.println("逆序 = " + reverseOrder);
	//也可以接收一个lambda
	List<Integer> ages = integers.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
}
  • limit 截取:该方法会返回一个不超过给定长度的流。
public static void testLimit(){
	List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
	//截取流中得前三个元素  collect = [1, 2, 1]
	List<Integer> collect = integers.stream().limit(3).collect(Collectors.toList());
	System.out.println("collect = " + collect);
}
  • skip 舍弃:该方法会返回一个扔掉了前面n个元素的流。如果流中元素不足n个,则返回一个空流。
public static void testSkip(){
	List<Integer> integers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
	//丢掉流中得前三个元素  collect = [3, 3, 2, 4]
	List<Integer> collect = integers.stream().skip(3).collect(Collectors.toList());
	System.out.println("collect = " + collect);
}
  • map 归纳:该方法会接受一个函数作为参数,这个函数会被应用到每个元素上,并将其映射成一个新的元素。就是根据指定函数获取流中得每个元素得数据并重新组合成一个新的元素。
public static void main(String[] args) {
	//自己建好得一个获取对象list得方法
	List<Dish> dishList = Dish.getDishList();
	//获取每一道菜得名称  并放到一个list中
	List<String> collect = dishList.stream().map(Dish::getName).collect(Collectors.toList());
	//collect = [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
	System.out.println("collect = " + collect);
}
  • flatMap 扁平化:该方法key可以让你把一个流中的每个值都换成另一个流,然后把所有的流都链接起来成为一个流。

    给 定 单 词 列 表[“Hello”,“World”] ,你想要返回列表 [“H”,“e”,“l”, “o”,“W”,“r”,“d”],你可能会认为这很容易,通过map你可以把每个单词映射成一张字符表,然后调用 distinct 来过滤重复的字符,但是这个方法的问题在于,传递给 map 方法的Lambda为每个单词返回了一个 String[] ( String列表)。因此, map 返回的流实际上是Stream<String[]> 类型的。而你真正想要的是用Stream 来表示一个字符流。

    正确写法应该是通过flatMap对其扁平化并作出对应处理。

public static void main(String[] args) {
	String[] words = {"Hello", "World"};
	List<String> collect = Stream.of(words).        //数组转换流
		map(w -> w.split("")).  //去掉“”并获取到两个String[]
		flatMap(Arrays::stream).        //方法调用将两个String[]扁平化为一个stream
		distinct().                     //去重    
		collect(Collectors.toList());
	//collect = [H, e, l, o, W, r, d]
	System.out.println("collect = " + collect);
}
  • peek对每个元素执行操作并返回一个新的Stream: 设计初衷就是在流的每个元素恢复运行之前,插入执行一个动作。
public static void main(String[] args) {
	List<Integer> numbers = Arrays.asList(2, 3, 4, 5);
	List<Integer> result =numbers.stream()
		.peek(x -> System.out.println("from stream: " + x))
		.map(x -> x + 17)
		.peek(x -> System.out.println("after map: " + x))
		.filter(x -> x % 2 == 0)
		.peek(x -> System.out.println("after filter: " + x))
		.limit(3)
		.peek(x -> System.out.println("after limit: " + x))
		.collect(Collectors.toList());
}
  • collect 收集:从上面得代码已经可以看出来,collect是将最终stream中得数据收集起来,最终生成一个list,set,或者map。
public static void main(String[] args) {
	List<Dish> dishList = Dish.getDishList();
	//list
	List<Dish> collect = dishList.stream().limit(2).collect(Collectors.toList());
	//set
	Set<Dish> collect1 = dishList.stream().limit(2).collect(Collectors.toSet());
	//map
	Map<String, Dish.Type> collect2 = dishList.stream().limit(2).collect(Collectors.toMap(Dish::getName, Dish::getType));
}

二、查找和匹配

  • anyMatch:anyMatch方法可以回答“流中是否有一个元素能匹配到给定的谓词”。会返回一个boolean值。
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	boolean b = dish.stream().anyMatch(Dish::isVegetarian);
	System.out.println(b);
}
  • allMatch:allMatch方法和anyMatch类似,校验流中是否都能匹配到给定的谓词。
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	//是否所有菜的热量都小于1000
	boolean b = dish.stream().allMatch(d -> d.getCalories() < 1000);
	System.out.println(b);
}
  • noneMatch:noneMatch方法可以确保流中没有任何元素与给定的谓词匹配。
public static void main(String[] args) {
    List<Dish> dish = Dish.getDish();
    //没有任何菜的热量大于等于1000
    boolean b = dish.stream().allMatch(d -> d.getCalories() >= 1000);
    System.out.println(b);
}
  • findAny:findAny方法将返回当前流中的任意元素。
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findAny();
	System.out.println("any = " + any);
}
  • findFirst:findFirst方法能找到你想要的第一个元素。
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	Optional<Dish> any = dish.stream().filter(Dish::isVegetarian).findFirst();
	System.out.println("any = " + any);
}

三、归约 reduce

  • 元素求和
public static void main(String[] args) {
	List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
	//求list中的和,以0为基数
	Integer reduce = integers.stream().reduce(0, (a, b) -> a + b);
	//Integer的静态方法
	int sum = integers.stream().reduce(0, Integer::sum);
	System.out.println("reduce = " + reduce);
}
  • 最大值和最小值
public static void main(String[] args) {
	List<Integer> integers = Arrays.asList(1, 2, 3, 6, 8);
	Optional<Integer> min = integers.stream().reduce(Integer::min);
	System.out.println("min = " + min);
	Optional<Integer> max = integers.stream().reduce(Integer::max);
	System.out.println("max = " + max);
}

四、收集器 Collectors

  • 查找流中的最大值和最小值 minBy maxBy
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	//创建一个Comparator来进行比较  比较菜的卡路里
	Comparator<Dish> dishComparator = Comparator.comparingInt(Dish::getCalories);
	//maxBy选出最大值
	Optional<Dish> collect = dish.stream().collect(Collectors.maxBy(dishComparator));
	System.out.println("collect = " + collect);
	//选出最小值
	Optional<Dish> collect1 = dish.stream().collect(Collectors.minBy(dishComparator));
	System.out.println("collect1 = " + collect1);
}
  • 汇总 summingInt:Collectors.summingInt 。它可接受一个把对象映射为求和所需 int 的函数,并返回一个收集器。
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	//计算总和
	int collect = dish.stream().collect(Collectors.summingInt(Dish::getCalories));
	System.out.println("collect = " + collect);
}
  • 平均数 averagingInt
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	//计算平均数
	Double collect = dish.stream().collect(Collectors.averagingInt(Dish::getCalories));
	System.out.println("collect = " + collect);
}
  • 连接字符串 joining
public static void main(String[] args) {
	List<Dish> dish = Dish.getDish();
	String collect = dish.stream().map(Dish::getName).collect(Collectors.joining());
	System.out.println("collect = " + collect);
}

joining 工厂方法有一个重载版本可以接受元素之间的分界符,这样你就可以得到一个逗号分隔的菜肴名称列表。

String collect = dish.stream().map(Dish::getName).collect(Collectors.joining(","));
  • 得到流中的总数 counting
long howManyDishes = dish.stream().collect(Collectors.counting());
相关标签: jdk1.8新特性