Java8中除了引入了好用的Lambda表达式、Date API以外,另外还有一大亮点就是Stream API了,也是最值得全部Java开发人员学习的一个知识点,由于它的功能很是的强大,尤为是和前面学习的Lambda表达式、函数式接口、方法引用共同使用的时候。html
Stream流是数据渠道,用于操做数据源所生成的元素序列,即操做集合、数组等等。其中集合和stream的区别是:集合讲的是数据,而stream讲的是计算。Stream的API所有都位于java.util.stream这样一个包下,它可以帮助开发人员从更高的抽象层次上对集合进行一系列操做,就相似于使用SQL执行数据库查询。而借助java.util.stream包,咱们能够简明的声明性的表达集合,数组和其余数据源上可能的并行处理。实现从外部迭代到内部迭代的改变。它含有高效的聚合操做、大批量的数据处理,同时也内置了许多运算方式,包括筛选、排序、聚合等等。简单来讲,用Stream来操做集合——减小了代码量,加强了可读性,提升运行效率。java
而后这里要提一下:Stream和Java IO中的stream是彻底不一样概念的两个东西。本文要讲解的stream是可以对集合对象进行各类串行或并发汇集操做,而Java IO流用来处理设备之间的数据传输,它们二者大相径庭。数据库
Stream的主要特色有:
一、stream自己并不存储数据,数据是存储在对应的collection(集合)里,或者在须要的时候才生成的。
二、stream不会修改数据源,老是返回新的stream。
三、stream的操做是延迟(lazy)执行的:仅当最终的结果须要的时候才会执行,即执行到终止操做为止。数组
首先咱们先使用之前的方法来对集合进行一些操做,代码示例以下:网络
public class StreamTest { public static void main(String[] args) { //建立集合 ArrayList<String> list = new ArrayList<>(); list.add("one"); list.add("two"); list.add("three"); list.add("four"); list.add("five"); list.add("six"); //遍历数据,只遍历4个 for (int i = 0; i < list.size()-1; i++) { //过滤掉字符串——two if (!(list.get(i).contains("two"))) { System.out.println(list.get(i)); } } } }
我相信像上面这样的代码估计每一个java开发人员分分钟均可实现。但若是要求再复杂一点,代码量就会大量增长,因此再经过建立一个存储数据的集合,而后对集合进行遍历,再经过特定的条件进行筛选而且将结果输出。这样的代码中或多或少会有缺点的:
一、代码量一多,维护确定更加困难。可读性也会变差。并发
二、难以扩展,要修改成并行处理估计会花费很大的精力。app
而后下面展现了用Stream来实现相同的功能,很是的简洁,若是须要添加其余的方法也很是的方便。注:Stream中的一些方法可能暂时不清楚,这不要紧,后面都会一 一介绍它们。dom
//使用filter,limit,forEach方法 list.stream().filter(s->!s.contains("two")).limit(4).forEach(System.out::println);
最终两种方式的代码运行结果是同样的:ide
从上面使用Stream的状况下咱们能够发现是Stream直接链接成了一个串,而后将stream过滤后的结果转为集合并返回输出。而至于转成哪一种类型,这由JVM进行推断。整个程序的执行过程像是在叙述一件事,没有过多的中间变量,因此能够减少GC压力,提升效率。因此接下来开始探索Stream的功能吧。函数
Stream的原理是:将要处理的元素看作一种流,流在管道中传输,而且能够在管道的节点上处理,包括有过滤、筛选、去重、排序、汇集等。元素在管道中通过中间操做的处理,最后由终止操做获得前面处理的结果。
因此Stream的全部操做分为这三个部分:建立对象-->中间操做-->终止操做,以下图。
(1)、建立Stream:一个数据源(如:集合、数组),获取一个流。
(2)、中间操做:一个中间操做链,对数据源的数据进行处理。
(3)、终止操做(终端操做):一个终止操做,执行中间操做链,并产生结果。
①、使用Java8中被扩展的Collection接口,其中提供了两个获取流的方法:
//一、方法一:经过Collection ArrayList<String> list=new ArrayList<>(); //获取顺序流,即有顺序的获取 Stream<String> stream = list.stream(); //获取并行流,即同时取数据,无序 Stream<String> stream1 = list.parallelStream();
②、使用Java8 中Arrays的静态方法stream()获取数组流:
//二、方法二:经过数组 String str[]=new String[]{"A","B","C","D","E"}; Stream<String> stream2 = Arrays.stream(str);
③、使用Stream类自己的静态方法of(),经过显式的赋来值建立一个流,它能够接受任意数量的参数:
//三、方法三:经过Stream的静态方法of() Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5, 6);
④、建立无限流(迭代、生成)
//4.、方式四:建立无限流(迭代、生成) //迭代(须要传入一个种子,也就是起始值,而后传入一个一元操做) Stream.iterate(2, (x) -> x * 2).limit(10).forEach(System.out::println); //生成(无限产生对象) Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);
以上的四种方式学习前三中便可,第四种不经常使用。除了上面这些额外还有其余建立Stream对象的方式,如Stream.empty():建立一个空的流、Random.ints():随机数流等等。
中间操做的全部操做会返回一个新的流,但它不会修改原始的数据源,而且操做是延迟执行的(lazy),意思就是在终止操做开始的时候才中间操做才真正开始执行。
中间操做主要有如下方法:filter、limit、 skip、distinct、map (mapToInt, flatMap 等)、 sorted、 peek、 parallel、 sequential、 unordered等。
为了更好地举例,咱们先建立一个Student类:
public class Student { private int id; private String name; private int age; private String address; public Student() { } public Student(int id, String name, int age, String address) { this.id = id; this.name = name; this.age = age; this.address = address; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Student student = (Student) o; return id == student.id && age == student.age && Objects.equals(name, student.name) && Objects.equals(address, student.address); } @Override public int hashCode() { return Objects.hash(id, name, age, address); } @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}'; } }
后面的全部测试数据用会用下面这一组数据(必要时会改):
List<Student> students= Arrays.asList( new Student(1, "张三", 18, "北京"), new Student(2, "李四", 19, "上海"), new Student(3, "王五", 20, "广州"), new Student(4, "赵六", 21, "浙江"), new Student(5, "孙七", 22, "深圳") );
而后开始介绍流的中间操做的方法使用:
①、filter(Predicate<? super T> predicate):筛选:接收 Lambda表达式,表示从流中过滤某些元素。
//筛选年龄大于19岁而且住在浙江的学生 students.stream().filter(s -> s.getAge() > 19).filter(s -> "浙江".equals(s.getAddress())).forEach(System.out::println);
运行结果为:
这里咱们建立了五个学生对象,而后通过filter的筛选,筛选出年龄大于19岁而且家庭住址是在浙江的学生集合。
②、limit(long maxSize):截断:表示使其元素不超过给定数量。
//给定获取3个数据 students.stream().limit(3).forEach(System.out::println);
运行结果:
咱们只让它截断3个,每次都是从第一个数据开始截取。
③、skip(long n):跳过:表示跳过前 n 个元素。若流中元素不足 n 个,则返回一个空流。它与 limit(n)互补。
//跳过前3个数据 students.stream().skip(3).forEach(System.out::println);
运行结果:
能够发现输出的数据刚好与limit方法互补。
④、distinct():筛选:去除流中重复的元素。
这里我将第二个数据改为和第一个同样,看结果会怎样。
public static void main(String [] args) { List<Student> students= Arrays.asList( new Student(1, "张三", 18, "北京"), new Student(1, "张三", 18, "北京"), new Student(3, "王五", 20, "广州"), new Student(4, "赵六", 21, "浙江"), new Student(5, "孙七", 22, "深圳") ); //去除重复元素 students.stream().distinct().forEach(System.out::println); } }
运行结果:
能够发现相同的元素被去除了,可是注意:distinct 须要实体中重写hashCode()和 equals()方法才可使用。
⑤、map(Function<? super T, ? extends R> mapper):映射(转换):将一种类型的流转换为另一种类型的流。
为了便于理解咱们来分析一下,map函数中须要传入一个实现Function<T,R>函数式接口的对象,而该接口的抽象方法apply接收T类型,返回是R类型,因此map也能够理解为映射关系。
//lambda表达式 students.stream().map(s->s.getName()).forEach(System.out::println); //方法引用 students.stream().map(Student::getName).forEach(System.out::println);
运行结果:
上面的例子中将Student对象转换为了普通的String对象,获取Student对象的名字。
⑥、flatMap(Function<? super T, ? extends Stream<? extends R>> mapper):转换合并:将流中的每一个值都转换成另外一个流,而后把全部流组合成一个流而且返回。
它和map有点相似。flatMap在接收到Stream后,会将接收到的Stream中的每一个元素取出来放入另外一个Stream中,最后将一个包含多个元素的Stream返回。
List<Student> student1= Arrays.asList( new Student(1, "张三", 18, "北京"), new Student(2, "李四", 19, "上海") ); List<Student> student2= Arrays.asList( new Student(3, "王五", 20, "广州"), new Student(4, "赵六", 21, "浙江"), new Student(5, "孙七", 22, "深圳") ); //lambda Stream.of(student1,student2).flatMap(student -> student.stream()).forEach(System.out::println); //方法引用 Stream.of(student1,student2).flatMap(List<Student>::stream).forEach(System.out::println); //常规方法 Stream.of(student1,student2).flatMap(new Function<List<Student>, Stream<?>>() { @Override public Stream<?> apply(List<Student> students) { return students.stream(); } }).forEach(System.out::println);
运行结果都是:
由于flatMap中最后须要将每一个元素组合成一个流,因此flatMap方法形参返回了一个stream流。
若是map和flatmap还不清楚能够参考这篇博客:java8 stream流操做的flatMap(流的扁平化) 写的很清楚。
⑦、sorted():天然排序:按天然顺序排序的流元素。
这里就不用Student对象做为举例了,不然要在Student类中实现Comparable接口。
//天然排序 List<String> list = Arrays.asList("CC", "BB", "EE", "AA", "DD"); list.stream().sorted().forEach(System.out::println);
运行结果:
上面使用String中默认实现的接口自动完成排序。
⑧、sorted(Comparator<? super T> comparator):自定排序:按提供的比较符排序的流元素 。
//自定排序 students.stream().sorted((s1,s2)-> { if(s1.getId()>s2.getId()){ return s1.getId().compareTo(s2.getId()); }else{ return -s1.getAge().compareTo(s2.getAge()); } }).forEach(System.out::println);
运行结果:
上面的代码表示为先按照id进行排序,若是id相同则按照年龄降序排序,你能够将其余id改成 1测试一下。
终止操做主要有如下方法:forEach、 forEachOrdered、anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、count、min、 max、 reduce、 collect、toArray、iterator。下面只介绍一下经常使用的方法。
注意:Stream流只要进行了终止操做后,就不能再次使用了,再次使用得从新建立流。
①、void forEach(Consumer<? super T> action):遍历:接收Lambda表达式,遍历流中的全部元素。
forEach上面已经用的够多的了,这里就不说了。
②、anyMatch/allMatch/noneMatch:匹配:返回值为boolean,参数为(Predicate<? super T> predicate)。
boolean b = students.stream().allMatch((s) -> s.getAge() > 20); System.out.println("allMatch()"+"\t"+b); boolean b1 = students.stream().anyMatch((s) -> s.getAge() > 21); System.out.println("anyMatch()"+"\t"+b1); boolean b2 = students.stream().noneMatch((s) -> s.getAge() == 22); System.out.println("noMatch()"+"\t"+b2);
运行结果:
③、findFirst/findAny:查找:返回Optional<T>,无参数。
//先按id排好序 Optional<Student> first = students.stream().sorted((s1, s2) -> s1.getId().compareTo(s2.getId())).findFirst(); System.out.println("findFirst()"+"\t"+first.get()); Optional<Student> any = students.stream().filter((s)->s.getAge()>19).findAny(); System.out.println("findAny()"+"\t"+any.get());
运行结果:
④、long count():统计:返回流中元素的个数。
//统计流中数量 long count = students.stream().count(); System.out.println("count()"+"\t"+count);
运行结果:
⑤、max/min:大小值:返回Optional<T>,无参数。
//获取年龄最大值 Optional<Student> max = students.stream().max((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())); System.out.println("max()"+"\t"+max.get()); //获取年龄最小值 Optional<Student> min = students.stream().min((s1, s2) -> Integer.compare(s1.getAge(), s2.getAge())); System.out.println("min()"+"\t"+min.get());
运行结果:
⑥、reduce:归约:能够将流中元素反复结合在一块儿,获得一个值。
//累加0-10 List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Integer sum = list.stream().reduce(0, (x, y) -> x + y); //一、T reduce(T identity, BinaryOperator<T> accumulator); System.out.println("reduce1--"+sum); //二、Optional<T> reduce(BinaryOperator<T> accumulator); Optional<Integer> sum1 = list.stream().reduce(Integer::sum); System.out.println("reduce2--"+sum1.get()); Optional<Integer> sum2 = list.stream().reduce(new BinaryOperator<Integer>() { @Override public Integer apply(Integer integer1, Integer integer2) { return Integer.sum(integer1,integer2); } }); System.out.println("reduce3--"+sum2.get());
运行结果:
备注:map和reduce的链接一般称为map-reduce模式,因Google用它来进行网络搜索而出名。
Stream流与Collectors中的方法是很是好的搭档,经过组合来以更简单的方式来实现更增强大的功能,咱们利用Stream中的collect方法来实现。
collect:收集:将流转换为其余形式。它接收一个 Collector接口的实现,用于给Stream中元素作汇总的方法。
Collector接口中方法得实现决定了如何对流执行收集操做(如收集到List,Set,Map)。可是Collectors实用类提供了不少静态方法,能够方便地建立常见得收集器实例。
因此下面逐一介绍Collectors中的方法:
①、Collectors.toList():将流转换成List。
/** * Collectors.toList():将流转换成List */ List<String> list = students.stream().map(student -> student.getName()).collect(Collectors.toList()); System.out.println("toList----"+list);
②、Collectors.toSet():将流转换为Set。
/** * Collectors.toSet():将流转换成Set */ Set<String> set = students.stream().map(student -> student.getName()).collect(Collectors.toSet()); System.out.println("toSet----"+set);
③、Collectors.toCollection():将流转换为其余类型的集合。
/** * Collectors.toCollection():将流转换为其余类型的集合 */ TreeSet<String> treeSet = students.stream().map(student -> student.getName()).collect(Collectors.toCollection(TreeSet::new)); System.out.println("toCollection----"+treeSet);
④、Collectors.counting():元素个数。
/** * Collectors.counting():元素个数 */ Long aLong = students.stream().collect(Collectors.counting()); System.out.println("counting----"+aLong);
⑤、Collectors.averagingInt()、Collectors.averagingDouble()、Collectors.averagingLong():求平均数。
这三个方法均可以求平均数,不一样之处在于传入得参数类型不一样,可是返回值都为Double。
/** * Collectors.averagingInt() * Collectors.averagingDouble() * Collectors.averagingLong() * 求平均数 */ Double aDouble = students.stream().collect(Collectors.averagingInt(student -> student.getAge())); System.out.println("averagingInt()----"+aDouble); Double aDouble1 = students.stream().collect(Collectors.averagingDouble(student -> student.getAge())); System.out.println("averagingDouble()----"+aDouble1); Double aDouble2 = students.stream().collect(Collectors.averagingLong(student -> student.getAge())); System.out.println("averagingLong()----"+aDouble2);
⑥、Collectors.summingDouble()、Collectors.summingDouble()、Collectors.summingLong():求和。
这三个方法均可以求和,不一样之处在于传入得参数类型不一样,返回值为Integer, Double, Long。
/** * Collectors.summingInt() * Collectors.summingDouble() * Collectors.summingLong() * 求和 */ Integer integer = students.stream().collect(Collectors.summingInt(student -> student.getAge())); System.out.println("summingInt()----"+integer); Double aDouble3 = students.stream().collect(Collectors.summingDouble(student -> student.getAge())); System.out.println("summingDouble()----"+aDouble3); Long aLong1 = students.stream().collect(Collectors.summingLong(student -> student.getAge())); System.out.println("summingLong()----"+aLong1);
⑦、Collectors.maxBy():求最大值。
/** * Collectors.maxBy():求最大值 */ Optional<Integer> integer1 = students.stream().map(student -> student.getId()).collect(Collectors.maxBy((x, y) -> Integer.compare(x, y))); System.out.println("maxBy()----"+integer1.get());
⑧、Collectors.minBy():求最小值。
/** * Collectors.minBy():求最小值 */ Optional<Integer> integer2 = students.stream().map(student -> student.getId()).collect(Collectors.minBy((x, y) -> Integer.compare(x, y))); System.out.println("maxBy()----"+integer2.get());
⑨、Collectors.groupingBy():分组 ,返回一个map。
/** * Collectors.groupingBy():分组 ,返回一个map */ Map<Integer, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getId)); System.out.println(listMap);
其中Collectors.groupingBy()还能够实现多级分组,以下:
/** * Collectors.groupingBy():多级分组 ,返回一个map */ //先按name分组而后得出每组的学生数量,使用重载的groupingBy方法,第二个参数是分组后的操做 Map<String, Long> stringLongMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting())); System.out.println("groupingBy()多级分组----"+stringLongMap); //先按id分组而后再按年龄分组 Map<Integer, Map<Integer, List<Student>>> integerMapMap = students.stream().collect(Collectors.groupingBy(Student::getId, Collectors.groupingBy(Student::getAge))); System.out.println("groupingBy()多级分组----"+integerMapMap); //遍历 Map<Integer,List<Student>> map = new HashMap<>(); Iterator<Map.Entry<Integer, Map<Integer, List<Student>>>> iterator = integerMapMap.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<Integer, Map<Integer, List<Student>>> entry = iterator.next(); System.out.println("key="+entry.getKey()+"----value="+entry.getValue()); }
⑩、Collectors.partitioningBy():分区。按true和false分红两个区。
/** * Collectors.partitioningBy():分区,分红两个区 */ Map<Boolean, List<Student>> listMap1 = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 18)); System.out.println(listMap1);
⑪、Collectors.joining():拼接。按特定字符将数据拼接起来。
/** * Collectors.joining():拼接 */ String str = students.stream().map(student -> student.getName()).collect(Collectors.joining("---")); System.out.println(str);
Java8中的Stream提供的功能很是强大,用它们来操做集合会让咱们用更少的代码,更快的速度遍历出集合。并且在java.util.stream包下还有个类Collectors,它和stream是好很是好的搭档,经过组合来以更简单的方式来实现更增强大的功能。虽然上面介绍的都是Stream的一些基本操做,可是只要你们勤加练习就能够灵活使用,很快的能够运用到实际应用中。