本系列文章译自Venkat Subramaniam的
Functional Programming in Java
第四节:进化而非革命
我们用不着转向别的语言,就能享受函数式编程带来的好处;需要改变的只是使用Java的一些方式。C++,Java,C#这些语言都支持命令式和面向对象的编程。不过现在它们都开始投入函数式编程的怀抱里了。我们刚才已经看到了这两种风格的代码,并讨论了函数式编程能带来的好处。现在我们来看下它的一些关键概念和例子来帮助我们学习这种新的风格。
Java语言的开发团队花费了大量的时间和精力把函数式编程的能力添加到了Java语言和JDK里。要享受它带来的好处,我们得先介绍几个新的概念。我们只要遵循下面几条规则就能提升我们的代码质量:
声明式
提倡不可变性
避免副作用
优先使用表达式而不是语句
使用高阶函数进行设计
我们来看下这几条实践准则。
声明式
我们所熟悉的命令式编程的核心就是可变性和命令驱动的编程。我们创建变量,然后不断修改它们的值。我们还提供了要执行的详细的指令,比如生成迭代的索引标志,增加它的值,检查循环是否结束,更新数组的第N个元素等。在过去由于工具的特性和硬件的限制,我们只能这么写代码。
我们也看到了,在一个不可变集合上,声明式的contains方法比命令式的更容易使用。所有的难题和低级的操作都在库函数里来实现了,我们不用再关心这些细节。就冲着简单这点,我们也应该使用声明式编程。不可变性和声明式编程是函数式编程的精髓,现在Java终于把它变成了现实。
提倡不可变性
变量可变的代码会有很多活动路径。改的东西越多,越容易破坏原有的结构,并引入更多的错误。有多个变量被修改的代码难于理解也很难进行并行化。不可变性从根本上消除了这些烦恼。
Java支持不可变性但没有强制要求——但我们可以。我们需要改变修改对象状态这个旧习惯。我们要尽可能的使用不可变的对象。
声明变量,成员和参数的时候,尽量声明为final的,就像Joshua Bloch在” Effective Java“里说的那句名言那样,“把对象当成不可变的吧”。
当创建对象的时候,尽量创建不可变的对象,比如String这样的。创建集合的时候,也尽量创建不可变或者无法修改的集合,比如用Arrays.asList()和Collections的unmodifiableList()这样的方法。
避免了可变性我们才可以写出纯粹的函数——也就是,没有副作用的函数。
避免副作用
假设你在写一段代码到网上去抓取一支股票的价格然后写到一个共享变量里。如果我们有很多价格要抓取,我们得串行的执行这些费时的操作。如果我们想借助多线程的能力,我们得处理线程和同步带来的麻烦事,防止出现竞争条件。最后的结果是程序的性能很差,为了维护线程而废寝忘食。如果消除了副作用,我们完全可以避免这些问题。
没有副作用的函数推崇的是不可变性,在它的作用域内不会修改任何输入或者别的东西。这种函数可读性强,错误少,容易优化。由于没有副作用,也不用再担心什么竞争条件或者并发修改了。不仅如此,我们还可以很容易并行执行这些函数,我们将在145页的来讨论这个。
优先使用表达式
语句是个烫手的山芋,因为它强制进行修改。表达式提升了不可变性和函数组合的能力。比如,我们先用for语句计算折扣后的总价。这样的代码导致了可变性以及冗长的代码。使用map和sum方法的表达性更强的声明式的版本后,不仅避免了修改操作,同时还能把函数串联起来。
写代码的时候应该尽量使用表达式,而不是语句。这样使得代码更简洁易懂。代码会顺着业务逻辑执行,就像我们描述问题的时候那样。如果需求变动,简洁的版本无疑更容易修改。
使用高阶函数进行设计
Java不像Haskell那些函数式语言那样强制要求不可变,它允许我们修改变量。因此,Java不是,也永远不会是,一个纯粹的函数式编程语言。然而,我们可以在Java里使用高阶函数进行函数式编程。
高阶函数使得重用更上一层楼。有了高阶函数我们可以很方便的重用那些小而专,内聚性强的成熟的代码。
在OOP中我们习惯了给方法传递给对象,在方法里面创建新的对象,然后返回对象。高阶函数对函数做的事情就跟方法对对象做的一样。有了高阶函数我们可以
把函数传给函数
在函数内创建新的函数
在函数内返回函数
我们已经见过一个把函数传参给另一个函数的例子了,在后面我们还会看到创建函数和返回函数的示例。我们先再看一遍“把函数传参给函数”的那个例子
prices.stream()
.filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9)))
report erratum ? discuss
.reduce(BigDecimal.ZERO, BigDecimal::add);
在这段代码中我们把函数price -> price.multiply(BigDecimal.valueOf(0.9)),传给了map函数。传递的这个函数是在调用高阶函数map的时候才创建的。通常来说一个函数有函数体,函数名,参数列表,返回值。这个实时创建的函数有一个参数列表后面跟着一个箭头(->),然后就是很短的一段函数体了。参数的类型由Java编译器来进行推导,返回的类型也是隐式的。这是个匿名函数,它没有名字。不过我们不叫它匿名函数,我们称之为lambda表达式。
匿名函数作为传参在Java并不算是什么新鲜事;我们之前也经常传递匿名内部类。即使匿名类只有一个方法,我们还是得走一遍创建类的仪式,然后对它进行实例化。有了lambda表达式我们可以享受轻量级的语法了。不仅如此,我们之前总是习惯把一些概念抽象成各种对象,现在我们可以将一些行为抽象成lambda表达式了。
用这种编码风格进行程序设计还是需要费些脑筋的。我们得把已经根深蒂固的命令式思维转变成函数式的。开始的时候可能有点痛苦,不过很快你就会习惯它了,随着不断的深入,那些非函数式的API逐渐就被抛到脑后了。
这个话题就先到这吧,我们来看看Java是如何处理lambda表达式的。我们之前总是把对象传给方法,现在我们可以把函数存储起来并传递它们。 我们来看下Java能够将函数作为参数背后的秘密。
第五节:加了点语法糖
用Java原有的功能也是可以实现这些的,不过lambda表达式加了点语法糖,省掉了一些步骤,使我们的工作更简单了。这样写出的代码不仅开发更快,也更能表达我们的想法。
过去我们用的很多接口都只有一个方法:像Runnable, Callable等等。这些接口在JDK库中随处可见,使用它们的地方通常用一个函数就能搞定。原来的这些只需要一个单方法接口的库函数现在可以传递轻量级函数了,多亏了这个通过函数式接口提供的语法糖。
函数式接口是只有一个抽象方法的接口。再看下那些只有一个方法的接口,Runnable,Callable等,都适用这个定义。JDK8里面有更多这类的接口——Function, Predicate, Comsumer, Supplier等(157页,附录1有更详细的接口列表)。函数式接口可以有多个static方法,和default方法,这些方法是在接口里面实现的。
我们可以用@FunctionalInterface注解来标注一个函数式接口。编译器不使用这个注解,不过有了它可以更明确的标识这个接口的类型。不止如此,如果我们用这个注解标注了一个接口,编译器会强制校验它是否符合函数式接口的规则。
如果一个方法接收函数式接口作为参数,我们可以传递的参数包括:
匿名内部类,最古老的方式
lambda表达式,就像我们在map方法里那样
方法或者构造器的引用(后面我们会讲到)
如果方法的参数是函数式接口的话,编译器会很乐意接受lambda表达式或者方法引用作为参数。
如果我们把一个lambda表达式传递给一个方法,编译器会先把这个表达式转化成对应的函数式接口的一个实例。这个转化可不止是生成一个内部类而已。同步生成的这个实例的方法对应于参数的函数式接口的抽象方法。比如,map方法接收函数式接口Function<T, R>作为参数。在调用map方法时,java编译器会同步生成它,就像下图所示的一样。
lambda表达式的参数必须和接口的抽象方法的参数匹配。这个生成的方法将返回lambda表达式的结果。如果返回类型不直接匹配抽象方法的话,这个方法会把返回值转化成合适的类型。
我们已经大概了解了下lambda表达式是如何传递给方法的。我们先来快速回顾一下刚讲的内容,然后开始我们lambda表达式的探索之旅。
总结
这是Java一个全新的领域。通过高阶函数,我们现在可以写出优雅流利的函数式风格的代码了。这样写出的代码,简洁易懂,错误少,利于维护和并行化。Java编译器发挥了它的魔力,在接收函数式接口参数的地方,我们可以传入lambda表达式或者方法引用。
我们现在可以进入lambda表达式以及为之改造的JDK库的世界来感觉它们的乐趣了。在下一章中,我们将从编程里面最常见的集合操作开始,发挥lambda表达式的威力。
译注:终于搞完无聊的第一章了。下一章开始会有更多实际的例子了。
未完待续,后续文章请继续关注
deepinmind。
原创文章转载请注明出处:
http://it.deepinmind.com
分享到:
相关推荐
Java函数式编程是一种编程范式,它强调使用函数作为程序的基本构建块,将计算视为函数的组合,并且尽可能避免改变状态和可变数据。在Java 8及更高版本中,函数式编程得到了官方的大力支持,引入了Lambda表达式、...
Java函数式编程是一种将函数作为一等公民的编程范式,它强调使用函数来构造程序,减少副作用,提高代码的可读性和可维护性。在Java 8及更高版本中,函数式编程得到了显著增强,引入了Lambda表达式、函数接口、Stream...
Java 8函数式编程
Java函数式编程是一种高效、简洁的编程范式,它在Java 8中得到了全面支持,大大改变了Java开发人员编写代码的方式。本套黑马程序员的Java函数式编程视频教程涵盖了Lambda表达式、Stream流以及函数式编程的核心概念,...
Java 8 函数式编程 Java 8 函数式编程是指在 Java 8 中引入的一种新的编程范式,它强加了额外的约束,即所有数据必须是不可变的,设置一次,永不改变。函数式编程将值传递给函数,该函数然后生成新值但从不修改...
Java8 函数式编程百度网盘共享,Java8 函数式编程百度网盘,Java8 百度网盘,Java8 函数式编程 PDF 开源资源, Java8 Lambdas 表达式百度网盘,Java8 Lambdas 百度网盘, 象征性的给一分意思意思
Java函数式编程是一种编程范式,它将计算视为数据处理,并强调程序的数据流和变换,而不是对指令进行控制。在Java 8及更高版本中,函数式编程得到了强大的支持,引入了Lambda表达式、Stream API以及函数式接口等概念...
Java 8是一个重要的Java语言版本,它引入了对函数式编程的支持,极大地提升了代码的简洁性和可读性,特别是对于集合操作。函数式编程是一种编程范式,它将计算视为函数的组合,而不是状态的改变或控制流程。在Java 8...
Java Lambda 函数式编程是Java 8引入的一项重要特性,它极大地简化了处理函数对象的方式,使得代码更加简洁、易读。Lambda表达式是函数式编程的核心,它允许我们将函数作为一个参数传递,或者将函数作为返回值。在这...
通过阅读《JavaScript函数式编程指南》,读者可以了解到函数式编程的基本概念和实现技巧,并且会发现JavaScript作为函数式编程语言的适用性。 函数式编程指南通常会涵盖以下几个重要知识点: 一等公民的函数:在...
### 深入理解Java函数式编程和Streams API #### 一、引言 随着Java 8的发布,函数式编程范式正式被引入到Java语言中,这标志着Java编程方式的重大转变。Java 8引入了Lambda表达式、方法引用等新特性,使得Java能够...
面向Java开发者的函数式编程是一种将函数式编程思想应用于Java开发中的实践方法。函数式编程是一种编程范式,强调程序数据的不可变性、避免副作用,并利用纯函数来构造软件。这种编程风格在处理复杂性、提高代码...
Java 8 是一个重要的Java版本,它引入了函数式编程的概念,极大地扩展了语言的功能,使得Java开发者可以采用更加简洁、高效的方式来编写代码。Richard Warburton 的《java 8 函数式编程》是一本深入解析这一主题的...
Java函数式编程是指利用函数式编程的思想和特性来开发Java应用程序。函数式编程强调将计算过程视为数学函数的求值,侧重于函数的组合、映射、过滤等操作,同时避免了可变状态和可变数据。 在Java中,函数式编程主要...
函数式编程(FP)是一种软件开发风格,它注重不依赖于编程状态的函数。函数式代码易于测试和复用,容易实现并发,且不容易受到bug的攻击。Scala是一种能很好支持函数式编程的新兴JVM语言。《Scala函数式编程》是针对...
在Java 8之前,重用通常依赖于对象和类型系统,但Java 8通过函数式编程引入了另一种重用机制,即通过函数实现代码重用。函数可以作为参数传递,也可以是函数的返回值,或者在函数中创建另一个函数,从而进一步提高...
Java 8是Java语言的一个重要版本,引入了大量新特性,其中最具革命性的就是函数式编程的支持。函数式编程在Java 8中的实现主要体现在Lambda表达式、Stream API以及方法引用等方面,这些新特性极大地提高了代码的简洁...
Java 函数式编程 Lambda表达式