一、stream简介
Stream API是Java 8中加入的一套新的API,主要用于处理集合操作,不过它的处理方式与传统的方式不同, 称为“数据流处理”。流(Stream)类似于关系数据库的查询操作,是一种声明式操作。
根据我个人的理解stream就是将数据转换成一个流,然后通过封装的API 对数据进行操作,stream的API基本上能实现各种数据操作。如果将集合中的数据看做是数据库中的表,那么stream的API则相当于SQL语言的各种语句。能够对数据进行筛选和各种条件查询。
二、函数式接口
因为stream中的API基本上都是基于函数式编程来写的。所以要想使用好stream,那就必须先掌握函数式编程,理解函数式接口。
函数式接口定义: 只包含一个抽象方法的接口,称为函数式接口。
如何判断一个接口是不是函数接口呢?
- 通过Lambda表达式来创建该接口的对象。(若Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
- 在任意函数式接口上使用@FunctionalInterface注解,这样做可以检查它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
是不是很简单,接下来我们可以自己定义一个函数式接口来玩一下。
package com.xingli.springlearningdemo.stream;
/**
* description: FunctionInterfaceDemo <br>
*
* @date: 2021/7/3 0003 下午 4:09 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class FunctionInterfaceDemo {
public static void main(String[] args) {
Dog dog = () -> System.out.println("这是一个小阔爱");
dog.say();
}
}
interface Dog{
public void say();
}
执行后输出如下
三、Java内置四大核心函数式接口
关于函数式接口阳哥的PPT曾经做过总结,我直接拿来给大家看一下:
下面逐一说一下这四个接口的用法。以为streamAPI中很多接口都是基于下面四种函数接口去写的。所以要想用好stream一定要很好的理解这四种接口。
下面每个接口我会一步一步的从匿名内部类-->拉姆达表达式 --> 简化版拉姆达表达式去逐一演示,便于理解为什么拉姆达表达式最终可以很简单。
Function<T, R>接口是一个函数型接口,接收一个参数,返回一个参数。对应日常操作包含入参和出参的方法。具体使用形式如下。
package com.xingli.springlearningdemo.stream;
import java.util.function.Function;
/**
* description: StreamFunctionDemo <br>
*
* @date: 2021/7/3 0003 上午 10:24 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class StreamFunctionDemo {
public static void main(String[] args) {
// Function(T,R) 功能型接口 入参T 出参R
// 匿名内部类写法
Function<Integer,Integer> function = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t) {
return t*2;
}
};
System.out.println(function.apply(1));
// 拉姆达表达式
Function<Integer,Integer> function2 = t ->{return t*2;};
System.out.println(function2.apply(1));
// 简化版拉姆达表达式
Function<Integer,Integer> function3 = t ->t*2;
System.out.println(function3.apply(1));
}
}
接下来看一下Stream的API,比如常用的map和flatMap方法就使用了Function<T, R>接口。
现在在去调用这个方法是不是就熟悉了。就拿map方法来说它使用Function<T, R>接口那就是传入一个参数,输出一个参数。
举个例子来说:假设现在有一个list里面有一堆字符串,我们要得到字符串的长度,就能使用map方法。
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
List<Integer> integerList = list.stream()
.map(s -> s.length())
.collect(Collectors.toList());
//简化版
List<Integer> integerList = list.stream()
.map(String::length)
.collect(Collectors.toList());
在这个例子中我们传入字符串,然后返回字符串的长度。剩下的三个接口可以自己在stream方法中找一下对应的方法,然后去调用熟悉一下,我就不再举例了。
Predicate<T> 是一个判定型接口,接收一个参数,返回固定Boolean值。具体用法如下:
package com.xingli.springlearningdemo.stream;
import java.util.function.Predicate;
/**
* description: StreamPredicateDemo <br>
*
* @date: 2021/7/3 0003 下午 12:13 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class StreamPredicateDemo {
// Predicate<T> 判定型接口 入参T 必须返回Boolean值
public static void main(String[] args) {
Predicate<Integer> predicate = new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer%2 == 0;
}
};
System.out.println(predicate.test(5));
Predicate<Integer> predicate1 = t ->{
return t%2 == 0;
};
System.out.println(predicate1.test(5));
Predicate<Integer> predicate2 = t -> t%2 == 0;
System.out.println(predicate2.test(5));
}
}
Supplier<T>是一个供给型函数,不接收参数,但是会返回一个参数。具体用法如下:
package com.xingli.springlearningdemo.stream;
import java.util.function.Supplier;
/**
* description: StreamSupplierDemo <br>
*
* @date: 2021/7/3 0003 上午 10:27 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class StreamSupplierDemo {
// Supplier<R> 供给型接口 没有入参 只有出参R
public static void main(String[] args) {
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "123456";
}
};
System.out.println(supplier.get());
Supplier<String> supplier1 = ()->{
return "123456";
};
System.out.println(supplier1.get());
Supplier<String> supplier2 = ()-> "123456";
System.out.println(supplier2.get());
}
}
Consumer<T>是一个消费型接口,接收一个参数,但是没有任何返回值。具体用法如下:
package com.xingli.springlearningdemo.stream;
import org.elasticsearch.common.recycler.Recycler;
import java.util.function.Consumer;
/**
* description: StreamConsumerDemo <br>
*
* @date: 2021/7/3 0003 下午 12:37 <br>
* @author: William <br>
* version: 1.0 <br>
*/
public class StreamConsumerDemo {
// Consumer<T> 消费型接口 只有入参没有出参
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("123456");
Consumer<String> consumer2 = s -> {
System.out.println(s);
};
consumer.accept("123456");
Consumer<String> consumer3 = s -> System.out.println(s);
consumer.accept("123456");
Consumer<String> consumer4 = System.out::println;
consumer.accept("123456");
}
}
四、stream流式计算
讲完了四大函数我们就能开始正式来说一下stream的一些方法了。
Stream流的获取方式
①通过集合Collection获取
List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
Stream<String> stream = list.stream();
② 通过数组获取
Stream<String> stream2 = Arrays.stream(new String[]{"aaa", "bbb", "ccc"});
③ 直接获取
Stream<String> stream3 = Stream.of("aaa","bbb","ccc");
stream常用方法
1.filter用来对数据筛选
可以看到使用的是四大函数中的Predicate,这个函数接收一个参数,但是只返回固定的boolean值。
List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
List<String> stringList = list.stream()
.filter(s -> s.length() == 3)
.collect(Collectors.toList());
2.limit 这个跟MySQL 中的limit基本上一样,就是取前多少条的意思
List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc"));
List<String> stringList = list.stream()
.filter(s -> s.length() == 3)
.limit(2)
.collect(Collectors.toList());
3.distinct去重 这个方法也类似MySQL中的distinct函数
List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc","aaa"));
List<String> stringList = list.stream()
.filter(s -> s.length() == 3)
.limit(2)
.distinct()
.collect(Collectors.toList());
4.skip跳过方法
需要注意的是这里是跳过前N个元素,像下面这个例子,第一步是筛选出来字符串长度为三的字符串,第二步是获取结果中的前五条,第三步是跳过结果中的前两条
List<String> list = new ArrayList<>(Arrays.asList("aaa","bbb","ccc","aaa","ddd","eee"));
List<String> stringList = list.stream()
.filter(s -> s.length() == 3)
.limit(5)
.skip(2)
.distinct()
.collect(Collectors.toList());
5.映射 map
对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。
如:将 list 中每一个 Integer类型元素自增后转化为 String类型
//将集合中的每一个元素+1,并转为字符串
List<Integer> list = Arrays.asList(1,2,3,4,5);
List<String> result = list.stream()
.map(s->String.valueOf(++s))
.collect(Collectors.toList());
//统计集合中>3的元素数量
int result = list.stream().filter(s -> s > 3).count();
6. 合并多个流 flatMap
之前工作中用到过,写了一篇博客。可以参考jdk8 stream流合并list
7.匹配元素
①是否匹配任一元素:anyMatch
anyMatch用于判断流中是否存在至少一个元素满足指定的条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
//判断流中是否含有>10的项
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
.anyMatch(x->x>10);
② 是否匹配所有元素:allMatch
allMatch用于判断流中的所有元素是否都满足指定条件,这个判断条件通过Lambda表达式传递给anyMatch,执行结果为boolean类型。
//判断流中是否全部>5
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
.allMatch(x->x>5);
③ 是否未匹配所有元素:noneMatch
noneMatch与allMatch恰恰相反,它用于判断流中的所有元素是否都不满足指定条件:
//判断流中是否 全部不满足 >5
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
boolean result = list.stream()
.noneMatch(x->x>5);
8.sorted排序
默认采用升序排列。
List<Integer> list = new ArrayList<>(Arrays.asList(1,3,5,4,2,7));
List<Integer> integerList = list.stream()
//默认排序采用升序排列
.sorted()
.collect(Collectors.toList());
System.out.println("integerList.toString() = " + integerList.toString());
List<Integer> integerList1 = list.stream()
//默认排序采用升序排列
.sorted((t1, t2) -> t2-t1)
.collect(Collectors.toList());
System.out.println("integerList1.toString() = " + integerList1.toString());
输出如下:
9. 归约统计
归约是将集合中的所有元素经过指定运算,折叠成一个元素输出,如:求最值、平均数等,这些操作都是将一个集合的元素折叠成一个元素输出;
使用基于数据流的方式,将流装载相应的 SummaryStatistics 来进行归约计算,可以实现更多的操作;
List<Integer> list = new ArrayList<>(Arrays.asList(1,3,5,4,2,7));
IntSummaryStatistics statistics = list.stream().mapToInt(x -> x).summaryStatistics();
//获取平均值
double average = statistics.getAverage();
//获取总个数
long count = statistics.getCount();
//获取最小值
int min = statistics.getMin();
//获取最大值
int max = statistics.getMax();
//获取总和
long sum = statistics.getSum();
10.parallelStream 并行处理(建议不用,可以自己定义线程池去操作,避免OOM异常)
parallelStream是流 进行并行处理的替代方案,parallelStream 的底层实现为 ForkJoin 线程池,JDK8 为为 parallelStream 提供了一个通用的线程池,对于代码实际运行时的 parallelStream 线程数量是不可控的,但是可以通过设置 JVM 运行参数 -Djava.util.concurrent.ForkJoinPool.common.parallelism=N (N为线程数量)来调整 JVM ForkJoinPool 的线程数量;
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
//get count of empty string
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();