1 函数式编程

函数式编程(Functional Programming)是编程范式的一种。最常见的编程范式是命令式编程(Impera Programming),比如面向过程、面向对象编程都属于命令式编程,大家用得最多、最熟悉。函数式编程并非近几年的新技术或新思维,其诞生已有50多年时间。

在函数式编程里面,一切都是数学函数。当然,函数式编程语言里也可以有对象,但这些对象是不可变的——要么是函数参数要么是返回值。函数式编程语言里没有for等循环,而是通过递归、把函数当成参数传递的方式实现循环效果。

简而言之,函数式编程的特点:一切皆函数(称算子?)、一切数据皆不可变,一切计算都是函数作用在不可变数据上产生新不可变数据的过程。

注:Java 8的主要变化

  • lambda表达式
  • 方法引用
  • 接口默认方法
  • Stream
  • 用Optional取代null
  • 新的日志和时间:推荐用Instance代替Date、用LocalDateTime代替Calendar、用DateTimeFormatter代替SimpleDateFormat
  • CompletableFuture
  • 去除了永久代(PermGen) 被元空间(Metaspace)代替

2 Java 8函数式编程(Lambda表达式)

Java 8开始支持函数式编程,其是通过Lambda表达式语法来支持的。

Java SE 8 adds a relatively small number of new language features -- lambda expressions, method references, default and static methods in interfaces, and more widespread use of type inference

2.1 Lambda表达式

Lambda表达式可以看成是对 特定接口的匿名实现类语法 的简写(只是语法上看如是,它们在JVM层面是有明显区别的,见后文),这里的“特定接口”是指函数式接口,若不是函数式接口则传参时若使用Lambda表达式编译器会报错。示例:

new Thread(new Runnable() {
@Override
public void run() {
}
});//通过匿名内部类创建Runnable接口实现类作为Thread的参数 new Thread(() -> {
});//通过Labmda表达式创建Thread的参数 Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);
Comparator<String> c = (String s1, String s2) -> s1.compareToIgnoreCase(s2);
FileFilter java = f -> f.getName().endsWith(".java");
button.addActionListener(e -> ui.dazzle(e.getModifiers()));
Supplier<Runnable> c = () -> () -> { System.out.println("hi"); };
Callable<Integer> c = flag ? (() -> 23) : (() -> 42);

Lambda表达式代替匿名内部类作为函数的参数时,相当于向函数传递了个算子。与匿名内部类相比,Lambda表达式省略了方法名、方法的参数类型有无均可,当然最好不要写类型以由编译器自动推断从而提高灵活性。

2.2 函数式接口

函数式接口:接口中抽象方法有且只有一个 的接口。注:由于java中Object是任意类或接口的父类,故该抽象方法签名不能与Object中的方法一样;接口中可有非抽象方法,如default、static method等;可以在接口上加@FunctionalInterface,此时编译器会检查接口是否符合函数式接口规范。示例:

    @FunctionalInterface
public interface CallBack {
public String run();// 须有且只有一个抽象方法
// public String toString();//不允许,抽象方法不能与Object中的方法的signature一样 default String getName() {// 允许有default方法
return "zhangsan";
} public static String getVersion() {// 允许有static方法
return "1";
}
}

Java 8中定义了一组常用的函数式接口(java.util.function包下):

函数接口 抽象方法 功能 示例
Predicate boolean test(T t) 判断真假 身高大于177cm?
Consumer void accept(T t) 消费消息  
Supplier T get() 生产消息  
Function R apply(T t) 将T类型值转换成R类型值 根据student对象获取名字
BiFunction R apply(T t, U u) 将T、U类型值转成R类型值 两个数乘积
UnaryOperator T apply(T t) 一元操作 逻辑非(!)
BinaryOperator T apply(T t, T u) 将两个T类型值转换成R类型值,BiFunction的特例  

除了上述几种函数式接口外,还提供了int、long、double的primitive specializations,如IntSupplier、LongBinaryOperator、LongToDoubleFunction等。其他primitive types的可以转换为这三个primitive types的。

这些函数式接口结合Java 8中引入的Stream一起使用能够可以很大程度提高开发者的编码效率。

Lambda表达式和匿名内部类的区别:

匿名内部类编译后编译器会为匿名类也单独生成class文件,lambda表达式则不会。=> lambda表达式中的this的意义跟在表达式外的一样。示例:

 public class Hello {
Runnable r1 = () -> { System.out.println(this); }
Runnable r2 = () -> { System.out.println(toString()); } public String toString() { return "Hello, world!"; } public static void main(String... args) {
new Hello().r1.run();
new Hello().r2.run();
}
}
//结果:输出两个“Hello, world!”
//若r1、r2用匿名内部类,则输出的两个结果不同,类似于:Hello$1@5b89a773 and Hello$2@537a7706

meaning of this in lambda

关于Lambda表达式,推荐参阅:Lambda-State-final

2.3 方法引用

若lambda表达式仅仅是调用某个方法,则可以写成方法引用,这种情况下使得方法引用代码更简洁易读。示例:

list.forEach(new Consumer() {
@Override
public void accept(Object s) {
System.out.println(s);
}
});//1 匿名内部类
list.forEach(s->System.out.println(s));//2 lambda表达式
list.forEach(System.out::println);//3 方法引用

方法引用有很多种,语法如下:

  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 父类的实例方法引用:super::methodName
  • 类型上的实例方法引用:ClassName::methodName
  • 构造方法引用:Class::new
  • 数组构造方法引用:TypeName[]::new

3 Java 8 Stream

Java 8的interface引入了default method,以对集合框架Collection进行了扩展(从而其继承者如List、Set、Map也扩展了):增加了很多default method以支持Stream及Lambda表达式操作。

3.1 what

Java 8 API添加了新的抽象Stream(这里的Stream不是IO中的流),可以让我们以声明的方式处理数据。这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,如筛选、排序、聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由终止操作(terminal operation)得到前面处理的结果。

示例:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();

Stream并不是某种数据结构,它更像是数据源(数据源可以是一个数组,Java容器或I/O channel等)的一种迭代器(Iterator),单向、不可重复、数据源只能遍历一次。要得到一个Stream通常不会手动创建,而是调用对应的工具方法,如:list1.stream()、list1.parallelStream()、Arrays.stream(T[] array)

3.2 Stream的特点

无存储:stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等,且可以是无限的。

无状态(为函数式编程而生):对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream。

惰式执行:stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。

可消费性:stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成stream。

3.3 stream使用

对stream的操作分为两类,中间操作(intermediate operations)和终止操作(terminal operations):

中间操作总是会惰式执行,调用中间操作只会生成一个标记了该操作的新stream,仅此而已。

结束操作会触发实际计算,计算发生时会把所有中间操作积攒的操作以pipeline的方式执行,这样可以减少迭代次数。计算完成之后stream就会失效。

Stream“以声明的方式处理数据”,其提供了一系列操作,主要有:

forEach

distinct、filter、sorted

map、flatMap

reduce(虽也提供了max、min、count、sum,但这些均可由reduce实现等价效果)

collect,收集器,方法声明为 <R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner) ,三个参数分别指定目标容器是什么、元素如何产生、若是并行执行则多个结果如何合并。

实际用得更多的是只有一个Collector参数的collect方法,java.util.stream.Collectors工具类封装了生成大多数常用Collector的静态方法。如:

joining

summingDouble

生成列表:

toList、toSet:返回的集合类型为接口,具体类型由库决定

toCollection(ArrayList::new):返回的集合具体类型自己指定

生成map:

partitioningBy //只能分为两组,key为Boolean类型

groupingBy //可分为多组,类似于SQL中的group by操作

toMap

在使用时,就是通过为这些操作指定Lambda表达式(亦可称算子或函数?)来指定操作逻辑的。示例:

 //reduce
Integer reduce = Stream.of(1, 2, 3, 4).reduce(0, (acc, x) -> acc+ 1);//相当于count //map
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
List<String> names = students.stream().map(student -> student.getName()).collect(Collectors.toList()); //flateMap
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185));
List<Student> studentList = Stream.of(students,
asList(new Student("艾斯", 25, 183),
new Student("雷利", 48, 176)))
.flatMap(students1 -> students1.stream()).collect(Collectors.toList()); //toSet
Set<String> set = stream.collect(Collectors.toSet()); // (2) //toCollection
HashSet<String> hashSet = stream.collect(Collectors.toCollection(HashSet::new));// (4) //toMap
// 使用toMap()统计学生GPA
Map<Student, Double> studentToGPA =
students.stream().collect(Collectors.toMap(Function.identity(),// 如何生成key
student -> computeGPA(student)));// 如何生成value //partitioningBy
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD)); //groupingBy
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment)); Map<Department, Integer> totalByDept = employees.stream()// 使用下游收集器统计每个部门的人数
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.counting()));// 下游收集器 Map<Department, List<String>> byDept = employees.stream()// 按照部门对员工分布组,并只保留员工的名字
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.mapping(Employee::getName,// 下游收集器
Collectors.toList())));// 更下游的收集器 //joining
List<Student> students = new ArrayList<>(3);
students.add(new Student("路飞", 22, 175));
students.add(new Student("红发", 40, 180));
students.add(new Student("白胡子", 50, 185)); String names = students.stream()
.map(Student::getName).collect(Collectors.joining(",","[","]"));

stream api demo

3.4 Stream工作流执行原理(stream-pipeline)

参阅:https://objcoding.com/2019/03/04/lambda/#stream-pipelines

3.5 parallel Stream

java并行API演变:

  • 1.0-1.4 中的 java.lang.Thread
  • 5.0 中的 java.util.concurrent
  • 6.0 中的 Phasers 等
  • 7.0 中的 Fork/Join 框架
  • 8.0 中的 parallelStream

parallelStream是java 8引入的,用于并行操作。其内部用了Fork/Join框架。

适用场景:CPU密集型

为何可比普通的多线程或线程池快:使用Fork/Join框架(ForkJoinPool.commonPool()),ForkJoinPool会自动将大任务拆解成无交集的子任务给不同线程并行执行,最后汇集结果。与线程池不同的是,还采取了工作窃取(work-stealing)算法,当有线程完成计算任务时会从其他线程的任务队列取任务来执行,从而整体上提高执行效率。

优点:代码简介,执行效率高;缺点:黑箱、不好跟踪调试

使用:尽可能用Stream API,多核情况下尽可能用parallelStream

参阅:

parallelStream原理:https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/7-ParallelStream.md

Stream API性能测试:https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/8-Stream%20Performance.md

4 参考资料

使用:https://www.cnblogs.com/snowInPluto/p/5981400.htmlhttp://www.jiangxinlingdu.com/thought/2019/09/06/jdk8s.html

原理:https://objcoding.com/2019/03/04/lambda/

Java 函数式编程(Lambda表达式)与Stream API的更多相关文章

  1. 函数式编程/lambda表达式入门

    函数式编程/lambda表达式入门 本篇主要讲解 lambda表达式的入门,涉及为什么使用函数式编程,以及jdk8提供的函数式接口 和 接口的默认方法 等等 1.什么是命令式编程 命令式编程就是我们去 ...

  2. 十分钟学会Java8:lambda表达式和Stream API

    Java8 的新特性:Lambda表达式.强大的 Stream API.全新时间日期 API.ConcurrentHashMap.MetaSpace.总得来说,Java8 的新特性使 Java 的运行 ...

  3. JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用

    jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default   Lambda表达式     L ...

  4. java8中Lambda表达式和Stream API

    一.Lambda表达式 1.语法格式 Lambda是匿名函数,可以传递代码.使用“->”操作符,改操作符将lambda分成两部分: 左侧:指定了 Lambda 表达式需要的所有参数 右侧:指定了 ...

  5. java8新特性-lambda表达式和stream API的简单使用

    一.为什么使用lambda Lambda 是一个 匿名函数,我们可以把 Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风 ...

  6. 十分钟学会Java8的lambda表达式和Stream API

    01:前言一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序.过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照. 刚开始写写是打基础,但写的多了,各种乏 ...

  7. Java8的lambda表达式和Stream API

    一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序.过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照. 刚开始写写是打基础,但写的多了,各种乏味,非过来 ...

  8. 函数式编程--lambda表达式对比匿名内部类

    从前面的整理中我们看出了,Lambda表达式其实是匿名内部类的一种简化,因此它可以部分取代匿名内部类. 1,Lambda表达式与匿名内部类存在如下相同点: 1),Lambda表达式与匿名内部类一样,都 ...

  9. JDK1.8中的Lambda表达式和Stream

    1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有python或者javascript的语言基础,对理解Lambda表达式有很大帮助,因为Java正 ...

  10. Java函数式编程:一、函数式接口,lambda表达式和方法引用

    Java函数式编程 什么是函数式编程 通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率 我们可以这样理解:面向对象编程抽象数据,函数式编程抽象 ...

随机推荐

  1. VS 插件 ReSharper 的个人习惯

    个人习惯 1. 按 F12 恢复转到定义 很多时候,我个人不太喜欢一按 F12 就跳转到 ReSharper 自带的 查看代码浏览器,我还是喜欢 VS 默认的,于是点击菜单栏的 "RESHA ...

  2. 【题解】Dvoniz [COCI2011]

    [题解]Dvoniz [COCI2011] 没有传送门,只有提供了数据的官网. [题目描述] 对于一个长度为 \(2*K\) 的序列,如果它的前 \(K\) 个元素之和小于等于 \(S\) 且后 \( ...

  3. 5种mysql日志分析工具比拼

    5种mysql日志分析工具比拼 摘自: linux.chinaitlab.com  被阅读次数: 79 由 yangyi 于 2009-08-13 22:18:05 提供 mysql slow log ...

  4. .NET Core 使用NPOI读取Excel返回泛型List集合

    我是一名 ASP.NET 程序员,专注于 B/S 项目开发.累计文章阅读量超过一千万,我的博客主页地址:https://www.itsvse.com/blog_xzz.html 网上有很多关于npoi ...

  5. 修复dtcms5.0后台管理编辑器上传视频和图片被过滤问题

    1.原程序代码调用上传接口文件路径更改为父节点相对路径: 2.修复ueditor.config.js配置: img: ['src', 'alt', 'title', 'width', 'height' ...

  6. Python - 运算符 - 第五天

    Python语言支持以下类型的运算符 算术运算符 比较(关系)运算符 赋值运算符 逻辑运算符 位运算符 成员运算符 身份运算符 运算符优先级 Python算术运算符 以下假设变量a为10,变量b为21 ...

  7. http接口和webservice接口的区别

    web service(SOAP)与HTTP接口的区别 什么是web service? soap请求是HTTP POST的一个专用版本,遵循一种特殊的xml消息格式Content-type设置为: t ...

  8. JZOJ.2117. 【2016-12-30普及组模拟】台风

    题目大意: 天气预报频道每天从卫星上接受卫星云图.图片被看作是一个矩阵,每个位置上要么是”#”,要么”.”,”#”表示该位置没有云,”.”表示有云,地图上每个位置有多达8个相邻位置,分别是,左上.上. ...

  9. 英语LIGNALOO沉香lignaloo单词

    沉香lignaloo,是瑞香科.沉香属的一种乔木,高5-15米.树皮暗灰色,几平滑,纤维坚韧:小枝圆柱形,具绉纹,幼时被疏柔毛,后逐渐脱落,无毛或近无毛.产于中国广东.海南.广西.福建等地.喜生于低海 ...

  10. 【Android】天气应用

    模仿华为的"天气"应用写的一个小Demo.部分功能.动画效果没有实现,也没有过多考虑性能.Bug等其它方面的因素.写这个Demo的初衷是想熟悉下目前网上常用的一些框架. Demo采 ...