转自原文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);