1、Stream(流)是什么?
- Stream即流,在Java 8中引入,它与java.io包里的InputStream与OutputStream是完全不同的概念。简单来讲流是一个包含着一系列数据项的集合,但它又不是一个存储容器,更像是一个与数据项相关的算法容器。
- Scala和Groovy证明了,函数作为一等公民可以大大扩充程序员的工具库。在Stream中也使用了函数式编程的思想,极大地提高和解放了生产力。熟练使用Stream强大的功能,你能写出更加简洁、表现力更强的代码,从此告别996。
2、 Stream 的构造
-
- 通过集合的stream()或者parallelStream();
-
- 流的静态方法,如Stream.of();
-
- 随机数流Random.ints();
-
- 包装类型在Stream中常用,但是其实基本类型也能使用Stream。如定义基本类型的Stream,可以使用IntStream,LongStream,DoubleStream的接口静态方法of,range,empty
-
- 从文件等输入流中构造
//1.集合中构造
Arrays.asList(1,2,3,4,5).stream()...;
//2.静态构造
Stream.of(1,2,3,4,5)...
//3.随机数流
//IntStream
new Random(100).ints();
IntStream.of(1,2,3);
IntStream.range(1,100);
//LongStream
new Random(100).longs();
//DoubleStream
new Random(100).doubles();
//4.IntStream/LongStream/DoubleStream
//也可以使用Stream<Integer>、Stream<Long>、Stream<Double>构造
IntStream.of(new int[]{1,2,3});
//5.文件输入构造
//通常Stream不需要关闭,仅仅是需要关闭在IO通道上运行的流
try(final Stream<String> lines=Files.lines(Paths.get("somePath"))){
lines.forEach(System.out::println);
}
复制代码
3、 Stream工作流程
-
Stream类似于一个迭代器,可以对流中每个元素迭代处理。串行化的处理与迭代器(Itrerator)类似,但是Stream的功能远不止迭代这么简单。
-
其中的中间操作、终端操作。通常小伙伴在stream处理中一顿操作后,发现IDE爆红,经常不太明白什么原因,很多情况下是不明白中间操作与终端操作。
-
简单来讲,中间操作执行后会返回一个流,类似于builder设计模式中build()后通常会有return this这么一个操作,中间操作返回了一个处理流从而提供了链式调用语法。而终端操作就是一个收尾操作,一般返回void或者非stream结果。如我们常用的toList()、toSet()、toMap()、toArray就是非stream结果,只有副作用的 foreach() 也是void。
-
map、flatmap、filter、peek、limit、skip、distinct、sorted...都是中间操作,foreach、forEachOrdered、collect、findFirst、min、max则是终端操作。
3.1、中间操作
3.1.1、map
- 作用于stream中的每个元素
//下面两种等价的方式,完成将字符串转大写并排序
//1.函数式接口方式
()->stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();
//2.Lambda表达式方式
()->stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
.map(v->v.toUpperCase()).sorted();
复制代码
3.1.2、flatmap
-
与map类似,都是一个函数作用于stream中的每个元素。
-
从函数签名可以看出:map返回值是一个对象,对象形成了一个新的Stream。而flatmap返回的是一个Stream。flatmap不会在创建一个新Stream,而是将原来元素转换为Stream。通常用于流的扁平化处理
//map()签名 <R> Stream<R> map(Function<? super T, ? extends R> mapper); //flatmap()签名 <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); //flatmap返回Stream Stream.of(1,22,33).flatMap(v->Stream.of(v*v)).collect(Collectors.toList()); //map返回对象 Stream.of(1,22,33).map(v->v*v).collect(Collectors.toList()); //flatMap的扁平化处理 List<Map<String, String>> list = new ArrayList<>(); Map<String,String> map1 = new HashMap(); map1.put("1", "one"); map1.put("2", "two"); Map<String,String> map2 = new HashMap(); map2.put("3", "three"); map2.put("4", "four"); list.add(map1); list.add(map2); Set<String> output= list.stream() // Stream<Map<String, String>> .map(Map::values) // Stream<List<String>> .flatMap(Collection::stream) // Stream<String> .collect(Collectors.toSet()); //Set<String> [one, two, three,four] 复制代码
3.1.3、peek
-
peek也是对流中的每一个元素进行操作,除了生成一个包含原所有元素的新Stream,还提供一个Consumer消费函数。
-
与map对比可看出peek在流处理中,可以做一些输出、外部处理、副作用等无返回值。生成一个包含原Stream的所有元素的新Stream,新Stream每个元素在被消费之前都会执行peek给定的消费函数;
//对每一个元素进行一些副作用 List<Integer> list = new ArrayList(); List<Integer> result = Stream.of(1, 2, 3, 4) .peek(x -> list.add(x)) .map(x -> x * 2) .collect(Collectors.toList()); //1 //2 //3 //[1, 2, 3] System.out.println(list); //map()签名,返回值R <R> Stream<R> map(Function<? super T, ? extends R> mapper); //peek()签名,返回值void Stream<T> peek(Consumer<? super T> action); 复制代码
3.1.4、filter
- 设置过滤条件,满足filter的元素汇总生成一个新的stream
//筛选出 >0 的元素
Arrays.asList(1,2,3,4,5)
.stream()
.filter(v-> v>0)
.toArray(Integer[]::new);
//筛选出字母A开头的字符串
Stream.of("apple","banana","orange","grapes", "melon","blueberry","blackberry")
.filter(s->s.startWith("A"))
.forEach(System.out::println)
复制代码
3.1.5、limit/skip
- limit返回Stream前面几个元素,skip扔掉前面几个元素。
3.1.6、distinct
-
简单stream的去重
//去重 Stream.of(1,2,3,3,3,2,4,5,6) .distinct() .collect(Collectors.toSet()); 复制代码
3.2、终端操作
3.2.1、findFirst
-
它总是返回 Stream 的第一个元素,或者空。注意返回值是Optional。
-
Optional可能含有值,也可能不含,主要是尽可能避免NPE。
Optional<String> ops = Stream.of("apple","banana","orange","blueberry","blackberry")
.filter(s->s.startsWith("b"))
.findFirst();
//banana
ops.orElse("apple");
Optional<String> ops = Stream.of("apple","banana","orange","blueberry","blackberry")
.filter(s->s.startsWith("c"))
.findFirst();
//apple
ops.orElse("apple");
复制代码
3.2.2、强大的collect
也许很多人经常搞不清Collector、Collection、Collections、Collectors。 1.Collection是Java集合祖先接口; 2.Collections是java.util包下的一个工具,内含有各种处理集合的静态方法。 3.java.util.stream.Stream#collect(java.util.stream.Collector)是Stream的一个函数,负责收集流。 4.java.util.stream.Collector是一个收集函数的接口,声明一个收集器功能。 5.java.util.Collectors是一收集器的工具类,内置了一系列常用收集器的实现,如Collectors.toList()/toSet(),作为上述第3条的collect()参数。
-
toList/toMap
//toList //方式1 List<String> list = Stream.of("I","love","you","too") .collect(ArrayList::new,ArrayList::add,ArrayList::addAll); //方式2 List<String> list = stream.collect(Collections.toList()) //toMap Map<Integer, Integer> collect1 = Stream.of(1, 3, 4) .collect(Collectors.toMap(x -> x, x -> x + 1)); 复制代码
-
数组转List
List list = new ArrayList<>(Arrays.asList("a", "b", "c")); Integer [] myArray = { 1, 2, 3 }; List myList = Arrays.stream(myArray).collect(Collectors.toList()); //基本类型也可以实现转换(依赖boxed的装箱操作) int [] myArray2 = { 1, 2, 3 }; List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList()); 复制代码
-
也许collect是我们最常用的一个终端操作,toList、toSet、toMap一气呵成。但是如果看collect函数签名会发现,这个collect不简单。
//collect1
<R> R collect(
Supplier<R> supplier,
BiConsumer<R,? super T> accumulator,
BiConsumer<R,R> combiner)
//collect2
/**
@param1: supplier为结果存放容器
@param2: accumulator为结果如何添加到容器的操作
@param3: combiner则为多个容器的聚合策略
*/
<R,A> R collect(collector<? super T,A,R> collector);
复制代码
-
从函数签名中可看出其实我们只用到了collect的第二个方法,即使用jdk提供的Collector的toList、toSet、toMap。正在因为这些操作常用,故jdk中直接提供了这些collector。
-
我们也可以实现自己的Collector
/* T:流中要收集的对象的泛型 A:累加器的类型,累加器是在收集过程中用于累加部分结果的对象 R:收集操作得到的对象(通常但不一定是集合)的类型。 */ public interface Collector<T,A,R> { //结果容器 Supplier<A> supplier(); //累加器执行累加的具体实现 BiConsumer<A, T> accumulator(); //合并2个结果的容器 BinaryOperator<A> combiner(); //对结果容器应用最终转换finisher Function<A, R> finisher(); //characteristics Set<Characteristics> characteristics(); } //自定义Collector //1.建立新的结果容器supplier(),返回值必须是一个空Supplier,供数据收集过程使用 //toList返回一个空List<>,toSet、toMap类似 @Override public Supplier<List<T>> supplier() { return ArrayList::new; } //2.累加器执行累加的具体实现accumulator() //从BiConsumer看出返回值void,接受2个参数第一个是累计值,第二个是当期处理的第n个元素 @Override public BiConsumer<List<T>, T> accumulator() { //可以看出是个拼接list的操作 return List::add; } //3.转换最终结果容器的finisher() //流遍历完成后,有时需要对结果处理,可以借助finisher。finisher()须返回累加过程中的最后一个调用 函数,用于将累加器对象转换为集合。 //接收2个参数,第一个参数是累加值,第二个参数是返回值,返回值就是我们最终要的东西。 @Override public Function<List<T>, List<T>> finisher() { //原样输出不额外处理 return (i) -> i; } //其实就是Function.identity() //4.合并容器的combiner() //Stream支持并行操作,但并行的子部分处理规约如何处理?combiner()就是指明了各个子任务如何合并。 @Override public BinaryOperator<List<T>> combiner() { //每个子任务是一个List,2个子任务结果合并累加到第一个子任务上 return (list1, list2) -> { list1.addAll(list2); return list1; }; } //5.characteristics() 复制代码
-
字符串的toList的Collector实现,核心是理解T、A、R
public class MyCollector<String> implements Collector<String, List<String>, List<String>> {
@Override
public Supplier<List<String>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<String>, String> accumulator() {
return (List<String> l, String s) -> {
l.add(s);
};
}
@Override
public BinaryOperator<List<String>> combiner() {
return (List<String> l, List<String> r) -> {
List<String> list = new ArrayList<>(l);
list.addAll(r);
return list;
};
}
@Override
public Function<List<String>, List<String>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
}
}
Stream<String> apple = Stream
.of("apple","banana", "orange", "grapes", "melon", "blueberry", "blackberry");
System.out.println(apple.collect(new MyCollector<>()));
//字符串的拼接concat
String concat = Stream
.of("apple", "apple","banana", "orange", "grapes", "melon", "berry", "blary")
.collect(
StringBuilder::new,
StringBuilder::append,
StringBuilder::append)
.toString();
//等价于上面,这样看起来应该更加清晰
String concat = stringStream.collect(() -> new StringBuilder(),(l, x) -> l.append(x), (r1, r2) -> r1.append(r2)).toString();
复制代码
3.2.3、reduce操作
- 这个方法的主要作用是把stream元素组合起来。它提供一个初始值(种子),然后将运算规则(BinaryOperator),和前面Stream的第一个、第二个、第n个元素组合。从这个意义上来讲,字符串拼接、数组的sum、min、max、average都是特殊的reduce。
Integer sum = integers.reduce(0,(a,b)->a+b);
或者
Integer sum = integers.reduce(0,Integer::sum);
复制代码
- 没有起始值的情况下,会把Stream前面2个元素组合起来,返回的是Optional( 由于没有起始的reduce(),故可能没有足够的元素,因此设计返回的是Optional)
//原生操作
final Integer[] integers = Lists.newArrayList(1, 2, 3, 4, 5)
.stream()
.collect(() -> new Integer[]{0}, (a, x) -> a[0] += x, (a1, a2) -> a1[0] += a2[0]);
//reducing操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
.stream()
.collect(Collectors.reducing(0, Integer::sum));
//当然Stream也提供了reduce操作
final Integer collect = Lists.newArrayList(1, 2, 3, 4, 5)
.stream().reduce(0, Integer::sum)
复制代码
- reduce拼接字符串
String concat = Stream.of("A","B","C","D")
.reduce("",String::concat);
复制代码
- reduce求最小值
double minValue = Stream.of(-1.5,1.0,-3.0,-2.0)
.reduce(Double.MAX_VALUE,Double::min);
复制代码
- reduce求和
//有起始值
int sumValue = Stream.of(1,2,3,4)
.reduce(0,Integer::sum);
//无起始值
int sumValue = Stream.of(1,2,3,4)
.reduce(Integer::sum).get();
复制代码
4、开发中常用的
4.1、 自己生成流
- 通过实现Supplier接口,可以自己控制流的生成。
- 把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);
复制代码
4.2 、生成等差数列
//步长为3
Stream.iterate(0, n -> n + 3)
.limit(10)
.forEach(x -> System.out.print(x + " "));
复制代码
4.3、 reduce、collect实现filter功能
1.collect实现方式更简单,效率也更高。 2.reduce每次需要new ArrayList是因为reduce规定第二个参数: BiFunction accumulator表达式不能改变其自身参数acc原有值,所以每次都要new ArrayList(acc),再返回新的list。
//reduce 方式
public static <T> List<T> filter(Stream<T> stream,Predicate<T> predicate){
return stream.reduce(new ArrayList<T>(),(acc,t)->{
if(predicate.test(t)){
List<T> lists = new ArrayList<T>(acc);
lists.add(t);
return lists;
}
return acc;
}, (List<T> left,List<T> right)->{
List<T> lists= new ArrayList<T>(left);
lists.addAll(right);
return lists;
}
}
//collect
public static <T> List<T> filter(Stream<T> stream, Predicate<T> predicate) {
return stream.collect(ArrayList::new, (acc, t) -> {
if (predicate.test(t))
acc.add(t);
}, ArrayList::addAll);
}
复制代码
4.4、 返回规约类型
//使用toCollection()指定规约容器的类型
ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));// (3)
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4)
复制代码
4.5、 groupingBy上下游收集器
- 使用partitioningBy()生成的收集器,适用于将Stream中的元素依据某个二值逻辑(满足、不满足)分成互补相交的两部分,如性吧,及格与否
- groupingBy()按照某个属性对数据分组,属性相同的元素会被对应到Map的一个key上
//partitioningBy()
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
//groupingBy
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
//mapping的下游
//按照部门对员工进行分组,并且只保留员工的名字
Map<Department,List<String>> byDept=employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.mapping(Employee::getName, //下游收集器
Collectors.toList()))); //更下游收集器
复制代码