引言:几乎每个java应用都会创建和处理集合,但是集合的传统CollectionAPI处理需要写一堆代码,使用起来不尽人意还会使代码变得臃肿,这个时候java8提供的StreamAPI(内部迭代)会让一切变得简单的.java8最大的特性是引入了Lambda表达式,函数式编程,使用不可变值和函数,函数对不可变值进行处理,映射成另一个值。
1.函数式接口
使用@FunctionalInterface注解修饰的接口,代表只有一个抽象方法,用作lambda表达式的表达,函数式接口中可以有多个默认方法和静态方法。能够常见的函数式接口比如Runnable,Comparator…
2 常见的函数式接口
函数式接口 | 函数描述符 | 抽象方法 | 功能 | 示例 |
Predicate<T> | T->boolean | test(T t) | 真假判断 | Predicate<Integer> predicate = a -> a > 1; |
Consumer<T> | T->void | accept(T t) | 消费型函数 | Consumer<String> consumer = a -> System.out.println(a); |
Function<T,R> | T->R | R apply(T t) | 将T映射为R | Function<Long,Integer> function = a -> a.intValue(); |
Supplier<T> | ()->T | T get() | 生产型函数 | Supplier<String> supplier = () -> "test"; |
UnaryOperator<T> | T->T | 继承自Function<T, T> | 接收T对象,返回T对象 | UnaryOperator<Employee> unaryOperator = employee -> employee.setSalary(100); |
BinaryOperator<T> | (T,T)->T | 继承自BiFunction<T,T,T> | 接收两个T对象,返回T对象 | BinaryOperator<Integer> binaryOperator = (a, b) -> a * b; |
BiPredicate<L,R> | (L,R) -> boolean | test(T t,R r) | 接收T和R对象,返回boolean值 | BiPredicate<Integer, Integer> bi = (x, y) -> x > y; |
BiConsumer<T,U> | (T,R) ->void | void accept(T t, U u) | 接收T和U对象,不返回值 | BiConsumer<String, String> biConsumer = (x, y) -> System.out.println(x + "===" + y); |
BiFunction<T,U,R> | (T,U) ->R | R apply(T t, U u) | 接收T和U对象,返回R对象 | BiFunction<String, String, String> biFunction = (x, y) -> x + "===" + y; |
public class Demo {
public static void main(String[] args) {
// Consumer
Consumer<Integer> consumer = item -> System.out.println(item);
Arrays.asList(1, 2, 3, 4).forEach(item -> consumer.accept(item));
// Predicate
Predicate<Integer> predicate = item -> item > 175;
boolean test = predicate.test(180);
// Function
Function<Student, String> function = Student::getName;
String name = function.apply(new Student("张三", 18));
//UnaryOperator
UnaryOperator<Boolean> unaryOperator = a -> !a;
Boolean b = unaryOperator.apply(true);
//BinaryOperator
BinaryOperator<Integer> operator = (x, y) -> x * y;
Integer result = operator.apply(1, 4);
}
@Data
static class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public Student() {
}
}
}
3.方法引用
java8中有一种lambda表达式的写法叫方法引用,方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递。在一些情况下,它显得更易读,清晰。
方法引用主要有三种
①指向静态方法的方法引用
比如Integer的parseInt方法,可以写成Integer::parseInt
②指向任意类型实例方法的方法引用
比如String的length方法,可以写成String::length,或者上面的Student::getName
③指向现有对象的实例方法的方法引用
比如有一个变量this,支持实例方法resolveData,然后就可以写成this::resolveData
4构造函数引用
构建一个新的实例对象
Supplier<Student> a1 = Student::new;
Student student = a1.get();
等价于
Supplier<Student> a1 = () -> new Student();
Student student = a1.get();
对于有参构造函数的话,适合Function接口
BiFunction<String,Integer,Student> a1 = Student::new;
Student student = a1.apply("张三",18);
5 java8的流式处理
CollectionAPI需要用户去做迭代,如foreach,成为外部迭代。相反,StreamAPI使用内部迭代,帮你做了迭代,还把得到的流值存在了某个地方,你只要给出一个函数说要干嘛就可以了。
流的操作是惰性求值,分为中间操作和终端操作,在终端操作之前,中间操作返回的都是Stream,直到遇到终端操作才计算结果,而且流只能遍历一次,而且流中的元素是按需计算的。
中间操作:
filter: 参数=== Predicate<T>, 函数描述符=== T->Boolean
map:参数=== Function<T,R>,函数描述符=== T->R
limit:截断流
sorted:参数=== Comparator<T>,函数描述符=== (T,T) -> int
distinct:去重
终端操作:
forEach:消费流中的每个元素并对其应用指定lambda,这一操作返回void
count:返回流中的元素的个数,这一操作返回long
collect:把流归约成一个集合,比如List,Map甚至Integer
总而言之,流的使用一般包括三件事:⒈一个数据源来执行一个查询⒉一个中间操作链,形成一条流的流水线⒊一个终端操作,执行流水线,并能生成结果。
①filter,筛选。
筛选出学生中成年人的学生信息
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
Predicate<Student> ageAbove18Predicate = s -> s.getAge() >= 18;
List<Student> result = students.stream().filter(ageAbove18Predicate).collect(Collectors.toList());
②map,映射
直接查看学生的年龄信息
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
Function<Student,Integer> ageFunction = Student::getAge;
List<Integer> result = students.stream().map(ageFunction).collect(Collectors.toList());
③limit,截断流
查看年龄最小的两位学生的信息
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
Comparator<Student> ageComparator = Comparator.comparing(Student::getAge);
List<Student> result = students.stream().sorted(ageComparator).limit(2).collect(Collectors.toList());
④distinct,去重
按照年龄去重
ArrayList<Student> students = Lists.newArrayList(new Student("张三1", 18),new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
List<Integer> result = students.stream().map(Student::getAge).distinct().collect(Collectors.toList());
⑤skip,跳过元素
按照学生列表顺序跳过第一个之后的剩余学生信息
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
List<Student> result = students.stream().skip(1).collect(Collectors.toList());
⑥flatMap,流的扁平化
给定单词列表["Hello","World"],想要返回["H","e","l","o","W","r","d"]
String[] stringArray = {"Hello","World"};
List<String> result = Arrays.stream(stringArray).map(w -> w.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
⑦查找和匹配,allMathc,anyMatch,nonMatch,findAny,findFirst
有些操作不需要处理整个流就能得到结果,例如,你需要对一个用and连起来的大布尔表达式求值,不管表达式有多长,只要有一个表达式为false,就可以推断出整个表达式为false,就是短路。对于流而言,某些操作不需要处理整个流就能得到结果
找出第一个数的平方能被2整除的数
List<Integer> integers = Arrays.asList(1, 2, 3, 5, 6);
Integer first = integers.stream().map(x -> x * x).filter(x -> x % 2 == 0).findFirst().orElse(-1);
对于findFirst和findAny,主要是针对并行,如果你不关心返回的元素是哪个,请使用findAny
⑧归约
List<Integer> integers = Arrays.asList(1, 2, 3, 5, 6);
Integer reduce = integers.stream().reduce(0, Integer::sum);
第一个参数是初始值,第二个参数是BinaryOperator,接收两个T类型数据,返回T类型数据
6 原始类型流特化
java8引入三个原始类型特化流接口解决暗含的装箱操作,分别为IntStream,DoubleStream,LongStream,分别将流中的元素特化为int,long,double
映射到数据流
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
int sum = students.stream().mapToInt(Student::getAge).sum();
转换回对象流
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
IntStream intStream = students.stream().mapToInt(Student::getAge);
Stream<Integer> boxed = intStream.boxed();
7 无限流
StreamAPI提供了两个静态方法来从函数生成流,Stream.iterate和Stream.generate,这两个操作可以创建所谓的无限流。
迭代:
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
接收一个0的初始值,使用表达式n+2在前一个元素的基础上加上2生成新的元素作为下一个元素的新值。
著名的斐波那契数列生成:
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]}).limit(10).map(t -> t[0]).forEach(System.out::println);
生成:
与iterare类似,generate方法也可以按需生成一个无限流
Stream.generate(Math::random).limit(5).forEach(System.out::println);
generate接收一个Supplier参数,故斐波那契也可以使用generate生成
IntSupplier intSupplier = new IntSupplier() {
private int previous = 0;
private int next = 1;
@Override
public int getAsInt() {
int oldPrevious = previous;
int nextValue = previous + next;
previous = next;
next = nextValue;
return oldPrevious;
}
};
IntStream.generate(intSupplier).limit(10).forEach(System.out::println);
8 收集数据
①查找流中最大值和最小值
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
Optional<Student> max1 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));
Optional<Student> max2 = students.stream().max(Comparator.comparing(Student::getAge));
Optional<Student> min1 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));
Optional<Student> min2 = students.stream().min(Comparator.comparing(Student::getAge));
②汇总
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19));
IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge));
打印出的结果是
{"average":18.0,"count":3,"max":19,"min":17,"sum":54}
IntSummaryStatistics一次可以得到总和,平均值,最大值,最小值
③连接字符串
String collect1 = students.stream().map(Student::getName).collect(Collectors.joining());
String collect2 = students.stream().map(Student::getName).collect(Collectors.joining(","));
String collect3 = students.stream().map(Student::getName).collect(Collectors.joining(",","[","]"));
joining接收CharSequence delimiter分隔符和前缀、后缀。
④分组
按照年龄分组
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19), new Student("马六", 19));
Map<Integer, List<Student>> collect3 = students.stream().collect(Collectors.groupingBy(Student::getAge));
结果为 {17:[{"age":17,"name":"李四"}],18:[{"age":18,"name":"张三"}],19:[{"age":19,"name":"王五"},{"age":19,"name":"马六"}]}
还可以多级分组。
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19), new Student("马六", 19), new Student("马六", 19));
Map<Integer, Map<String, List<Student>>> collect1 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.groupingBy(Student::getName)));
⑤分区
学生年龄大于18和小于等于18的分区
ArrayList<Student> students = Lists.newArrayList(new Student("张三", 18), new Student("李四", 17), new Student("王五", 19), new Student("马六", 19), new Student("马六", 19));
Map<Boolean, List<Student>> collect1 = students.stream().collect(Collectors.partitioningBy(item -> item.getAge() > 18));
结果 {false:[{"age":18,"name":"张三"},{"age":17,"name":"李四"}],true:[{"age":19,"name":"王五"},{"age":19,"name":"马六"},{"age":19,"name":"马六"}]}