Java 中的一种更轻松的函数式编程途径

声明:以下文章来自IBM官网,作者Venkat Subramaniam,本人和阅读者不得用于任何盈利途径。

函数式编程--编程思维转变

Java 开发人员早已习惯了命令式编程和面向对象的编程,因为 Java 语言从第一个版本开始就支持这些格式。在 Java 8

中,我们获得了一组强大的新的函数特性和语法。函数式编程已有数十年的历史,而且与面向对象的编程相比,函数式编程通常更简洁、更具表达力、更不容易出错,而且更容易并行化。所以在 Java

程序中引入函数特性是有充分理由的。尽管如此,函数式的编程需要对代码的设计方式进行一些改变。

关于本系列

Java 8 是自 Java 语言诞生以来进行的一次最重大更新 — 包含了非常丰富的新功能,您可能想知道从何处开始着手了解它。在本系列中,作者兼教师 Venkat

Subramaniam 提供了一种惯用的 Java 8 编程方法:这些简短的探索会激发您反思您认为理所当然的 Java 约定,同时逐步将新方法和语法集成到您的程序中。

我发现声明式思考(而不是命令式思考)可以简化向更加函数化编程的过渡。在新的Java 8 习惯用语系列的第一篇文章中,我将解释命令式、声明式和函数式编程之间的区别和共性。然后,将展示如何使用声明式思考方式逐步将函数方法集成到 Java 程序中。

命令式格式

经过针对命令式编程培训的开发人员已经习惯了告诉程序做什么和该如何做。这是一个简单示例:

清单 1. findNemo in the imperative

style

import java.util.*;

public class FindNemo {

public static void main(String[] args) {

List names =

Arrays.asList("Dory", "Gill", "Bruce", "Nemo", "Darla", "Marlin", "Jacques");

findNemo(names);

}

public static void findNemo(List names) {

boolean found = false;

for(String name : names) {

if(name.equals("Nemo")) {

found = true;

break;

}

}

if(found)

System.out.println("Found Nemo");

else

System.out.println("Sorry, Nemo not found");

}

}

findNemo()方法首先初始化一个可变的flag变量,也称为垃圾(garbage)变量。开发人员通常会随便给这些变量命名,比如f、t或temp,这些名称表明了我们对这些变量不应存在的一般态度。在本例中,该变量名为found。

接下来,该程序会在给定的names列表中循环,一次处理一个元素。它检查获得的名称是否与它寻找的值(在本例中为Nemo)相等。如果值匹配,那么它会将 flag 变量设置为true,并通知控制流“跳出”循环。

因为这是一个命令式编程的程序 — 许多 Java 开发人员最熟悉的格式 — 所以您需要定义程序的每一步:告诉它迭代每个元素,比较值,设置 flag

变量,然后跳出循环。命令式格式为您提供了完全的控制权,这有时是件好事。而另一方面,您需要执行所有工作。在许多情况下,可以减少工作量来提高效率。

声明式格式

声明式编程意味着,您仍会告诉程序要做什么,但将实现细节留给底层的函数库。让我们看看用声明式格式重新编写来自清单 1的findNemo方法时,会发生什么:

清单 2. findNemo in the declarative

style

public static void findNemo(List names) {

if(names.contains("Nemo"))

System.out.println("Found Nemo");

else

System.out.println("Sorry, Nemo not found");

}

首先请注意,这个版本中没有任何垃圾变量。您也没有将精力浪费在对集合的循环处理上,而是使用内置的contains()方法来完成工作。您仍然需要告诉程序要做什么 — 检查集合是否包含我们寻找的值 — 但将细节留给底层的方法。

在命令式示例中,您控制着迭代,而且程序完全按照要求来操作。在声明式版本中,您无需关心工作如何完成,只要它完成即可。contains()的实现可能不同,但只要结果符合预期,您可能就会很开心。花更少的精力获得同样的结果。

训练自己以声明式编程思考,这将大大简化向 Java 中的函数式编程的过渡。为什么呢?因为函数式编程是以声明式为基础而建立的。声明式思考试图逐步从命令式编程过渡到函数式编程。

函数式格式

尽管函数式格式的编程始终是声明式的,但简单地使用声明式编程并不等于函数式编程。这是因为函数式编程合并了声明式方法与高阶函数。图 1 直观地展示了命令式、声明式和函数式编程之间的关系。

图 1. 命令式、声明式和函数式编程的联系

Java 中的高阶函数

在 Java 中,要将对象传递给方法,在方法内创建对象,并从方法中返回对象。可以对函数执行同样的操作。也就是说,可以将函数传递给方法,在方法内创建函数,并从方法返回函数。

在此上下文中,方法是类的一部分 — 静态或实例 —          但函数对于方法而言是本地函数,不能特意与类或实例关联。可以接收、创建或返回函数的函数或方法被视为高阶函数

函数式编程示例

采用新编程的格式需要改变您思考程序的方式。可以通过简单的示例来实践这一过程,并逐步建立更复杂的程序。

清单 3. A Map in the imperative

style

import java.util.*;

public class UseMap {

public static void main(String[] args) {

Map pageVisits = new HashMap<>();

String page = "https://agiledeveloper.com";

incrementPageVisit(pageVisits, page);

incrementPageVisit(pageVisits, page);

System.out.println(pageVisits.get(page));

}

public static void incrementPageVisit(Map pageVisits, String page) {

if(!pageVisits.containsKey(page)) {

pageVisits.put(page, 0);

}

pageVisits.put(page, pageVisits.get(page) + 1);

}

}

清单 3中,main()函数创建了一个HashMap,其中包含一个网站的页面访问次数。同时,每次访问给定页面,incrementPageVisit()方法都会增加计数。我们将重点查看此方法。

incrementPageVisit()方法是使用命令式格式编写的:它的职责是递增给定页面的计数,将该计数存储在Map中。该方法不知道给定页面是否有计数,所以它首先会检查是否存在计数。如果不存在,那么它会插入一个 “0” 作为该页面的计数。然后获得该计数,递增它,并将新值存储在Map中。

声明式思考要求您将此方法的设计思路从 “如何做” 转变为 “做什么”。当调用方法incrementPageVisit()时,您希望将给定页面的计数初始化为 1 或将运行值递增 1。这就是做什么

因为您在执行声明式编程,所以下一步是扫描 JDK 库,查找Map接口中可以完成您的目标的方法 —          也就是说,寻找一个知道如何完成给定任务的内置方法。

事实证明,merge()方法能完美实现您的目标。清单 4 使用新的声明式方法修改来自清单 3的incrementPageVisit()方法。但是,在本例中,并不仅仅通过选择更智能的方法采用更加声明式的格式编程;因为merge()是一个高阶函数,所以新代码实际上是一个不错的函数式编程示例:

清单 4. A Map in the functional

style

public static void incrementPageVisit(Map pageVisits, String page) {

pageVisits.merge(page, 1, (oldValue, value) -> oldValue + value);

}

在清单 4 中,page作为第一个参数传递给merge():该键的值应该更新。第二个参数是给该键分配的初始值,如果Map中不存在该键(在本例中,该值为        “1”)。第三个参数(一个拉姆达表达式)接收 map 中该键对应的值作为其参数,并且将该值作为变量传递给 merge 方法中的第二个参数。        这个拉姆达表达式返回的是它的参数的和,实际上就是递增计数。

比较清单 4中的incrementPageVisit()方法中的一行代码与清单 3中的多行代码。清单 4中的程序是一个函数式编程示例,由此可见声明式考虑问题有助于我们的跳跃性思维。

结束语

在 Java 程序中采用函数式方法和语法有许多好处:代码简洁,更富于表达,不易出错,更容易并行化,而且通常比面向对象的代码更容易理解。我们面临的挑战在于将思维方式从命令式编程

— 绝大多数开发人员都熟悉它 — 转变为声明式思考。

尽管函数式编程不那么容易或直观,但您可以通过学习关注您想要程序实现的目的而不是关注您希望它执行的方式,从而实现思维上的巨大飞跃。通过允许底层函数库管理执行代码,您将逐步直观地了解高阶函数,它们是函数式编程的构建基块。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容