1. 此函数非彼函数
在弄清楚什么是函数编程之前,有必要先弄清楚究竟什么是函数这个问题。在面向对象编程中,我们经常将方法称为函数,那么方法与函数究竟是否是同一个东西的不同称呼呢?函数式编程中的“函数”是指数学意义上的函数,不是编程语言中的“函数”。数学上的函数(Function)可以看成一个小机器,给这个机器提供一定的原材料(输入参数),它就会加工出(输出)一定的产品(返回值),如图1所示。
图1 函数示意图
图片来自:https://en.wikipedia.org/wiki/Function_(mathematics)
方法往往是有副作用(Side Effect)的,即一个方法的调用往往意味着对象状态的变化,而这种变化可能使得用同样的参数多次调用同一个方法,每次调用的返回值可能各不相同。而数学上的函数是没有副作用的(Side Effect Free),因此只要使用同样的参数调用同一个函数,那么不管调用这个函数多少次,每次调用的返回值总是相同的。例如,对于一个计算矩形面积的函数f(x,y)=x*y(x和y代表矩形的长和宽),那么无论调用多少次f(3,4),每次的返回值总是12。
从“无副作用”的角度来看,一个方法只要对其的调用不会产生副作用,那么它就是一个函数,否则它就不是函数。例如,String.charAt(int)这个方法就是一个函数。System.currentTimeMillis()这个方法就不是函数,因为对其的每次调用的返回值都可能不相同。
2. 函数式编程:函数作为“一等公民”
面向对象编程是Java平台在Java 8之前就已经支持的一种编程范式(Paradigm),Java平台从Java 8开始支持另外一种编程范式——函数式编程(Functional Programming)。面向对象编程中,对象是“一等公民”。所谓“一等公民”就是指可以作为“值”的语言元素。“值”既可以赋值给变量,也可以作为方法调用的参数进行传递以及作为方法的返回值。例如,我们可以将“1024”赋值给一个int型变量,也可以将其作为方法调用的参数,或者作为一个方法的返回值。同样,对象(确切的说是对对象的引用,相当于指针)也可以作为一个变量(引用型变量)值或者作为一个方法调用的参数,或者作为一个方法的返回值(返回一个对象的方法我们通常称之为工厂方法)。
在面向对象编程中,方法(或者函数)并非“一等公民”,因为方法在这里就不是一个值。而在函数式编程中,函数翻身成为“一等公民”,因此在这里函数可以作为一个值赋值给一个变量,可以作为函数调用的参数(即用函数作为参数去调用一个函数),可以作为函数的返回值(即一个函数的返回值是另外一个函数)。
3. 函数式编程的优势
3.1. 编写更为简洁的代码
函数式编程使得我们能够编写更为简洁的代码。下面看一个例子。假设有个服务(如清单1所示),其启动动作(即Service.start()方法)比较耗时,因此我们希望用异步的方式去启动这个服务,以避免主线程被阻塞。
清单1 一个启动比较耗时的服务
public class SomeTimeConsumingService implements Service {
@Override
public void start() {
// 模拟启动动作的耗时
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
;
}
}
}
为此,我们只需要创建一个专门的线程(工作者线程),并在该线程的run方法中调用Service.start()即可,如清单2所示。
清单2 以异步方式启动一个耗时服务(面向对象编程)
public class ServiceStarter {
public static void main(String[] args) {
Thread serviceStarter;
final SomeTimeConsumingService service = new SomeTimeConsumingService();
//创建Runnable实例以调用Service.start()
serviceStarter = new Thread(new Runnable() {
@Override
public void run() {
service.start();
}
});
//启动线程
serviceStarter.start();
}
}
清单2中我们创建的匿名Runnable实例的目的仅仅是为了在工作者线程中调用Service.start()而已。如果我们能够直接把Service.start()方法作为一个参数传递给Thread类的构造器(姑且将构造器看做一种特殊的方法),那么这里我们也就省却了创建Runnable实例,从而使代码更加简洁。在Java 8之后我们的确可以这么做,如清单3所示。
清单3 以异步方式启动一个耗时服务(函数式编程)
public class ServiceStarterFP {
public static void main(String[] args) {
Thread serviceStarter;
final SomeTimeConsumingService service = new SomeTimeConsumingService();
serviceStarter = new Thread(service::start);//直接将start方法作为参数传递给构造器
serviceStarter.start();
}
}
这里,“service::start”是一个方法引用(Method Reference),它表示对象service的start方法。可见,我们将一个方法作为一个参数传递给了Thread类的构造器,这使得清单3相比清单2中相应代码要简洁一些。读者也许在想,Thread类的构造器的参数的类型是java.lang.Runnable,何以我们能够将一个方法(作为“值”)作为其参数传入呢?这样为何不会出现类型不匹配(兼容)的问题呢?后续的笔记中我们会解释这一点。
从这个例子我们可以看到,Java 8中方法已然跻身作为函数的“一等公民”行列,因此方法可以作为参数进行传递。显然Service.start()并不是真正意义上函数,因为它是有副作用的(至少它有可能是有副作用的,因为它是用来启动一个服务的)。尽管如此,这个方法还是享受到了函数的“一等公民”待遇(作为值传递)。由此可见,Java 8中一个方法可以看做是一个函数,但是Java并不强制这个方法必须是无副作用的。
3.2. 函数式编程为并发编程提供了便利
函数式编程为并发编程提供了便利。函数是无副作用的,这就意味着这函数必须是无状态(Stateless)。无状态是多线程编程和函数式编程的共同好友。我们知道,在多线程编程中,如果多个线程之间存在共享可变状态(Shared Mutable State),那么为了确保线程安全我们往往需要借助锁,而锁除了其开销较大之外还存在可能导致死锁等问题。相反,如果多个线程之间不存在共享状态(或者仅存在只读状态),那么这些线程是可以并行(Parallel)的。这不仅有利于提高系统的并发性,也使得代码更为简单。函数也是类似,既然函数是(必须是)无状态的,那么多个线程同时执行一个函数的时候也就无需加锁,这既简化了代码又有利于提供系统的并发性。
4. 作者简介
黄文海,著有《Java多线程编程实战指南(核心篇)》、《Java多线程编程实战指南(设计模式篇)》。
5. 参考资料
1.Raoul-Gabriel Urma等.Java 8实战.人民邮电出版社,2016
2.黄文海.Java多线程编程实战指南(核心篇).电子工业出版社,2017
3.黄文海.Java多线程编程实战指南(设计模式篇).电子工业出版社,2015
相关推荐
读书笔记:java 函数式编程学习
Java 8是Java语言的一个重要版本,引入了大量新特性,其中最具革命性的就是函数式编程的支持。函数式编程是一种编程范式,它强调将计算视为函数的组合,避免可变状态和副作用,使得代码更加简洁、易于测试和并行化。...
本学习笔记主要涵盖了Java的基础知识,包括面向对象、集合、IO流、多线程、反射与动态代理以及Java 8的新特性等方面,旨在帮助初学者或有经验的开发者巩固和提升Java编程技能。 1. 面向对象(OOP):Java的核心是...
读书笔记:《Scala与Clojure函数式编程模式Java虚拟机高效编程》学习代码记录
12. **Java 8及更高版本的新特性**:涵盖Lambda表达式、函数式编程、Stream API、日期时间API等Java新特性,这些都是现代Java开发不可或缺的部分。 通过学习这份Markdown版的Java学习笔记,不仅可以掌握Java编程的...
读书笔记:函数式编程 scala,java8,groovy 学习
总的来说,这个压缩包提供的学习资料涵盖了Java函数式接口的理论和实践,以及Java Web开发中的关键概念,对想要深入学习Java和Java Web技术的开发者来说是一份宝贵的资源。通过深入学习和实践,开发者可以掌握使用...
读书笔记:记录对算法、数据结构以及Java 8之后函数式编程的学习
1. **lambda表达式**:JDK 8引入了lambda表达式,简化了函数式编程。它允许将匿名函数作为方法参数,或者用作方法返回值。Lambda表达式使得代码更加简洁,尤其在处理集合和并发操作时效果显著。 2. **函数式接口**...
《Java JDK 8学习笔记》是由林信良教授在2015年3月出版的一本详尽解析Java SE 8新特性的书籍,由清华大学出版社发行。这本书共计643页,内容完整且清晰,包含目录和书签,便于读者高效地查阅和学习。 在Java JDK 8...
9. **函数式编程**:Java 8引入了Lambda表达式和Stream API,这使得Java支持了函数式编程风格,笔记可能涵盖这些新特性的讲解。 10. **多线程**:Java提供了丰富的API支持并发编程,如Thread、Runnable接口,以及...
函数式编程的概念在Java 8引入后变得重要,Lambda表达式和Stream API使得代码更加简洁和高效。此外,Java的并发编程也是一大亮点,线程和同步机制(如synchronized关键字、volatile变量、Lock接口)使多线程环境下...
Lambda表达式提供了一种简洁的语法来表示匿名函数,使得函数式编程风格在Java中成为可能。通过这种方式,开发者可以更加灵活地处理集合,特别是在并行流和函数接口如Runnable、Comparator或Function中。 Java 8引入...
最后,随着Java 8及后续版本的发布,函数式编程的引入、Lambda表达式、Stream API等新特性为Java带来了更多可能性。这些新的工具和概念使得Java在保持其强大功能的同时,也变得更加简洁和高效。 总的来说,"java...
12. **Lambda表达式**:Java 8引入的新特性,用于简化函数式编程,包括函数接口,lambda语法,流(Stream API)及并行流。 13. **日期时间API**:Java 8改进的日期和时间处理,如LocalDate, LocalDateTime, Duration...
这份压缩包文件"Java学习笔记&工作经验总结.rar"包含了多个PDF文档,分别涵盖了Java的基础知识、高级特性、数据结构以及学员的学习总结,是深入理解Java编程的宝贵资料。 1. **Java SE基础全程学习笔记.pdf**: 这...
在Java编程世界中,Lambda表达式是Java 8引入的一项重大更新,极大地简化了函数式编程。Lambda表达式是一种匿名函数,可以被视为没有名称的方法,允许我们以更简洁的方式传递代码块。这种特性在处理集合和事件驱动...
20. Stream API:处理集合的新方式,支持函数式编程风格。 这份"java 学习笔记大全"应该覆盖了以上所有知识点,通过学习和实践,你可以掌握Java编程的核心技能,无论是进行简单的控制台程序开发,还是复杂的分布式...
### CoreJava学习笔记 #### 一、JAVA特点与运行原理 **JAVA特点:** 1. **简单性**:Java的设计者们将C++语言中许多不易理解和容易混淆的部分去除,使得Java更容易理解与掌握。 2. **面向对象**:Java几乎一切都...
函数式编程的概念也在Java 8中得到了强化,学习者将了解到如何使用函数式接口、方法引用和构造器引用,以及如何在集合操作中应用函数式编程思想。此外,日期时间API的更新也是一个亮点,它提供了更强大、更直观的...