1.Lambda 表达式:
Lambda 表达式将函数当成参数传递给某个方法,或者把代码本身当作数据处理;
语法格式:
1.1.用逗号分隔的参数列表
1.2.-> 符号
1.3.和语句块组成
演示
//1 完整的格式
Arrays.asList("a", "b", "c" ,"d").forEach((x) -> {
System.out.println(x);
});
//语句块只有一句时,可省略{}
Arrays.asList("a", "b", "c" ,"d").forEach((x) -> System.out.println(x));
//参数只有一个时,可以省略()
Arrays.asList("a", "b", "c" ,"d").forEach(x -> System.out.println(x));
//方法引用,还可以更加简化
Arrays.asList("a", "b", "c" ,"d").forEach(System.out::println);
//等价于
List<String> list = Arrays.asList("a", "b", "c" ,"d");
for(String e : list){
System.out.println(e);
}
2.方法引用:
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。
方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很
多复杂的模板代码。
演示
package com.algorithm;
import java.util.function.Supplier;
public class Student {
private String studentNo; //学号
private String name;
private Integer age;
private String classGrade;
private Integer score;
public Student(){}
public Student(String studentNo,String name,Integer age,String classGrade,Integer score){
this.studentNo = studentNo;
this.name = name;
this.age = age;
this.classGrade = classGrade;
this.score = score;
}
public static Student create(Supplier<Student> supplier) {
return supplier.get();
}
public static void printScore(Student student) {
System.out.println( "score = " + student.getScore() );
}
public void printStudent() {
System.out.println(this.toString() );
}
public String getStudentNo() {
return studentNo;
}
public void setStudentNo(String studentNo) {
this.studentNo = studentNo;
}
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 String getClassGrade() {
return classGrade;
}
public void setClassGrade(String classGrade) {
this.classGrade = classGrade;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
2.1.构造器引用 语法是Class::new (注意:这个构造器没有参数)
Student student1 = Student.create(Student::new);
//等价于
Student student2 = Student.create(() -> new Student());
2.2.静态方法引用 语法是Class::static_method(注意:这个方法接受一个Student类型的参数)
//default void forEach(Consumer<? super T> action) {}
students.forEach(Student::printScore);
//等价于
students.forEach(s -> Student.printScore(s));
//2.3.成员方法的引用 语法是Class::method (注意,这个方法没有定义入参)
students.forEach(Student::printStudent);
//等价于
students.forEach(s -> s.printStudent());
将1到n的数字放入到长度为n+1的数组中,最好情况,假如将1到n的数组依次放入数
组中,则数组最后一个槽位就会为空,要填满整个数组则要从1到n的数字中选一个填
入到最后。所以必然会有重复的数字出现。
3.函数式接口:
1.接口中只能有一个接口方法
2.可以有静态方法和默认方法
3.使用 @FunctionalInterface 标记
4.默认方法可以被覆写
我们基本不需要定义自己的函数式接口,Java8 已经给我们提供了大量的默认函数式接
口,rt.jar 包的 java.util.function目录下可以看到所有默认的函数式接口,大致分为几类.
3.1.Function<T, R> T 作为输入,返回的 R 作为输出
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Function<T, R> {
/**
* 该方法是接口的核心方法,定义该函数的业务逻辑
*/
R apply(T t);
/**
* 设置在业务逻辑方法(apply)执行之前的预处理,类似于前置切面,当通过compose方式设置了前置方法,在
* 调用apply时,会先执行compose设置的方法,因此compose设置的方法的入参类型需要和apply的入参类型一
* 致,而compose设置的方法的出参将作为apply方法的入参,所以compose设置的方法的出参类型也要和apply
* 方法的入参一致。
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* 该方法和compose相反,设置在业务逻辑方法(apply)执行之后的处理逻辑,类似于后置切面,当通过andThen
* 方式设置了后置方法,在调用apply执行完成之后,会执行andThen设置的方法,因此andThen设置的方法的入
* 参类型需要和apply的出参类型一致,而andThen方法的出参将作为apply方法的最终出参,所以andThen的出
* 参类型也要和apply方法的出参一致。
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* identity方法会返回一个不进行任何处理的Function,即输出与输入值相等;
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
演示
//3.1.Function<T,R> T 作为输入,返回的 R 作为输出
Function<Student,Integer> f0 = (s) -> {
return s.getAge();
};
//简化后
Function<Student,Integer> f1 = s -> s.getAge();
//apply方法
Integer result1 = f1.apply(new Student("5","student5",13,"3",100));
System.out.println(result1);
Function<Integer, Integer> f2 = f -> f + 2;
Function<Integer, Integer> f3 = f -> f * 2;
/**
* compose方法
* 表示在执行f2时,先执行f3,并且执行f3时使用f3的输出作为输入
* 等同于:
* Integer x = f3.apply(2);
* System.out.println(f2.apply(x));
*/
Integer result2 = f2.compose(f3).apply(2);
System.out.println(result2);
/**
* andThen方法
* 表示执行f2的apply方法后使用其返回的值当作输入再执行f3的apply方法
* 等同于:
* Integer x = f2.apply(2);
* System.out.println(f3.apply(x));
*/
Integer result3 = f2.andThen(f3).apply(2);
System.out.println(result3);
/**
* identity方法会返回一个不进行任何处理的Function,即输出与输入值相等;
*/
Object result4 = Function.identity().apply("input");
System.out.println(result4);
3.2.Predicate<T> T 作为输入 ,返回 boolean 值的输出
该接口用来定义逻辑判断的函数,类似于单元测试的Assert。通过实现Predicate来定义
测试的标准,返回是否测试通过,核心方法就是test,传入待测试的值,返回测试结果
(boolean类型)
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
/**
* 测试方法,也是该接口的核心方法,根据测试的规则,判断输入的参数是否测试通过
*/
boolean test(T t);
/**
* 调用当前Predicate的test方法之后再去调用other的test方法,相当于进行两次判断
* 并且的意思。两个条件都满足返回true
*/
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/**
* 对当前判断进行取非操作
*/
default Predicate<T> negate() {
return (t) -> !test(t);
}
/**
* 对当前判断进行取或操作
*/
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
/**
* 对当前操作进行取等操作,注意是一个静态方法
* 该方法主要是用作将一个equals判断封装成函数对象,简化Predicate实例的创建
*/
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
演示
List<Student> students = new ArrayList<>();
students.add(new Student("1","student1",10,"1",80));
students.add(new Student("2","student2",11,"1",90));
students.add(new Student("3","student3",12,"2",85));
students.add(new Student("4","student4",12,"2",90));
//3.2.Predicate<T> T 作为输入 ,返回 boolean 值的输出
//test方法
Predicate<Integer> predicate1 = x -> x > 0;
predicate1.test(30);
//and方法
Predicate<Integer> predicate2 = x -> x > 1;
predicate1.and(predicate2).test(30);//两者条件均满足返回true
//一般简化为
predicate1.and(x -> x > 1).test(30);
//or方法
predicate1.or(predicate2).test(30);
//一般简化为
predicate1.or(x -> x > 1).test(30);
//negate方法
predicate1.negate().test(30);
//isEqual方法
Predicate<Integer> predicate3 = Predicate.isEqual(30);
predicate3.test(30);
//对空对象的判断
Predicate<Object> isNull = Predicate.isEqual(null);
isNull.test(null);
//Stream<T> filter(Predicate<? super T> predicate);
Predicate<Student> predicate4 = student -> student.getAge() > 10;
//filter方法的入参就是Predicate
List<Student> list1 = students.stream().filter(predicate4).collect(Collectors.toList());//获取年龄大于10的对象
List<Student> list2 = students.stream().filter( s-> s.getAge() > 10).collect(Collectors.toList());//一般不预定义
3.3.Consumer<T> T 作为输入 ,没有输出
顾名思义,实现该接口的类主要是用来消费数据,但是不返回结果,因此,在该接口的
内部可以通过异步来实现业务的处理,而不影响数据的消费。该接口只支持一个参数。
该接口和Function接口的主要区别是没有返回值。
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
/**
* 该方法是接口的核心方法,定义该函数的业务逻辑
*/
void accept(T t);
/**
* 设置在业务逻辑方法(accept)执行之后的处理逻辑,类似于后置切面,当通过andThen方式设置了后置方法,
* 在调用accept执行完成之后,会执行andThen设置的方法。
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
演示
//3.3.Consumer<T> T 作为输入 ,没有输出
Consumer<String> consumer1 = str -> System.out.println("str : " + str);
Consumer<String> consumer2 = str -> System.out.println(str + "hello world");
consumer1.accept("java,");
consumer1.andThen(consumer2).accept("java,");
consumer1.andThen(s -> System.out.println(s + "hello world")).accept("java,");
3.4.Supplier<R> 没有输入 , R 作为输出
顾名思义,该接口为声明一个供应商,用来获取指定泛型的实例,类似于工厂,执行该
接口的获取实例方法(get)不支持接收参数。
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
/**
* 调用get方法返回一个T类型对象
*/
T get();
}
演示
//3.4.Supplier<R> 没有输入 , R 作为输出
Supplier<String> supplier1 = () -> { return "abc";};
supplier1.get();
//简化后
Supplier<String> supplier2= () -> "abc";
Supplier<String> supplier3 = String::new;
System.out.println(supplier3.get());
Supplier<Student> supplier4 = () -> new Student("5","student5",13,"3",100);
supplier4.get();
前面演示几种常用的函数式接口,其他接口操作类似
3.5. BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
3.6. BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
3.7. BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
3.8. BiPredicate<T,U> 代表了一个两个参数的boolean值方法
3.9. BooleanSupplier 代表了boolean值结果的提供方
3.10. DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
3.11. DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。
3.12. DoubleFunction<R> 代表接受一个double值参数的方法,并且返回结果
3.13. DoublePredicate 代表一个拥有double值参数的boolean值方法
3.14. DoubleSupplier 代表一个double值结构的提供方
3.15. DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。
3.16. DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
3.17. DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
3.18. IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。
3.19. IntConsumer 接受一个int类型的输入参数,无返回值 。
3.20. IntFunction<R>接受一个int类型输入参数,返回一个结果 。
…
4.Streams:
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素
进行操作,比如:筛选、排序、聚合等。
特点:
1 . 不是数据结构,不会保存数据。而是按照特定的规则对数据进行计算,一般会输出
结果。
2. 不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。通常情况下会
产生一个新的集合或一个值。
3. 延迟执行特点,只有调用终端操作时,中间操作才会执行。
stream(顺序流)和parallelStream(并行流)的区分:
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多
线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。如果流中
的数据量足够大,并行流可以加快处速度。
stream使用分为中间操作和终端操作:
中间操作:filter,map,collection,sorted…
终端操作:foreach,find,match,reduce,max,min,count…
4.1 Stream的创建
//4.1.1.调用java.util.Collection.stream() 方法创建流。
List<String> array = new ArrayList<>();
Stream<String> stream = array.stream();// 创建一个顺序流
Stream<String> parallelStream = array.parallelStream();// 创建一个并行流
//4.1.2.调用java.util.Arrays.stream(T[] array)方法创建流
int[] intArray = {1,2,3};
Integer[] integerArray = new Integer[3];
IntStream intStream = Arrays.stream(intArray);
Stream IntegerStream = Arrays.stream(integerArray);
//4.1.3.调用Stream的静态方法:of()、iterate()、generate() 创建流
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5, 6);//of()
Stream<Integer> s2 = Stream.iterate(0, x -> x + 3).limit(6);//iterate()
s2.forEach(System.out::println); // 0 2 4 6 8 10
Stream<Double> s3 = Stream.generate(Math::random).limit(3);
4.2 流的筛选与切片
4.2.1 filter:过滤流中的某些元素
4.2.2 limit(n):获取n个元素
4.2.3 skip(n):跳过n元素,配合limit(n)可实现分页
4.2.4 distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
Stream<Integer> s4 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 8);
Stream<Integer> s5 = s4.filter(s -> s > 4)//5,6,7,8,8
.distinct() //5,6,7,8
.skip(2) //7,8
.limit(1); //7
s5.forEach(System.out::println);
4.3 映射
4.3.1 map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个
新的元素。
4.3.2 flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有
流连接成一个流。
List<String> list3 = Arrays.asList("a,b,c", "1,2,3");
Stream<String> s6 = list3.stream().map(s -> s.replaceAll(",", ""));
s6.forEach(System.out::println); // abc 123
Stream<String> s7 = list3.stream().flatMap(s -> Arrays.stream(s.split(",")));
s7.forEach(System.out::println); // a b c 1 2 3
4.4 排序
4.4.1 sorted():自然排序,流中元素需实现Comparable接口
4.4.2 sorted(Comparator com):定制排序,自定义Comparator排序器
List<Student> students = new ArrayList<>();
students.add(new Student("1","student1",10,"1",80));
students.add(new Student("2","student2",11,"1",90));
students.add(new Student("3","student3",12,"2",85));
students.add(new Student("4","student4",12,"2",90));
List<String> list4 = Arrays.asList("a", "c", "b");
//String 类自身已实现Compareable接口
list4.stream().sorted().forEach(System.out::println);// a b c
//自定义排序:先按姓名升序,姓名相同则按年龄升序
students.stream().sorted(
(o1, o2) -> {
if (o1.getName().equals(o2.getName())) {
return o1.getAge() - o2.getAge();
} else {
return o1.getName().compareTo(o2.getName());
}
}
).forEach(System.out::println);
4.5 聚合、匹配操作(终端操作)
4.5.1 max:返回流中元素最大值
4.5.2 min:返回流中元素最小值
4.5.3 count:返回流中元素的总个数
4.5.4 findFirst:返回流中第一个元素
4.5.5 findAny:返回流中的任意元素
4.5.6 noneMatch:当流中每个元素都不符合该断言时才返回true,否则返回false
4.5.7 anyMatch:只要流中有一个元素满足该断言则返回true,否则返回false
4.5.8 allMatch:当流中每个元素都符合该断言时才返回true,否则返回false
List<Integer> list5 = Arrays.asList(1, 2, 3, 4);
Integer max = list5.stream().max(Integer::compareTo).get(); //4
Integer min = list5.stream().min(Integer::compareTo).get(); //1
Integer findFirst = list5.stream().findFirst().get(); //1
Integer findAny = list5.stream().findAny().get(); //2
boolean noneMatch = list5.stream().noneMatch(e -> e > 5); //true
boolean anyMatch = list5.stream().anyMatch(e -> e > 2); //true
boolean allMatch = list5.stream().allMatch(e -> e > 5); //false
4.6 归约操作
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
Optional<T> reduce(BinaryOperator<T> accumulator):
第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中
元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数
为流中的第三个元素;依次类推。
T reduce(T identity, BinaryOperator<T> accumulator):
流程Optional<T> reduce(BinaryOperator<T> accumulator)一样,只是第一次执行时,
accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):
在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在
并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执
行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,
则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流
程进行规约。
List<Integer> list6 = Arrays.asList(1, 2, 3, 4, 5);
Integer integer1 = list6.stream().reduce((x1, x2) -> x1 + x2).get();
Integer integer2 = list6.stream().reduce(2, (x1, x2) -> x1 + x2);
Integer integer3 = list6.stream().reduce(0,(x1, x2) -> x1 - x2, (y1, y2) -> y1 * y2);
Integer integer4 = list6.parallelStream().reduce(0,(x1, x2) -> x1 - x2, (y1, y2) -> y1 * y2);
4.7 收集操作
4.7.1 collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
4.7.2 Collector<T, A, R> 是一个接口,有以下5个抽象方法:
4.7.2.1 Supplier<A> supplier():创建一个结果容器A
4.7.2.2 BiConsumer<A, T> accumulator():消费型接口,第一个参数为容器A,第二个
参数为流中元素T。
4.7.2.3 BinaryOperator<A> combiner():函数接口,将并行流中各个子进程的运行结果
合并。
4.7.2.4 Function<A, R> finisher():函数式接口,参数为:容器A,返回类型为:collect
方法最终想要的结果R。
4.7.2.5 Set<Characteristics> characteristics():返回一个不可变的Set集合,用来表明
该Collector的特征。
有以下三个特征:
CONCURRENT:表示此收集器支持并发。
UNORDERED:表示该收集操作不会保留流中元素原有的顺序。
IDENTITY_FINISH:表示finisher参数只是标识而已,可忽略。
List<Student> students = new ArrayList<>();
students.add(new Student("1","student1",10,"1",80));
students.add(new Student("2","student2",11,"1",90));
students.add(new Student("3","student3",12,"2",85));
students.add(new Student("4","student4",12,"2",90));
//转list
List<Integer> ageList = students.stream().map(Student::getAge).collect(Collectors.toList());
//转set
Set<Integer> ageSet = students.stream().map(Student::getAge).collect(Collectors.toSet());
//转map(注:key不能相同,否则报错)
Map<String, Integer> map = students.stream().collect(Collectors.toMap(Student::getName, Student::getAge));
//学生总数
Long studentNum = students.stream().collect(Collectors.counting());
//最大年龄
Integer max = students.stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compare)).get();
//年龄求和
Integer sumAge = students.stream().collect(Collectors.summingInt(Student::getAge));
//平均年龄
Double average = students.stream().collect(Collectors.averagingDouble(Student::getAge));
//分组
Map<Integer, List<Student>> ageMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
//按分数分组
Map<Boolean, List<Student>> group1 = students.stream().collect(Collectors.partitioningBy(x -> x.getScore() > 90));
//按班级分组
Map<String, List<Student>> group2 = students.stream().collect(Collectors.groupingBy(Student::getClassGrade));
//分区,分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<Student>> partMap = students.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
//归约
Integer allAge = students.stream().map(Student::getAge).collect(Collectors.reducing(Integer::sum)).get();
//接合(joining)
String names = students.stream().map(p -> p.getName()).collect(Collectors.joining(","));