Lambda
方法引用
默认方法
函数接口
Function
Stream
“Lambda 表达式”(lambda expression)是一个匿名函数,代码简洁并且可读。Lambda表达式可以替代以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**特征**
* 1. 类型声明(可选): 可以不需要声明类型,编译器会识别参数值
* 2. 参数圆括号(可选):单个参数时,不需要圆括号。多个参数时,需要圆括号。
* 3. 大括号和return关键字(可选):如果只有一个表达式,可以省略大括号和return关键字,编译器会*自动的返回值;在使用大括号的情况下,则必须指明返回值
*
*(parameters) -> expression
*(parameters) -> { statements;
*/
//案例
public class LambdaExDemo {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
list.add(new Student("张三",21));
list.add(new Student("李四",15));
list.add(new Student("王五",23));
list.add(new Student("赵六",32));
System.out.println("排序前:"+list);
//第一种 传统匿名Compartor接口排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge().compareTo(o2.getAge());
}
});
System.out.println("匿名接口方法排序后: "+list);
//使用Lambda表达式接口来代替匿名接口方法
//声明式,不使用大括号,只写单条语句
Collections.sort(list,(Student s1,Student s2)->s1.getAge().compareTo(s2.getAge()));
System.out.println("声明式,不使用大括号,只写单条语句: "+list);
//不声明式,使用大括号,可以写多条语句
Collections.sort(list,(a,b)->{
System.out.println("------------------ : "+a.getAge().compareTo(b.getAge()));
System.out.println("a.Age: "+a.getAge()+" b.Age: "+b.getAge());
return a.getAge().compareTo(b.getAge());
});
System.out.println();
System.out.println("不声明式,使用大括号,可以写多条语句: "+list);
//使用Lambda表达式调用静态方法
Collections.sort(list,(a,b)->Student.sortByName(a,b));
System.out.println("使用Lambda表达式调用静态方法: "+list);
//使用Lambda表达式调用类的实现
Collections.sort(list,(a,b)->new Student().sortByAge(a,b));
System.out.println("使用Lambda表达式调用类的实现: "+list);
}
}
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LambdaExDemo01 {
/**
* 方法引用的种类:
* 1.类静态方法的引用
* 2.某个对象的方法的引用
* 3.特定类的任意对象的方法的引用
* 4.构造方法的引用
*/
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
//静态方法的引用
Collections.sort(list,Student::sortByName);
System.out.println("静态方法的引用: "+list);
//对象方法的使用
Collections.sort(list,new Student()::sortByAge);
System.out.println("类方法的使用: "+list);
//特定类方法的调用
Integer [] s = new Integer[]{5,1,3,6,7,8,2};
Arrays.sort(s,Integer::compare);
System.out.println("特定类方法的调用: "+Arrays.toString(s));
//引用类的构造器
Student student = Student.create(Student::new);
System.out.println("引用类的构造器 : "+student.toString());
}
}
在Java8以前,未接口添加一个通用的实现是很不容易的,接口一旦发布就等于是定型,如果这个时候在接口内增加一个方法,那么就会破坏所有实现接口的对象。
默认方法(虚拟扩展方法或守护方法)的目标就是解决这个问题,使得接口在发布之后任然能够逐步演化。
public interface SmallStudent {
/**
* 静态方法和默认方法均可以有多个,默认方法可以被覆盖
*/
//默认方法
default void print(){
System.out.println("我是一名小学生");
}
//静态方法
static void sing(){
System.out.println("小学生在说话!");
}
}
“函数接口”,就是除去默认方法以及继承的抽象的方法,只有显示声明一个抽象的接口。它使用@Functionallnterface注解在类上进行标注,也可以省略。Java会自动识别。以下是一些常用案例。
java.util.Comparator
Comparator是Java中经典接口,在排序中较为常用。Java8在此之上新增了一些新的默认方法
public static void main(String[] args) {
Integer [] abs = new Integer[]{3,5,8,7,6};
Comparator<Integer> comparator = Integer::compare;
Arrays.sort(abs,comparator);
System.out.println("ASC:"+Arrays.toString(abs));
Arrays.sort(abs,comparator.reversed());
System.out.println("DESC:"+Arrays.toString(abs));
}
java.util.function.Predicate
该接口包含方法boolean test(T t),一般用于条件的检测,内部包含三个默认方法:and(与),or(或),negate(非)用于各式的判断。
public static void main(String[] args) {
//notice : 在这里与或非的判断顺序是从左到右的,调用的顺序会影响结果。
Predicate<Integer> predicate = a -> a > 5;
predicate.test(10);
System.out.println(predicate.test(10)); //true
predicate.negate().test(10);
System.out.println(predicate.negate().test(10));//false
predicate.or(a -> a < 1).and(a -> a > -1).negate().test(-1);
System.out.println( predicate.or(a -> a < 1).and(a -> a > -1).negate().test(-1));//true
}
java.util.function.Supplier
此类只有一个方法:T get();
Supplier接口是在1.8中新出现的函数接口,用于支持函数是编程,它用于返回一个任意泛型的实力对象,与工厂功能类似。
java.util.function.Consumer
该接口表示一个接受单个输入参数并且没有返回值的操作。不像其他函数式接口,Consumer接口期望执行修改内容的操作。例如,我们需要一个批量修改student的方法,利用Predicate和Consumer可以完成。
public static List updateBatch(List<Student> students, Predicate<Student> predicate, Consumer<Student> consumer){
for (int i = 0; i <students.size() ; i++) {
if(predicate.test(students.get(i))){
consumer.accept(students.get(i));
}
}
return students;
}
测试
public static void main(String[] args) {
//批量修改年龄小于25的均改为25
List<Student> list = Student.getSomeStuddent();
System.out.println(Student.updateBatch(list,s -> s.getAge() < 25,s -> s.setAge(25)));
List<String > students = Student.updateBatch(list,s -> s.getAge() < 25,s -> s.setAge(25));
System.out.println(students );
System.out.println(list);
}
Function
Java8提供的java.util.function包的核心函数接口有4个:
Function接口是为Java8提供了函数式编程的基础,apply方法与Consumer的accept方法功能类似。但是提供了返回及类型转换的可能。功能更加强大;再通过andThen与compose方法可以使Function组成Function功能链,进行多级数据处理及转换。
主要方法
详解
apply:
R apply(T t);
接收类型:T
返回类型:R
类型转换:T -> R
Function 接口的核心方法,可以执行任意的操作,且具有返回值。接收一个T对象,在经过处理后,返回一个R类型的对象。主要功能为类型转换及数据处理。
compose:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
接收类型:Function super V, ? extends T>
返回类型:Function
类型转换:(V+)->(T-)->T->R
apply执行顺序:before -->this
此处“V+”指代“? super V”,表示包含V在内的V的任意父类;"T-"指代“? extends T”,表示包含T在内的T的任意子类。compose方法返回一个Function
通过compose方法,可以在某个Function执行之前插入一个Function执行。由于返回类型依旧为Function,可以重复调用compose方法形成方法链。
andThen:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
接收类型:Function super R, ? extends T>
返回类型:Function
类型转换:T->R->(R+)->(V-)
apply执行顺序:this–>after
此处“R+”指代“? super R”,表示包含R在内的R的任意父类;"V-"指代“? extends V”,表示包含V在内的V的任意子类。andThen方法返回一个Function
通过andThen方法,可以在某个Function执行之后插入一个Function执行。由于返回类型依旧为Function,可以重复调用andThen方法形成方法链。
identity:
static <T> Function<T, T> identity() {
return t -> t;
}
接收类型:无
返回类型:Function
类型转换:T→T
该方法的说明是:返回一个函数,它总是返回输入参数。调用该方法可以得到一个返回输入参数的Funtion,这个Function就可以单纯的用来做数据处理,而不用类型转换。
Stream
java8中提供的Stream API,即流式处理,可以将List,Set,Array等对象转换成流进行操作。Stream内的流操作分为两种,中间操作与最终操作,中间操作会返回一个全新的Stream对象,意味着你的操作不会影响最初的流;最终操作会将流进行转换或者操作,返回非Stream的对象。Stream可以替代传统的循环操作,从线程上的区别,Stream分为穿行(Stream)和并行(parallelStream)。
Stream内一般方法:
中间操作:
//distinct
//去除Stream中重复的对象,并返回一个流。(使用对象的equals方法)
//这个方法是通过类的 equals() 方法来判断两个元素是否相等的
java Stream<T> distinct();
//skip
//跳过Stream中的前n个对象,将其他对象返回一个Stream.如果n超过了Stream中对象的个数,则会返回一个空的Stream
java Stream<T> skip(long n)
//limit
//截取Stream的前maxSize个对象,并形成一个新Stream
java Stream<T> limit(long maxSize);
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
//返回前2个元素
list = list.stream().limit(2).collect(Collectors.toList());
System.out.println(list);
}
//filter
//根据给定的predicate来过滤对象,返回满足条件的对象构成Stream
Stream<T> filter(Predicate<? super T> predicate);
//案例
public static void main(String[] args) {
//保留年龄为25的Student
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
list = list.stream().filter(student -> student.getAge() == 25).collect(Collectors.toList());
System.out.println(list);
}
//skip 跳过指定的几个
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
//用在limit之前,先去除前m个元素再返回剩余元素的前n个元素,也就是先得到两个再跳过一个
list = list.stream().limit(2).skip(1).collect(Collectors.toList());
}
//map
//通过给定的mapper,将T类型的流转换为R类型的Stream。 类似与类型转换
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
List<String> names = list.stream().map(Student::getName).collect(Collectors.toList());
System.out.println(names);
}
//flatMap
//flatMap也是将Stream进行转换,flatMap与map的区别在于将一个Stream中的每个值都转成一个个Stream,然后再将这些流扁平化成为一个Stream。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
public static void main(String[] args) {
// List list = Student.getSomeStuddent();
List<String> list = new ArrayList<>();
list.add("aaa bbb ccc");
list.add("ddd eee fff");
list.add("ggg hhh iii");
System.out.println(list);
//将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流
//例子中,我们的目的是把 List 中每个字符串元素以" "分割开,变成一个新的 List
list = list.stream().map(s->s.split(" ")).flatMap(Arrays::stream).collect(Collectors.toList());
System.out.println(list);
}
//sorted
//sorted方法可以对Stream进行排序。排序的对象必须实现Comparable,如果没实现会抛出ClassCastException;不提供comparator时,则会调用compareTo方法。
java Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator);
//根据年龄来比较大小
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
//根据年龄来比较大小 升序
list = list.stream().sorted((s1,s2)-> s1.getAge() - s2.getAge()).collect(Collectors.toList());
//根据年龄来比较大小 升序降序
list = list.stream().sorted((s1,s2)-> s2.getAge() - s1.getAge()).collect(Collectors.toList());
//可以进行简化
list = list.stream().sorted(Comparator.comparingInt(Student::getAge)).collect(Collectors.toList());
list = list.stream().sorted(Comparator.comparingInt(Student::getAge).reversed()).collect(Collectors.toList());
System.out.println(list);
}
//peek
//对流中的每个对象执行提供的action操作。在Stack中,peek用于查看一个对象。在流中也是一样,用于在流循环时,根据给定的action进行查看对象。虽然可以进行元素修改操作,但不建议。
java Stream<T> peek(Consumer<? super T> action);
最终操作:
//聚合
//max & min
//根据给定的comparator返回Stream中的max或min。
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
//count
//返回Stream中对象的个数。
long count();
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
Long length = list.stream().count();
System.out.println(length);
}
//匹配
//anyMatch & allMatch & noneMatch
//根据给定的predicate判断Stream是否匹配条件。
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
//anyMatch,allMatch,noneMatch
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
//是否有一个匹配给定的条件
boolean res = list.stream().anyMatch(student -> student.getAge()==25);
System.out.println(res);
//是否左右匹配给定的条件
boolean res1 = list.stream().allMatch(student -> student.getAge()==25);
System.out.println(res1);
//是否没有元素匹配给定的条件
boolean res2 = list.stream().noneMatch(student -> student.getAge()==25);
System.out.println(res2);
}
//collect
//根据给定的collector对Stream中的元素进行操作,返回复杂数据结构的对象。用于将Stream中的对象转换成我们想要的结构,如list、map、set等。前例中就使用collect(Collectors.toList())将Stream中的对象转换成List。
<R, A> R collect(Collector<? super T, A, R> collector);
//reduce
//如果我们不知希望单纯的返回List这样的类型,而是希望将整个Stream经过一些操作后,规约成一个对象返回,就可以用到规约操作。reduce方法有两个参数,其中accumulator代表着规约的操作,即用何种的方式进行参数化处理;identity则是accumulator的标识值。
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
//reduce((T, T) -> T) 和 reduce(T, (T, T) -> T)
//用于组合流中的元素,如求和,求积,求最大值等
//计算年龄之和
//其中,reduce 第一个参数 0 代表起始值为 0,lambda (a, b) -> a + b 即将两值相加产生一个新值
Integer ageSum = list.stream().map(student -> student.getAge()).reduce(0,(a,b)->a+b);
System.out.println(ageSum);
Integer ageSum1 = list.stream().map(Student::getAge).reduce(0,(a,b)->a+b);
System.out.println(ageSum1);
//即不接受任何起始值,但因为没有初始值,需要考虑结果可能不存在的情况,因此返回的是 Optional 类型
Optional<Integer> sums = list.stream().map(Student::getAge).reduce(new Student()::sum);
System.out.println(sums.get());
}
//toArray
//将Stream中的对象返回成一个Object数组。
Object[] toArray();
//forEach
//顾名思义,对Stream中每个元素进行action操作,与peek类似,但forEach是一个最终操作,一般在结束时查看对象使用。
void forEach(Consumer<? super T> action);
//findFirst & findAny
//indFirst可以返回Stream中第一个对象,并将它封装在Optional中。findAny则不是返回第一个对象,而是任意一个对象。在顺序Stream中findFirst和findAny的结果是一致的,但在并行Stream中,findFirst存在着限制,故在并行Stream中需要使用findAny(findAny源码注释中写的是some element?)。同样将对象封装在Optional中。
Optional<T> findFirst();
Optional<T> findAny();
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
System.out.println(list);
IntStream intStream1 = list.stream().mapToInt(Student::getAge);
Stream<Integer> streams = intStream1.boxed();
System.out.println(streams.collect(Collectors.toList()));
}
求 1 到 10 的数值总和:
public static void main(String[] args) {
IntStream intStream = IntStream.rangeClosed(1, 10);
int sum = intStream.sum();
System.out.println(sum);
数组创建流
根据参数的数组类型创建对应的流:
Arrays.stream(T[ ])
Arrays.stream(int[ ])
Arrays.stream(double[ ])
Arrays.stream(long[ ])
值得注意的是,还可以规定只取数组的某部分,用到的是Arrays.stream(T[], int, int)
public static void main(String[] args) {
//只取索引第 1 到第 2 位的
int[] a = {1, 2, 3, 4};
Arrays.stream(a, 1, 3).forEach(System.out :: println);
}
//打印 2 ,3
文件生成流
Stream stream = Files.lines(Paths.get(“data.txt”));
每个元素是给定文件的其中一行
函数生成流
两个方法:
iterate : 依次对每个新生成的值应用函数
generate :接受一个函数,生成一个新的值
public static void main(String[] args) {
// 生成流,首元素为 0,之后依次加 2
Stream.iterate(0, n -> n + 2);
// 生成流,为 0 到 1 的随机双精度数
Stream.generate(Math :: random);
// 生成流,元素全为 1
Stream.generate(() -> 1);
}
public static void main(String[] args) {
List<Student> list = Student.getSomeStuddent();
List<Student> newlist = list.stream().collect(Collectors.toList());
}
汇总
(1)counting
用于计算总和:
long l = list.stream().collect(counting());
也可以:
long l = list.stream().count();
推荐第二种
(2)summingInt ,summingLong ,summingDouble
summing,计算总和,不过这里需要一个函数参数
计算 Person 年龄总和:
int sum = list.stream().collect(summingInt(Person::getAge));
当然,这个可以也简化为:
int sum = list.stream().mapToInt(Person::getAge).sum();
除了上面两种,其实还可以:
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
推荐第二种
由此可见,函数式编程通常提供了多种方式来完成同一种操作
(3)averagingInt,averagingLong,averagingDouble
看名字就知道,求平均数
Double average = list.stream().collect(averagingInt(Person::getAge));
当然也可以这样写
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
不过要注意的是,这两种返回的值是不同类型的
(4)summarizingInt,summarizingLong,summarizingDouble
这三个方法比较特殊,比如 summarizingInt 会返回 IntSummaryStatistics 类型
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
IntSummaryStatistics 包含了计算出来的**平均值,总数,总和,最值,**可以通过下面这些方法获得相应的数据
取最值
maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数
Optional optional = list.stream().collect(maxBy(comparing(Person::getAge)));
我们也可以直接使用 max 方法获得同样的结果
Optional optional = list.stream().max(comparing(Person::getAge));
joining 连接字符串
也是一个比较常用的方法,对流里面的字符串元素进行连接,其底层实现用的是专门用于字符串连接的 StringBuilder
String s = list.stream().map(Person::getName).collect(joining());
结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));
结果:jack,mike,tom
joining 还有一个比较特别的重载方法:
String s = list.stream().map(Person::getName).collect(joining(" and ", "Today “, " play games.”));
结果:Today jack and mike and tom play games.
即 Today 放开头,play games. 放结尾,and 在中间连接各个字符串
groupingBy 分组
groupingBy 用于将数据分组,最终返回一个 Map 类型
Map
例子中我们按照年龄 age 分组,每一个 Person 对象中年龄相同的归为一组
另外可以看出,Person::getAge 决定 Map 的键(Integer 类型),list 类型决定 Map 的值(List
多级分组
groupingBy 可以接受一个第二参数实现多级分组:
Map 按组收集数据 之前我就讲到了 parallelStream 方法能生成并行流,因此你通常可以使用 parallelStream 来代替 stream 方法,但是并行的性能问题非常值得我们思考 比方说下面这个例子 int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum); 但实际上是,这样的计算,效率是非常低的,比不使用并行还低!一方面是因为装箱问题,这个前面也提到过,就不再赘述,还有一方面就是 iterate 方法很难把这些数分成多个独立块来并行执行,因此无形之中降低了效率。 流的可分解性 作者: 一个计算机菜鸟
其中返回的 Map 键为 Integer 类型,值为 Map
Map
该例子中,我们通过年龄进行分组,然后 summingInt(Person::getAge)) 分别计算每一组的年龄总和(Integer),最终返回一个 Map
根据这个方法,我们可以知道,前面我们写的:
groupingBy(Person::getAge)
其实等同于:
groupingBy(Person::getAge, toList())
partitioningBy 分区
分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean
根据年龄是否小于等于20来分区
Map
.collect(partitioningBy(p -> p.getAge() <= 20));
打印输出
{
false=[Person{name=‘mike’, age=25}, Person{name=‘tom’, age=30}],
true=[Person{name=‘jack’, age=20}]
}
同样地 partitioningBy 也可以添加一个收集器作为第二参数,进行类似 groupBy 的多重分区等等操作。并行
我们通过这样一行代码来计算 1 到 100 的所有数的和,我们使用了 parallel 来实现并行。
这就说到流的可分解性问题了,使用并行的时候,我们要注意流背后的数据结构是否易于分解。比如众所周知的 ArrayList 和 LinkedList,明显前者在分解方面占优。
出处: https://www.cnblogs.com/qbzf-Blog/p/8946963.html#_caption_6
出处: https://cloud.tencent.com/developer/article/1187833
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利