Java8新特性之Stream API学习
Stream API 介绍
Java8中有两个最为重要的改变。第一个是Lambda表达式;另一个就是Stream API 。
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API为我们提供了一种高效且易于使用的处理数据的方式,代码简洁,优雅。
注意:
- Stream 不会存储元素
- Stream 不会改变数据源对象。而是返回一个持有结果的新的Stream
- Stream 操作是延迟执行的。
操作Stream的三个步骤
- 创建Stream:获取数据源的流
- 中间操作:一系列针对数据源数据的操作(操作连)
- 终止操作:执行中间的所有操作,并产生结果
实体类People
- 方便后续程序演示(多处用到)
public class People { private String name; private Integer age; private Integer salary; public People() {} public People(String name) { this.name = name; } public People(String name, Integer age) { this.name = name; this.age = age; } public People(String name, Integer age, Integer salary) { this.name = name; this.age = age; this.salary = salary; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getSalary() { return salary; } public void setSalary(Integer salary) { this.salary = salary; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((age == null) ? 0 : age.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((salary == null) ? 0 : salary.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; People other = (People) obj; if (age == null) { if (other.age != null) return false; } else if (!age.equals(other.age)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (salary == null) { if (other.salary != null) return false; } else if (!salary.equals(other.salary)) return false; return true; } @Override public String toString() { return "People [name=" + name + ", age=" + age + ", salary=" + salary + "]"; } }
- 后续程序中用到的List ps(放入测试类即可)
private List<People> ps = Arrays.asList( new People("Tom", 20, 5000), new People("Tony", 25, 6000), new People("Jerry", 30, 7000), new People("Lucy", 35, 8000), new People("John", 40, 5500), new People("Tom", 20, 5000) );
Tip:下面的例子中有很多地方用到了函数式接口和Lambda表达式中的知识,如方法引用等。可参考函数式接口与Lambda表达式
1. 创建Stream的方法
@Test public void test1(){ //方法1.通过单列集合类的stream()方法或parallelStream()方法获取Stream List<String> list = new ArrayList<>(); Stream<String> stream1 = list.stream(); Stream<String> stream = list.parallelStream();//获取并行流 //方法2.通过Arrays中的静态方法stream()获取数组流 People[] peoples = new People[10]; Stream<People> stream2 = Arrays.stream(peoples); //方法3.通过Stream接口的静态方法of(T... values) Stream<String> stream3 = Stream.of("Stream", "API", "test"); //方法4.创建无限流 //4.1 通过迭代获取无限流 Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2); //从0开始,每一次迭代+2, 一共迭代10次,并每次打印输出 stream4.limit(10).forEach(System.out::println);//forEach()是一个终止操作 //4.2 通过生成 //生成5个随机小数 Stream.generate(() -> Math.random()) .limit(5) .forEach(System.out::println); }
2. 中间操作
2.1 筛选与切片
- filter():过滤。接收Lambda,从流中排除某些元素,获得一个新流
Stream<T> filter(Predicate<? super T> predicate);
@Test public void test2(){ ps.stream() .filter((p) -> p.getAge() > 30) .forEach(System.out::println); }
- limit():截断流,使其元素不超过给定数量
Stream<T> limit(long maxSize);
@Test public void test3(){ ps.stream() .filter((p) -> { System.out.println("短路-> " + p.getAge()); return p.getAge() >= 25; }) .limit(2) .forEach(System.out::println); } //执行结果: 短路-> 20 短路-> 25 People [name=Tony, age=25, salary=6000] 短路-> 30 People [name=Jerry, age=30, salary=7000]
可以看到当得到2个符合要求的元素时,后面的元素将不再进行遍历
- skip(n):跳过前n个元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流,与limit(n)互补
Stream<T> skip(long n);
@Test public void test4(){ ps.stream() .filter((p) -> p.getAge >= 25) .skip(2) .forEach(System.out::println); }
- distinct():去重。通过流中元素的hashCode()方法与equals()方法去重,所以需要重写这两个方法
Stream<T> distinct();
@Test public void test5(){ ps.stream() .distinct() .forEach(System.out::println); }
- 注意:多个中间操作可以连起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理。看如下代码:
@Test public void test(){ ps.stream() .filter((p) -> { System.out.println("Stream API 的中间操作"); return p.getAge() >= 25; }); } //上述代码如果运行,将看不到任何结果。 //但如果给流加一个终止操作: @Test public void test(){ ps.stream() .filter((p) -> { System.out.println("Stream API 的中间操作"); return p.getAge() >= 25; }) .forEach(System.out::println); } //将会得到结果
2.2 映射
- map():接收一个函数作为参数,流中的每个元素都将应用到这个函数上,并通过这个函数返回一个新的元素
<R> Stream<R> map(Function<? super T, ? extends R> mapper); //Function接口传入T类型的参 数,返回R类型或R类型子类的结果,而map最终返回一个R类型的流
@Test public void test6(){ ps.stream() .map(People::getName)//方法引用->类::实例方法 .forEach(System.out::println);//方法引用->实例::实例方法 } //执行结果:将每个People对象的名字提取了出来 Tom Tony Jerry Lucy John Tom
- flatMap():接收一个函数作为参数,将流中的每个元素通过该函数转换为另一个流,然后把所有的流连接成一个流
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); //Function接口传入T类型的参数,返回一个R类型或是R类型的子类的流,但map最后只返回一个R类型的流。据说是将中间产生的流合并为一个流了
@Test public void test7(){ List<String> list = Arrays.asList("abc", "de", "fg"); Stream<Character> stream = list.stream() .flatMap(StreamApiTest::StringToCharacter); stream.forEach(System.out::println); } public static Stream<Character> StringToCharacter(String str){ List<Character> list = new ArrayList<>(str.length()); for(char ch : str.toCharArray()){ list.add(ch); } return list.stream(); } //执行结果:a b c d e f g
2.3 排序
- sorted():自然排序(根据Comparable接口中的compareTo方法)
@Test public void test8(){ List<String> list = Arrays.asList("tom", "lucy", "jerry", "rose", "bob"); list.stream() .sorted() .forEach(System.out::println); }
- sorted(Comparator com) :定制排序
Stream<T> sorted(Comparator<? super T> comparator);
@Test public void test9(){ ps.stream() .sorted((x, y) -> { if(Integer.compare(x.getAge(), y.getAge()) == 0){ return -Double.compare(x.getSalary(), y.getSalary()); } return -Integer.compare(x.getAge(), y.getAge()); }) .forEach(System.out::println); }
3. 终止操作
3.1 查找与匹配
- allMatch(): 检查是否匹配所有元素,返回一个boolean值
boolean allMatch(Predicate<? super T> predicate);
@Test public void test10(){ boolean b1 = ps.stream() .allMatch((p) -> p.getAge() > 25);//看是不是所有对象的年龄都大于25 System.out.println(b1); } //执行结果:false
- anyMatch():检查流中是否至少有一个元素满足条件,返回boolean值
boolean anyMatch(Predicate<? super T> predicate);
@Test public void test11(){ boolean b1 = ps.stream() .anyMatch((p) -> p.getAge() > 25); System.out.println(b1); } //执行结果:true
- noneMatch():检查流中是否所有元素都不匹配,返回boolean值
boolean noneMatch(Predicate<? super T> predicate);
@Test public void test12(){ boolean b1 = ps.stream() .noneMatch((p) -> p.getAge() < 20); System.out.println(b1); } //执行结果:true (没有一个年龄是小于20的,所有都不匹配,返回true)
final class Optional<T>
:一个容器类,可以有效的避免空指针异常。stream api中对于结果无法确定的情况,将会把结果封装在这个容器类中。其中两个重要的方法如下:T get():若值不为null,则可以得到该值
public T get() { if (value == null) { throw new NoSuchElementException("No value present"); } return value; }
T orElse(T other) :如果封装的value为null,可以通过此方法避免空指针
public T orElse(T other) { return value != null ? value : other; }
findFirst():返回流中第一个元素,返回类型为Optional,防止空指针异常
Optional<T> findFirst();
@Test public void test13(){ Optional<People> op = ps.stream() .sorted((x, y) -> -Integer.compare(x.getAge(), y.getAge())) .findFirst(); System.out.println(op.get()); }
- findAny():返回当前流中的任意一个元素
Optional<T> findAny();
@Test public void test14(){ Optional<People> op = ps.stream().findAny(); System.out.println(op.get()); }
- count():返回流中元素总个数
long count();
@Test public void test15(){ Long cnt = ps.stream() .filter((p) -> p.getAge() > 25) .count(); System.out.println(cnt); }
- max():返回流中最大的元素,通过自定义规则比较
Optional<T> max(Comparator<? super T> comparator);
@Test public void test16(){ Optional<People> op = ps.stream() .max((x, y) -> Double.compare(x.getSalary(), y.getSalary())); System.out.println(op.get()); }//得到薪水最高的人
- min():返回流中最小的元素,通过自定义规则比较
Optional<T> min(Comparator<? super T> comparator);
@Test public void test17(){ Optional<Integer> op = ps.stream() .map(People::getSalary) .min(Integer::compare); System.out.println(op.get()); }//先取出每个人的薪水,然后得到最低的薪水
- forEach():内部迭代
3.2 归约
reduce(T identity, BinaryOperator) / reduce(BinaryOperator):将流中元素反复结合起来,得到一个值。注意这两种使用方法的返回值
- reduce(T identity, BinaryOperator)
T reduce(T identity, BinaryOperator<T> accumulator); //identity: 累加元素的初始值 //public interface BinaryOperator<T> extends BiFunction<T,T,T>,所以需要实现BiFunction接口中的R apply(T t, U u);方法,即一个二元运算
@Test public void test18(){ List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6 ,7, 8, 9, 10); Integer sum = list.stream() .reduce(0, (x, y) -> x + y); System.out.println(sum); }//执行结果:55
- reduce(BinaryOperator)
Optional<T> reduce(BinaryOperator<T> accumulator);
@Test public void test19(){ Optional<Integer> op = ps.stream() .map(People::getSalary) .reduce((x, y) -> x + y); System.out.println(op.get()); }//将所有人的薪水累加,因为没有初始值,所有返回Optional
3.3 收集
将流转换为其他形式。接受一个Collector接口的实现,用于流中元素做汇总
- 可以将流中元素收集进入List,Set这一系列单列集合中
@Test public void test20(){ //将名字提取出来并收集到一个list中 List<String> list = ps.stream() .map(People::getName) .collect(Collectors.toList()); list.forEach(System.out::println); System.out.println("--------------------------"); //将名字提取出来放入set中 Set<String> set = ps.stream() .map(People::getName) .collect(Collectors.toSet()); set.forEach(System.out::println); System.out.println("--------------------------"); //将年龄提取出来放入hashset中 HashSet<Integer> hashSet = ps.stream().map(People::getAge) .collect(Collectors.toCollection(() -> new HashSet<>())); hashSet.forEach(System.out::println); }
如果想将元素放入特定的集合,如HashSet,LinkedHashSet等,就需要使用Collectors中的toCollection()方法,传入一个Supplier接口的实现即可。
- 可以收集为Map
@Test public void test21() { List<String> list = Arrays.asList("abc", "def", "g"); //将每个元素的小写作为key,大写作为value Map<String, String> map = list.stream() .collect(Collectors.toMap(s -> s, String::toUpperCase)); map.forEach((x, y) -> System.out.println(x + "->" + y)); }
- 求流中的元素的总数、平均值、总和、最大值、最小值
@Test public void test22() { //总数(过滤后元素的总数) Long cnt = ps.stream().filter((p) -> p.getAge() > 30) .collect(Collectors.counting()); System.out.println(cnt); //平均值(求工资的平均值) Double salary = ps.stream().collect(Collectors.averagingInt(People::getSalary)); System.out.println(salary); //总和(求工资总和) Integer sum = ps.stream().collect(Collectors.summingInt(People::getSalary)); System.out.println(sum); //最大值(求年龄最大的员工) Optional<People> p = ps.stream() .collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge()))); System.out.println(p.get()); //最小值(求最小的工资) Optional<Integer> min = ps.stream() .map(People::getSalary) .collect(Collectors.minBy((x,y) -> Integer.compare(x, y))); System.out.println(min.get()); }
- 分组
@Test public void test23() { //按年龄分组 Map<Integer, List<People>> map1 = ps.stream().collect(Collectors.groupingBy(People::getAge)); System.out.println(map1); //多级分组(先按年龄分组,再按薪水分组) Map<Integer, Map<String, List<People>>> map2 = ps.stream() .collect(Collectors.groupingBy(People::getAge, Collectors.groupingBy((p) -> { if(((People)p).getSalary() > 6000) { return "高薪"; } return "低薪"; }))); System.out.println(map2); }
- 分区(满足条件放在一起,true作为key,不满足条件放另一边,false作为key)
@Test public void test24() { Map<Boolean, List<People>> map = ps.stream() .collect(Collectors.partitioningBy((p) -> p.getAge() > 30)); System.out.println(map); }
- Collectors.summarizing...():另一种方式求平均值、总数、总和、最大值、最小值
@Test public void test25() { IntSummaryStatistics su = ps.stream() .collect(Collectors.summarizingInt(People::getSalary)); System.out.println(su.getAverage()); System.out.println(su.getCount()); System.out.println(su.getMax()); System.out.println(su.getMin()); System.out.println(su.getSum()); }
- Collectors.joining():连接
@Test public void test26() { String str1 = ps.stream().map(People::getName).collect(Collectors.joining()); System.out.println(str1); //TomTonyJerryLucyJohnTom String str2 = ps.stream().map(People::getName).collect(Collectors.joining(",")); System.out.println(str2); //Tom,Tony,Jerry,Lucy,John,Tom String str3 = ps.stream().map(People::getName).collect(Collectors.joining(",","==","==")); System.out.println(str3); //==Tom,Tony,Jerry,Lucy,John,Tom== }