让我先声明一下。从函数式编程的角度来看,下面的解释绝不是精确的或绝对准确的。相反,我将专注于解释的清晰性和简单性,以使尽可能多的Java开发人员了解这个美丽的世界。几年前,当我开始深入研究函数式编程时,我很快发现其中有丰富的信息,但对于几乎完全具有命令式背景的普通Java开发人员来说,这几乎是不可理解的。今天,事情正在慢慢发生变化。例如,有许多文章解释了基本的FP概念(参考:PracticalFunctionalJava(PFJ)简介)以及它们如何应用于Java。或者一篇解释如何正确使用Java流的文章。但是Monad仍然不在这些文章的重点范围内。我不知道为什么会这样,但我会尽力填补空白。那么,什么是单子?Monad是……一种设计模式。就这么简单。这种设计模式由两部分组成:Monad是一个值的容器。对于每个monad,都有将值包装到monad中的方法。Monads对其中包含的值实施“控制反转”。为了实现这一点,Monad提供了接受函数的方法。这些函数接受与存储在monad中的类型相同的值,并返回转换后的值。转换后的值被包装到与源值相同的Monad中。要理解模式的第二部分,我们可以看一下Monad的接口:interfaceMonad{Monadmap(Functionmapper);MonadflatMap(Function>mapper);}当然具体的Monad通常有更丰富的接口,但这两个方法应该是肯定存在的。乍一看,接受函数而不是访问值没有太大区别。事实上,这让monad可以完全控制如何以及何时应用转换函数。当您调用getter时,您会立即想要该值。在monad转换的情况下,它可能会立即应用或根本不应用,或者它的应用可能会延迟。由于无法直接访问内部值,monad可以表示甚至还不可用的值!下面我将展示一些monad的示例以及它们可以解决的问题。Monad缺失值或Optional/Maybe场景这个monad有很多名字——Maybe、Option、Optional。最后一个听起来很熟悉,不是吗?好吧,因为Java8Optional是Java平台的一部分。不幸的是,JavaOptional实现过于尊重传统的命令式方法,从而降低了它的用处。特别是Optional允许应用程序使用.get()方法获取值。如果缺少值,它甚至会抛出NPE。因此,Optional的使用通常仅限于表达潜在缺失值的返回,尽管这只是潜在用法的一小部分。也许monad的目的是表示可能缺失的值。传统上,这个角色在Java中一直为null保留。不幸的是,这会导致许多不同的问题,包括著名的NullPointerException。例如,如果您希望某些参数或某些返回值可以为空,则应在使用前检查它:publicUserProfileResponsegetUserProfileHandler(finalUser.IduserId){如果(用户==null){返回UserProfileResponse.error(USER_NOT_FOUND);}finalUserProfileDetailsdetails=userProfileService.findById(userId);if(details==null){returnUserProfileResponse.of(user,UserProfileDetails.defaultDetails());}returnUserProfile.of(user,details);}看起来眼熟吗?当然。让我们看看OptionMonad如何改变它(为简洁起见使用静态导入):ofNullable(userProfileService.findById(userId)).orElseGet(UserProfileDetails::defaultDetails))).orElseGet(()->UserProfileResponse.error(USER_NOT_FOUND));“干扰”也更少。这个例子展示了monadic“控制反转”是多么方便:转换不需要检查是否为null,它们只在值实际可用时才被调用。“如果/当值可用时做某事”是开始方便地使用Monad的关键心态。请注意,上面的示例保持原始API不变。但是更广泛地使用该方法并更改API以便它们返回Optional而不是null是有意义的:userProfileService.findById(userId).map(profile->UserProfileResponse.of(user,profile)));一些观察:代码更清晰并且包含几乎为零的样板文件。所有类型都是自动派生的。虽然并非总是如此,但在绝大多数情况下,类型是由编译器派生的——尽管与Scala相比,Java中的类型推断较弱。没有明确的错误处理,相反我们可以专注于“快乐的日子场景”。所有转换都可以方便地组合和链接,而不会破坏或干扰主要业务逻辑。事实上,上述属性对所有Monad都是通用的。折腾还是不折腾是个问题事情并不总是如我们所愿,我们的应用程序生活在现实世界中,充满痛苦、错误和失误。有时我们可以用它们做点什么,有时我们不能。如果我们无能为力,我们至少要通知调用者事情没有按照我们的预期进行。在Java中,我们传统上有两种机制来通知调用者出现问题:返回一个特殊值(通常为null)抛出异常而不是返回null我们还可以返回OptionMonad(见上文),但这通常是不够的,因为需要有关错误的更多详细信息。通常在这种情况下我们抛出一个异常。但是这种方法有一个问题。事实上,很少有问题。异常会中断执行流程异常会增加很多精神开销异常导致的精神开销取决于异常的类型:已检查的异常迫使您在此处处理它们,或者在签名中声明它们并将麻烦转移给调用者未检查的异常会导致相同级别的问题,但编译器不支持不知道哪个更糟。EitherMonad来了我们先来分析一下这个问题。我们想要返回的是一些特殊值,它可以是两种可能的事物之一:结果值(成功时)或错误(失败时)。请注意,这些东西是相互排斥的——如果我们返回一个值,我们不需要携带错误,反之亦然。上面几乎是对EitherMonad的精确描述:任何给定的实例只包含一个值,并且该值具有两种可能的类型之一。任何Monad的接口都可以这样描述:interfaceEither{EithermapLeft(Functionmapper);EitherflatMapLeft(Function>映射器);EithermapLeft(Function映射器);EitherflatMapLeft(Function>mapper);}这个接口非常冗长,因为它在左右值方面是对称的。对于更窄的用例,当我们需要传达成功或错误时,这意味着我们需要就某种约定达成一致——哪种类型(第一种或第二种)将保存错误,哪种类型将保存值。在这种情况下,Either的对称性质使其更容易出错,因为很容易在代码中无意中交换错误值和成功值。虽然此问题很可能会被编译器捕获,但最好针对此特定用例进行定制。如果我们修复其中一种类型,就可以做到这一点。显然,修复错误类型更方便,因为Java程序员习惯于从单个Throwable类型派生所有错误和异常。ResultMonad——任一Monad专门用于错误处理和传播。因此,让我们假设所有错误都实现相同的接口,我们称之为失败。现在我们可以简化和减少接口:interfaceResult{Resultmap(Functionmapper);ResultflatMap(Function>mapper);}ResultMonadAPI看起来与MaybeMonad的API非常相似。使用这个Monad,我们可以重写前面的例子:UserProfileResponse.of(user,profile)));好吧,它与上面的示例基本相同,唯一的变化是Monad—Result而不是Optional。与前面的例子不同,我们有关于错误的完整信息,所以我们可以在上面做一些事情。但是尽管完整的错误处理代码仍然很简单并且专注于业务逻辑。“承诺是一个大词。它要么成就某事,要么破坏某事。”我要展示的下一个Monad是PromiseMonad。不可否认,我还没有找到关于Promises是否是monad的明确答案。不同的作者对此有不同的看法。我纯粹是从实用的角度来看它:它的外观和行为与其他monad非常相似,所以我认为它们是monad。PromiseMonad代表一个(可能还不可用的)值。从某种意义上说,它与MaybeMonad非常相似。PromiseMonads可以用来表示,例如,对外部服务或数据库的请求结果,读取或写入文件等。基本上它可以表示任何需要I/O和时间来完成的事情。Promises支持我们在其他Monad中观察到的相同心态——“如果/当值可用时做某事”。请注意,由于无法预测操作的成功,因此让Promise不代表值本身而是代表其中包含值的Result会很方便。要了解它是如何工作的,让我们看一下下面的示例:...publicinterfaceArticleService{//返回指定用户发布的指定主题的文章列表Promise>userFeed(finalCollectiontopics,finalCollectionusers);}...publicinterfaceTopicService{//返回用户创建的主题列表Promise>topicsByUser(finalUser.IduserId,finalOrderorder);}...publicclassUserTopicHandler{privatefinalArticleServicearticleService;私人最终主题服务主题服务;publicUserTopicHandler(finalArticleServicearticleService,finalTopicServicetopicService){this.articleService=articleService;this.topicService=topicService;}publicPromise>userTopicHandler(finalUser.IduserId){returntopicService.topicsByUser(userId,Order.ANY).flatMap(topicsList->ararticleService.articlesByUserTopics(userId,topicsList.map(Topic::id)));}}为了提供整个上下文,我包括了两个必要的接口,但实际上有趣的部分是userTopicHandler()方法,尽管其简单性值得怀疑:调用TopicService并检索由提供的用户创建的主题列表成功后获取主题列表,该方法提取主题ID,然后调用ArticleService,它获取指定主题的用户创建文章列表执行端到端错误处理PostscriptMonads是非常强大和方便的工具。以“当价值可用时就做”的心态编写代码需要一些时间来适应,但一旦你开始使用它,它会让你的生活变得更轻松。它允许将大量精神开销卸载给编译器,并使许多错误在编译时而不是运行时不可能或检测不到。本文翻译自:【Monads的美丽世界-DEV社区】(https://dev.to/siy/beautiful-...)