转自原文https://blog.csdn.net/qq_37757008/article/details/84845556,稍作修改
1. Stream流
Stream流是Java8的新特性,它也是有关于集合的新api;Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作;
Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性;下面我们用一个例子来引入Stream流的操作:
我们写一个方法找偶数,返回一个新的list包含结果。在这里我们使用了设计模式中的策略模式,将它的处理方法拿出来,可以对它的使用方法进行主动的编写,同时我们用了一个新的模式Predicate,这个模式叫做断言模式,顾名思义就是对我们要进行的处理进行断言操作,断定它能进行的功能;
List<Integer> list = Arrays.asList(1,2,3,4,5);
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4,5);
System.out.println(exec(list, new Predicate<Integer>(){
@Override
public boolean test(Integer i) {
return i % 2 == 0;
}
}));
//System.out.println(exec(list, i -> i % 2 == 0));
}
// 设计模式中,策略模式
public static List<Integer> exec(List<Integer> list, Predicate<Integer> predicate) {
List<Integer> list2 = new ArrayList<>();
for(Integer i :list) {
if(predicate.test(i)) {
list2.add(i);
}
}
return list2;
}
其中,Predicate是一个函数接口,可以使用lambda表达式实现
可是这也是比较麻烦的 ;而且在之前我们遍历集合的时候就需要循环,或者 Iterator 迭代器来遍历;这也都是很浪费时间和空间的;那有没有一种方法,是我们不用调用方法,直接用语句来获得所有的偶数呢?这个时候我们就需要用到jdk1.8的新特性,Stream流,将数组元素变为一条数据流,然后对这个数据流进行操作,过滤或者收集想要的数据;
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
2. Stream类的方法
-
filter过滤器
Stream<T> filter(Predicate<? super T> predicate)
这个过滤器的方法就是对流中的数据一个一个的进行筛选,看看它是不是符合规则,如果不符合就拦截住,也就是舍弃,如果符合要求,则让他通过,进行下一步的操作,说白了就像一个滤网一样,我们可以设置过滤的规则,然后它对数据流进行筛选;
比如上面的例子来说我们就可以用这样的方法;
List<Integer> list = Arrays.asList(1,2,3,4,5,6); List<Integer> stream = list.stream().filter( i -> i%2 == 0).collect(Collectors.toList()); System.out.print(stream);
我们可以看到filter的参数就是一个Predicae<>断言;也就是说我们可以给它里面传一个lambda表达式,就是过滤的规则; 同时它返回的又是一个Stream流对象,这时我们又可以用它的collect(收集器),对过滤下来的数据收集起来,把他存入一个List集合;然后这时候我们输出得到的这个集合,就是我们收集的数据;
-
collect 归约
<R> R collect(Supplier<R> supplier,BiConsumer<R,? super T> accumulator,BiConsumer<R,R> combiner) <R, A> R collect(Collector<? super T, A, R> collector);
将Stream还原成第二种形式是第一种形式的压缩版,其中Collecotor可以通过Collectors这一工具类实现。
-
map 映射
<R> Stream<R> map(Function<? super T,? extends R> mapper)
映射的, lambda把原有的元素转换为另一个元素, 不会改变个数;上面的filter过滤器就是给把我们的数据按我们的规则筛选出来,那map()方法就是将我们的元素按照规则映射成另外的元素;比如我们要得到上面的list集合中的所有元素的2倍的集合,这时候我们就需要用到map()方法;
List<Integer> list4 = Arrays.asList(1, 2, 3, 4, 5, 6); List<Integer> collect = list4.stream().map(i -> i * 2).collect(Collectors.toList()); System.out.println(collect);
其中Function也是个函数接口
-
flatMap 扁平化映射
Returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element.
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
个人理解,将流中每个元素再次展开为流。比如我们现在定义了一个list集合,它的元素是字符串数组;如果我们想将它里面的每个字符串提取出来,组成一个新的集合,按照原来的方法我们就需要两重遍历,先遍历集合,后遍历数组,得到每一个字符串元素,再新建一个集合,将它们存进去;
List<String[]> list = new ArrayList<>(); list.add(new String[]{"张三", "李四"}); list.add(new String[]{"王五", "张三"}); list.add(new String[]{"钱七", "周八"}); List<String> list2 = new ArrayList<>(); for (String[] strings : list) { for (String string : strings) { list2.add(string); } } System.out.println(list2);
使用flatMap简化该过程
List<String> list3 = list.stream().flatMap( s -> Arrays.stream(s)).collect(Collectors.toList());
-
forEach 遍历流
Performs an action for each element of this stream.
void forEach(Consumer<? super T> action)
接收一个Consumer接口,该接口也是个函数接口
list3.stream().forEach(a-> { System.out.println(a); } );
-
Map类的遍历,虽然不属于流的范畴,但是也是JDK1.8中提供的一种更优雅的Map遍历的方式
Map<String, String> map = new HashMap<>(); map.put("a", "张"); map.put("b", "李"); map.forEach( (key, value) -> { System.out.println("key:" +key + " value:" + value); } );
该方法接收一个BiConsumer接口。
3. 流相关函数接口
-
Predicate 断言接口 对应的lambda 一个参数,返回结果是boolean (a) -> { return true|false; }
-
BiPredicate 双参数断言 对应的lambda 两个参数,返回结果是boolean (a, b) -> { return true|false; }
-
Function 函数接口 对应的lambda 一个参数,一个返回结果,参数和返回结果的类型可以不一样
-
BiFunction 双参数函数接口 两个参数,一个结果 (a, b) -> { 根据ab返回一个结果}
-
Consumer 消费接口 一个参数 没有结果 (a) -> { 不需要return }
-
BiConsumer 双参数消费接口 两个参数,没有结果 (a,b) -> { 不需要return }
-
Supplier 生产者接口 没有参数,返回一个结果 () -> {return 结果}
4. 例子
定义一个学生类
public class Student {
private String name;
private String sex;
private String city;
public Student(String name, String sex, String city) {
this.name = name;
this.sex = sex;
this.city = city;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", city='" + city + '\'' +
'}';
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
然后我们定义一个集合存放学生类的对象
List<Student> students = Arrays.asList(
new Student("zhang", "男", "西安"),
new Student("li", "男", "西安"),
new Student("wang", "女", "北京"),
new Student("zhao", "女", "上海"),
new Student("zhou", "男", "北京")
);
当上述List按性别分组
//按照性别分组
Map<String, List<Student>> map3 = students.stream().collect(
Collectors.groupingBy( s -> s.getSex() ));
System.out.println(map3);
按所在城市分组
//按照所在城市分组
Map<String, List<Student>> map4 = students.stream().collect(
Collectors.groupingBy( s -> s.getCity()));
System.out.println(map4);