Java - Stream流 - 让你的代码更简洁(创建流、中间操作(筛选、映射、排序)、终止操作(匹配、约简、收集))

    Java 8中有两个最为重要的改变:第一个是Lambda表达式,另一个则是Stream API。使用Stream配合Lambda表达式、方法引用会让你的代码更简洁。

Java - Lambda表达式 / 方法引用 / 构造器引用 / 数组引用(多种情况举例说明)

    Stream(java.util.stream)是Java 8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找过滤映射数据等操作,使用Stream API对集合数据进行操作,就类似于使用SQL进行的数据库查询。也可以使用Stream API来并行执行操作。简言之,Stream API提供了一种高效且易于使用的处理数据的方式。
    实际开发中,项目中多数数据源都来自于mySQL、Oracle等数据库。但现在数据源可以来自于MongDB、Radis等NoSQL。而这些数据需要在Java层面进行操作,不需要在数据库进行操作。

Stream的三个步骤

  1. 创建Stream:从一个数据源(集合、数组)获取一个流。(Stream不会存储元素,也不会改变数据源(类似Iterator)。它只对数据进行操作,最后返回一个新的Stream)
  2. 中间操作:多个中间操作组成一个中间操作链,对数据源的数据进行处理。(流水线)
  3. 终止操作:只要没有执行终止操作,中间操作都不会执行(延迟操作)。一旦执行终止操作,就会执行中间操作链,并产生结果。执行完终止操作之后该流不能再被使用,若要继续执行操作,需要重新创建流。
    在这里插入图片描述

    下面创建一个商品类Goods进行说明:

import java.util.ArrayList;
import java.util.List;

public class Goods {
	private String name;  //商品名称
	private int price;  //商品价格
	
	public Goods() {}
	public Goods(String name, int price) {
		this.name = name;
		this.price = price;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	
	@Override
	public String toString() {
		return "Goods [name=" + name + ", price=" + price + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + price;
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Goods other = (Goods) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (price != other.price)
			return false;
		return true;
	}
	//商品列表
	public static List<Goods> getGoods() {
		List<Goods> list = new ArrayList<Goods>();
		list.add(new Goods("Iphone", 5000));
		list.add(new Goods("Kindle", 650));
		list.add(new Goods("BMW", 688888));
		list.add(new Goods("Coffee", 20));
		list.add(new Goods("Laptop", 5555));
		list.add(new Goods("T-shirt", 138));
		list.add(new Goods("LOUIS VUITTON", 12345));
		list.add(new Goods("Pencil", 8));
		list.add(new Goods("Shoes", 654));
		list.add(new Goods("Book", 128));
		return list;
	}
}

创建Stream

方式一:通过集合

    Collection接口有两个获取流的方法:stream()parallelStream()

List<Goods> list = Goods.getGoods();
//default Stream<E> stream():返回一个顺序流
Stream<Goods> stream = list.stream();
//default Stream<E> parallelStream():返回一个并行流
Stream<Goods> parallelStream = list.parallelStream();

方式二:通过数组

    Arrays类的静态方法 stream() 可以获取数组流。

List<Goods> list = Goods.getGoods();
Goods[] goods = (Goods[]) list.toArray(new Goods[list.size()]);
//public static <T> Stream<T> stream(T[] array)
Stream<Goods> stream = Arrays.stream(goods);

    Arrays类中通过对stream()方法重载,可以处理基本类型的数组:

public static IntStream stream(int[] array)
public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)

方式三:通过Stream的of()

    通过Stream类的静态方法of()创建一个流。它可以接收任意数量的参数。

Stream<Integer> stream1 = Stream.of(1,2,3,4,5);
Stream<String> stream2 = Stream.of("a","b","c");

方式四:创建无限流

    使用Stream类的静态方法 iterate()generate() 创建无限流。

Stream.iterate(1, t -> t+1).limit(10).forEach(System.out::println);

    迭代生成前10个数。iterate第一个参数是初始值,第二个参数是迭代需要进行的操作。forEach是终止操作。limit是限制前10个。如果没有limit,则将无限迭代。

Stream.generate(Math :: random).limit(10).forEach(System.out::println);

    生成10个随机数。如果没有limit。则将无限生成。

中间操作

    多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,成为惰性求值

筛选与切片

  1. filter(Predicate<? super T> predicate)过滤。从流中过滤指定的元素。
  2. limit(long maxSize)截断。使其元素数量不超过给定的数量。
  3. skip(long n)跳过元素。返回一个跳过前n个元素的流。若流中元素不够n个,则返回一个空流(与limit(n)互补)
  4. distinct()去重。通过计算元素的hash值,去除重复的元素
  5. concat(Stream<? extends T> a, Stream<? extends T> b)连接流。它的元素是a的元素后面跟着b的元素。

    filter(Predicate<? super T> predicate)
    Predicate<T>boolean test(T t)
    获取商品列表中价格大于5000的商品:

List<Goods> list = Goods.getGoods();
list.stream().filter(good -> good.getPrice() > 5000).forEach(System.out :: println);

运行结果:
Goods [name=BMW, price=688888]
Goods [name=Laptop, price=5555]
Goods [name=LOUIS VUITTON, price=12345]

    获取商品列表中名称长度大于10的商品:

List<Goods> list = Goods.getGoods();
list.stream().filter(good -> good.getName().length() > 10).forEach(System.out :: println);

运行结果:Goods [name=LOUIS VUITTON, price=12345]


    limit(long maxSize)
    获取商品列表中前3个商品:

List<Goods> list = Goods.getGoods();
list.stream().limit(3).forEach(System.out :: println);

运行结果:
Goods [name=Iphone, price=5000]
Goods [name=Kindle, price=650]
Goods [name=BMW, price=688888]


    skip(long n)
    跳过商品列表中前8个商品:

List<Goods> list = Goods.getGoods();
list.stream().skip(8).forEach(System.out :: println);

运行结果:
Goods [name=Shoes, price=654]
Goods [name=Book, price=128]

    如果跳过商品列表中前11个商品,则最后返回一个空流。


    distinct()
    去除商品列表中重复的商品:

list.add(new Goods("Iphone", 5000));
list.add(new Goods("Iphone", 5000));
list.add(new Goods("Iphone", 5000));
list.add(new Goods("BMW", 688888));
list.add(new Goods("BMW", 688888));
list.add(new Goods("LOUIS VUITTON", 12345));
list.stream().distinct().forEach(System.out :: println);

    运行结果与原商品列表相同。


    concat(Stream<? extends T> a, Stream<? extends T> b)
    连接两个流:

Stream<Integer> part1 = Stream.of(1,2,3,4);
Stream<Integer> part2 = Stream.of(5,6,7);
Stream<Integer> result = Stream.concat(part1, part2);
result.forEach(System.out :: print);

运行结果:1234567

映射

  1. map(Function<? super T, ? extends R> mapper):接收一个函数作为参数,将元素转换成其它形式或提取信息。该函数会被应用到每个元素,并将其映射成一个新的元素。(类似集合的add())
  2. flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有的流汇总成一个流。(类似集合的addAll())
  3. IntStream mapToInt(ToIntFunction<? super T> mapper)

    map(Function<? super T, ? extends R> mapper)
    Function<T, R>R apply(T t)
    获取商品的名称并转化为大写(lambda表达式方式):

List<Goods> list = Goods.getGoods();
list.stream().map(good -> good.getName())
			 .map(string -> string.toUpperCase())
			 .forEach(string -> System.out.println(string));

    方法引用方式(好漂亮啊(♥∀♥)):

list.stream().map(Goods :: getName).map(String :: toUpperCase).forEach(System.out :: println);

    flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
    Function<T, R>R apply(T t)
    现在我们来区分一下map、flatmap。

public static Stream<Character> function(String string) {
	List<Character> list = new ArrayList<Character>();
	for(Character c : string.toCharArray()) {
		list.add(c);
	}
	return list.stream();
}

    上面这个函数是将传进来String分割成单独的字符,然后组成一个流。例如,function(“boat”)的返回值是流 [‘b’,‘o’,‘a’,‘t’] 。下面是使用map的做法:

List<String> list = Arrays.asList("ABC","DE","FG","HIJK");
Stream<Stream<Character>> map = list.stream().map(StreamTest :: function);
map.forEach(s -> {
	s.forEach(System.out :: print);
});

运行结果:ABCDEFGHIJK

    上面得到的map是由多个Stream<Character>类型的流组成,每个流又由Character类型组成,如果遍历所有字符需要双重遍历。map操作相当于集合的add(),得到[[‘A’,‘B’,‘C’],[‘D’,‘E’],[‘F’,‘G’],[‘H’,‘I’,‘J’,‘K’]]。
    下面是使用flatmap的做法:

List<String> list = Arrays.asList("ABC","DE","FG","HIJK");
Stream<Character> flatMap = list.stream().flatMap(StreamTest :: function);
flatMap.forEach(System.out :: print);

运行结果:ABCDEFGHIJK

    上面得到的flatmap是由多个Character类型的流组成,只需要单重遍历。flatmap操作相当于集合的addAll(),得到[‘A’,‘B’,‘C’,‘D’,‘E’,‘F’,‘G’,‘H’,‘I’,‘J’,‘K’]。

排序

  1. sorted():通过自然排序,产生一个新流
  2. sorted(Comparator<? super T> comparator):通过比较器排序,产生一个新流

Comparable(自然排序)和Comparator(比较器)的用法(Java)

    下面以sorted(Comparator<? super T> comparator)为例说明:将商品按商品的价格从高到低排序:

List<Goods> list = Goods.getGoods();
list.stream().sorted((good1, good2) -> -Integer.compare(good1.getPrice(), good2.getPrice()))
		.forEach(System.out :: println);

运行结果:
Goods [name=BMW, price=688888]
Goods [name=LOUIS VUITTON, price=12345]
Goods [name=Laptop, price=5555]
Goods [name=Iphone, price=5000]
Goods [name=Shoes, price=654]
Goods [name=Kindle, price=650]
Goods [name=T-shirt, price=138]
Goods [name=Book, price=128]
Goods [name=Coffee, price=20]
Goods [name=Pencil, price=8]

终止操作

    终止操作会执行中间的所有操作生成结果。其结果可以是任何不是流的值,如:List、Integer甚至是void。
    流进行了终止操作后,不能再次使用

匹配与查找

  1. boolean allMatch(Predicate<? super T> predicate):检测是否匹配所有元素
  2. boolean anyMatch(Predicate<? super T> predicate):检测是否至少匹配一个元素
  3. boolean noneMatch(Predicate<? super T> predicate):检测是否没有匹配的元素
  4. Optional<T> findFirst():返回第一个元素
  5. long count():返回流中元素总个数
  6. Optional<T> max(Comparator<? super T> comparator):返回留着最大值
  7. Optional<T> min(Comparator<? super T> comparator):返回留着最小值
  8. void forEach(Consumer<? super T> action):内部迭代(Iterator是外部迭代)。在并行流中,forEach方法会以任意顺序遍历各个元素。

    boolean anyMatch(Predicate<? super T> predicate)
    判断是否有商品的价格高于500000:

boolean allMatch = list.stream().anyMatch(good -> good.getPrice() > 500000);
System.out.println(allMatch);

运行结果:true


    Optional<T> findFirst()
    返回商品列表第一个商品:

Optional<Goods> findFirst = list.stream().findFirst();
System.out.println(findFirst);

运行结果:Optional[Goods [name=Iphone, price=5000]]


    long count()
    返回商品价格在100~1000的商品数量:

long count = list.stream().filter(goods -> 
			goods.getPrice() < 1000 &&
			goods.getPrice() > 100).count();
System.out.println(count);

运行结果:4


    Optional<T> max(Comparator<? super T> comparator)
    返回商品列表的最贵的商品:

Optional<Goods> max = list.stream().max((good1, good2)
		-> Integer.compare(good1.getPrice(), good2.getPrice()));
System.out.println(max);

运行结果:Goods [name=BMW, price=688888]

约简

    map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名。

  1. Optional<T> reduce(BinaryOperator<T> accumulator):将流中元素反复结合得到一个值,返回Optional<T>
  2. T reduce(T identity, BinaryOperator<T> accumulator):将流中元素反复结合得到一个值,返回T
  3. U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner):第一个参数幺元(初始值),用给定的accumulator函数产生流中元素的累积总和,用combiner将分别累积的各个部分整合成总和
  4. OptionalInt reduce(IntBinaryOperator op):将流中元素反复结合得到一个值,返回OptionalInt

    通常,如果reduce方法有一项约简操作 op,那么该约简就会产生 V0 op V1 op V2 op …,其中我们将函数调用 op(Vi, Vi+1) 写成 Vi op Vi+1。这项操作应该是可结合的:即组合元素时使用的顺序不应该成为问题。在数学标记法中,(X op Y) op Z 必须等于 X op (Y op Z),这使得在使用并行流时可以执行高效的约简。
    在实践中有很多有用的可结合操作,例如求和、乘积、字符串连接、取最大值和最小值、求集的并与交等。减法是一个不可结合操作,如 (6-3)-2 ≠ 6-(3-2)。


    OptionalInt reduce(IntBinaryOperator op)
    int applyAsInt(int left, int right)
    统计商品列表所有商品的价格之和:

OptionalInt sum = list.stream().mapToInt(Goods :: getPrice).reduce(Integer :: sum);
System.out.println(sum.getAsInt());

    也可以使用sum():

int sum = list.stream().mapToInt(Goods :: getPrice).sum();
System.out.println(sum);

运行结果:713386


    T reduce(T identity, BinaryOperator<T> accumulator)
    BinaryOperator<T,T,T>:T apply(T t, T t)
    Integer:int sum(int a, int b)
    如果有一个幺元值e使得 e op x = x,那么可以使用这个值作为计算的起点,如下面计算1-10的自然数之和:0+1+2+3+…+10

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream().reduce(0, Integer :: sum);  //第一个参数是初始值
System.out.println(sum);

    U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
    BiFunction<U, ? super T, U>:R apply(T t, U u)
    BinaryOperator<T,T,T>:T apply(T t, T t)
    现在,我们想计算商品类的商品的名称长度总和。但是,流的元素是String类型,累积结果是整数,所以我们需要使用另一种形式的reduce方法。

Integer result = list.stream().map(Goods::getName)
		.reduce(0, (total, string) -> total + string.length(), Integer::sum);

运行结果:62

    我们需要提供一种累积器函数 total + string.length(),这个函数会被反复调用,当用stream()创建流时,后面的 Integer::sum 没有执行。但当计算被并行化时,函数 total + string.length() 会有多个结果(每个结果就是对应字符串的长度),所以需要将每个结果合并,因此需要函数 Integer::sum 来处理。

int result = list.parallelStream().map(Goods :: getName)
		.reduce(0,(total, name) -> total + name.length(), Integer :: sum);
System.out.println(result);

运行结果:62

收集

  1. <R, A> R collect(Collector<? super T, A, R> collector):使用指定的收集器来收集当前流中的元素。Collectors实用类提供了很多静态方法,可以方便的创建常见收集器实例。如:List toList(),Set toSet(),Collection toCollection(),Map toMap()等。
  2. <A> A[] toArray(IntFunction<A[]> generator):在引用 A[]::new 时返回一个A类型的数组。
  3. summarizingInt(ToIntFunction<? super T> mapper):产生能够生成(Int/Long/Double)SummaryStatistics对象的收集器,通过它可以获得mapper应用于每个元素后所产生的结果的个数、总和、平均值、最大值、最小值。

    <R, A> R collect(Collector<? super T, A, R> collector)
    获取价格大于5000的商品并转换成Set:

List<Goods> list = Goods.getGoods();
Set<Goods> result = list.stream().filter(goods -> goods.getPrice() > 5000)
		.collect(Collectors.toSet());
result.forEach(System.out :: println);

运行结果:
Goods [name=LOUIS VUITTON, price=12345]
Goods [name=BMW, price=688888]
Goods [name=Laptop, price=5555]

    List、Set实现了Collection接口,Collection接口实现了Iterable接口,Iterable接口实现了forEach方法,所以List和Set都有forEach方法。Map接口也实现了forEach方法。
    如果想要控制获得的集合的种类,那么可以用下面的方式:

TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet :: new));

    获取商品列表的Hashmap:

List<Goods> list = Goods.getGoods();
Map<String, Integer> map = list.stream()
		.collect(Collectors.toMap(Goods :: getName, Goods :: getPrice));
Set<Entry<String, Integer>> entrySet = map.entrySet();
for(Map.Entry<String, Integer> entry : entrySet) {
	System.out.println(entry);
}

    对于每个toMap方法,都有一个等价的可以产生并发映射表的toConcurrentMap方法。单个并发映射表可以用于并行集合处理。当使用并行流,共享的映射表比合并映射表更高效。


    <A> A[] toArray(IntFunction<A[]> generator)
    IntFunction<R>:R apply(int value)

List<Goods> list = Goods.getGoods();
Goods[] goods = list.stream().toArray(Goods[] :: new);
for(Goods good : goods) {
	System.out.println(good);
}

    summarizingInt(ToIntFunction<? super T> mapper)
    ToIntFunction<T>:int applyAsInt(T value)
    获取商品个数,商品的价格的总和、最大值、最小值、平均值:

List<Goods> list = Goods.getGoods();
IntSummaryStatistics collect = list.stream().collect(Collectors.summarizingInt(Goods :: getPrice));
System.out.println("个数="+collect.getCount());
System.out.println("总和="+collect.getSum());
System.out.println("最大值="+collect.getMax());
System.out.println("最小值="+collect.getMin());
System.out.println("平均值="+collect.getAverage());

运行结果:
个数=10
总和=713386
最大值=688888
最小值=8
平均值=71338.6

基本类型流

    我们之前创建整数流都是收集到Stream<Integer>,但是将每个整数都包装到包装器对象中是很低效的,对其它基本类型也一样。流库中具有专门的类型:IntStream、LongStream、DoubleStream,可以直接存储基本类型值,而无需使用包装器。
    IntStream:可存储 intshortcharbyteboolean
    LongStream:可存储 long
    DoubleStream:可存储 floatdouble


    创建IntStream可以调用 IntStream.ofArrays.stream

IntStream stream1 = IntStream.of(1,2,3,4);
IntStream stream2 = Arrays.stream(new int[] {1,2,3,4});

    与对象流一样,IntStream 可以通过 generate 和 iterate 方法创建无限流。此外,IntStream 和 LongStream 有range(左闭右开)和 rangeClosed(左闭右闭),可以生成一段范围的整数。

IntStream stream1 = IntStream.range(0, 100);
IntStream stream2 = IntStream.rangeClosed(0, 100);

    我们可以通过 mapInt、mapLong、mapDouble 将对象流转换为基本类型流:

List<Goods> list = Goods.getGoods();
IntStream result = list.stream().mapToInt(Goods :: getPrice);

    我们可以使用boxed方法将基本类型流转换为对象流

Stream<Integer> boxed = result.boxed();

    
如有错误请大家指出了(ง •̀_•́)ง (*•̀ㅂ•́)و

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值