Java 8中有两个最为重要的改变:第一个是Lambda表达式,另一个则是Stream API。使用Stream配合Lambda表达式、方法引用会让你的代码更简洁。
Stream(java.util.stream)是Java 8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤、映射数据等操作,使用Stream API对集合数据进行操作,就类似于使用SQL进行的数据库查询。也可以使用Stream API来并行执行操作。简言之,Stream API提供了一种高效且易于使用的处理数据的方式。
实际开发中,项目中多数数据源都来自于mySQL、Oracle等数据库。但现在数据源可以来自于MongDB、Radis等NoSQL。而这些数据需要在Java层面进行操作,不需要在数据库进行操作。
Stream的三个步骤
- 创建Stream:从一个数据源(集合、数组)获取一个流。(Stream不会存储元素,也不会改变数据源(类似Iterator)。它只对数据进行操作,最后返回一个新的Stream)
- 中间操作:多个中间操作组成一个中间操作链,对数据源的数据进行处理。(流水线)
- 终止操作:只要没有执行终止操作,中间操作都不会执行(延迟操作)。一旦执行终止操作,就会执行中间操作链,并产生结果。执行完终止操作之后该流不能再被使用,若要继续执行操作,需要重新创建流。
下面创建一个商品类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。则将无限生成。
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,成为惰性求值。
筛选与切片
- filter(Predicate<? super T> predicate):过滤。从流中过滤指定的元素。
- limit(long maxSize):截断。使其元素数量不超过给定的数量。
- skip(long n):跳过元素。返回一个跳过前n个元素的流。若流中元素不够n个,则返回一个空流(与limit(n)互补)
- distinct():去重。通过计算元素的hash值,去除重复的元素
- 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
映射
- map(Function<? super T, ? extends R> mapper):接收一个函数作为参数,将元素转换成其它形式或提取信息。该函数会被应用到每个元素,并将其映射成一个新的元素。(类似集合的add())
- flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有的流汇总成一个流。(类似集合的addAll())
- 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’]。
排序
- sorted():通过自然排序,产生一个新流
- sorted(Comparator<? super T> comparator):通过比较器排序,产生一个新流
下面以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。
流进行了终止操作后,不能再次使用。
匹配与查找
- boolean allMatch(Predicate<? super T> predicate):检测是否匹配所有元素
- boolean anyMatch(Predicate<? super T> predicate):检测是否至少匹配一个元素
- boolean noneMatch(Predicate<? super T> predicate):检测是否没有匹配的元素
- Optional<T> findFirst():返回第一个元素
- long count():返回流中元素总个数
- Optional<T> max(Comparator<? super T> comparator):返回留着最大值
- Optional<T> min(Comparator<? super T> comparator):返回留着最小值
- 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用它来进行网络搜索而出名。
- Optional<T> reduce(BinaryOperator<T> accumulator):将流中元素反复结合得到一个值,返回Optional<T>
- T reduce(T identity, BinaryOperator<T> accumulator):将流中元素反复结合得到一个值,返回T
- U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner):第一个参数幺元(初始值),用给定的accumulator函数产生流中元素的累积总和,用combiner将分别累积的各个部分整合成总和
- 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
收集
- <R, A> R collect(Collector<? super T, A, R> collector):使用指定的收集器来收集当前流中的元素。Collectors实用类提供了很多静态方法,可以方便的创建常见收集器实例。如:List toList(),Set toSet(),Collection toCollection(),Map toMap()等。
- <A> A[] toArray(IntFunction<A[]> generator):在引用 A[]::new 时返回一个A类型的数组。
- 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:可存储 int、short、char、byte、boolean
LongStream:可存储 long
DoubleStream:可存储 float、double
创建IntStream可以调用 IntStream.of 或 Arrays.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();
如有错误请大家指出了(ง •̀_•́)ง (*•̀ㅂ•́)و