lambda 加和
几周前,我写了关于Java 8 lambda的介绍 。 在本简介中,我解释了什么是lambda以及如何将它们与Java 8中也引入的新Stream API结合使用。
Stream API为集合提供了更实用的接口。 此接口在很大程度上取决于lambda。 但是,lambda不仅具有改进的收集处理能力,还具有更多优势
Lambda为您提供了构建更流畅的API的机会。 为了说明这一点,作为示例,我喜欢使用UserStore
,它有助于使用数据库获取和保存用户。 它的公共API通常如下所示。
public interface UserStore {User find(Long id);List<User> findByLastname(String lastname);List<User> findByCompany(String company);..
}
findBy
方法的列表通常比我在此处包括的两个方法更长。 随着系统的发展,可能还会有其他人。 尽管可行,但实际上所有这些方法都可以完成相同的事情。 他们返回具有匹配特定值的属性的所有用户。
一些框架提供了解决此问题的方法。 如果您使用过Hibernate,您可能会知道它们通过findByExample
提供了解决方法,其中您将User
作为示例对象提供了查询的属性和值。 使用此示例对象中设置的任何值进行查询,而从查询中排除任何为null
字段。 您可以对此行为进行一些调整,但是这种方法存在许多问题。 考虑默认值,必填字段(即无法填写的字段)
null
)和不变性。 iBatis,MyBatis以及Spring Data使用代码生成来节省您实现所有这些方法的时间,从而使API膨胀到findBy
方法的列表。
这些变通办法可能会走很长一段路,但是它们确实留下了自己的特定问题。 另一种方法是使用lambda。
Lambda可以帮助我们将查询部分与过滤器规范分离。 让我们将findBy
函数更改为接受lambda的单个函数。
public interface UserStore {User find(Long id);List<User> findBy(Predicate<User> p);
}
那是一个更好的API。 显然,谓词检查User
对象有点天真。 您通常希望使用数据库查询进行过滤。 尽管如此,它仍然很好地满足了本示例的目的,您可以尝试使用自己的lambda来使用数据库查询进行过滤。 [注意: Predicate
是Java 8附带的,位于java.util.function
包中。
至少在以前的API中,这些谓词被捆绑在一个地方之前,您可能会生气,我们仍然可以捆绑(通用)谓词。 例如,通过创建一个包含它们的实用程序类UserPredicates
。
public final class UserPredicates {public static Predicate<User> lastname(String matcher) {return candidate -> matcher.equals(candidate.getLastname());}
}
使用新的UserStore
API变得非常简单。
static import UserPredicates.lastname;userStore.findBy(lastname("<lastname>");
但是, UserStore
中还有一件事确实让我感到困扰。 find(id)
函数返回一个用户。 但是,如果没有这样的用户怎么办?
可选的
为了对此进行改进,我们可以(并且应该)查看Java 8的另一个新功能,Optional。 这是monad的Java实现。 它看起来很像Scala的Option
。
使用Optional
我们可以更好地表示一个函数可以返回一个值,但不一定返回一个值,并防止使用null
。 在我们的find(id)
示例中,返回Optional
明确表示我们可以找到具有所请求ID的用户,但可能不存在这样的用户。
public interface UserStore {Optional<User> find(Long id);List<User> findBy(Predicate p);
}
该API现在不仅记录了您可能会获得用户的事实,而且从未返回null
。 我认为永不返回null
的API更安全。 有一天,一个新的程序员可能没有意识到find
可以返回null
并且结果是一个null指针异常。 只是希望团队能够在生产之前就抓住它。 只要不使用null
,就很容易防止空指针异常。
我们可以使用Optional
上的函数从用户那里获取值(如果有),或者从默认值获取。 例如,为了安全地获取用户的姓氏,我们编写以下内容。
Optional<User> user = userStore.find(id);
String lastname = user.map(User::getLastname).orElse("");
这段代码具有很强的表达力,不需要很多解释。 如果有用户,请获取其姓氏。 否则,获取一个空字符串。
如果我们需要向用户发送密码重置电子邮件(如果找到)怎么办?
Optional<User> user = userStore.find(id);
user.ifPresent(passwordReset::send);
如果找到用户,则发送密码重置,否则什么也没有发生。
由于Java不像其他可能提供的其他语言(例如Haskell,Clojure和Scala)那样支持解构,因此我们仅限于Optional
的功能。 这使得Optional
比其在任何一种语言中的等效性都弱。
建造者
当然,不仅存储库的API都可以从lambda中受益。 Optional
也是受益于lambda的API的一个很好的示例。 就我个人而言,我还发现lambda特别有用,可以代替过去的过往建造者。 通常不通过将特定的构建器传递给函数,而是通过从函数中生成一个构建器来改善去耦。 让我向您展示一个示例,用于发送电子邮件以阐明该想法。
public interface Mailer {void sendTextMessage(TextMessageBuilder message);void sendMimeMessage(MimeMessageBuilder message);
}
要使用Mailer
我们需要将特定的构建器传递给它。 这些构建器具有通用的界面,但是它们构建的消息类型不同。 Mailer
具有不同的方法,因为它必须根据所使用的类型添加不同的信息。 因此,任何客户端代码都紧密耦合以传递正确的构建器。
您可能会怀疑,这是lambda有用的地方。 Mailer
函数可以创建所需的生成器并将其产生给lambda,而不是要求客户端创建生成器并将其传递给客户端。
public interface Mailer {void sendTextMessage(MessageConfigurator configurator);void sendMimeMessage(MessageConfigurator configurator);@FunctionalInterfaceinterface MessageConfigurator {MessageBuilder configure(MessageBuilder message);}
}
要使用Mailer
我们需要做的就是提供一个lambda来构建消息。
mailer.sendTextMessage(message ->message.from(sender).to(recipients).subject("APIs").body("Lambdas can make for more fluent and stable APIs")
);
API现在更加稳定。 客户端代码与特定构建器中的任何更改都没有关联,只要构建器上的功能保持兼容就不会中断。
正如示例帮助我展示的那样,lambda可以帮助您构建更流畅和稳定的API,这些API更具意图。 这些API不需要太多文档供其他程序员使用,因为实际上很难使他们弄错。 作为一般准则,我更喜欢清晰明了的代码而不是文档。 修正,不记录。
当然,我在本文中仅显示了一些示例。 Lambda不仅适用于此处的示例,而且适用范围更广。 我希望本文能为您提供一些有关lambda可以帮助您的新见解,并希望您能想到它们如何改善您的代码。
翻译自: https://www.javacodegeeks.com/2013/11/lambdas-for-fluent-and-stable-apis.html
lambda 加和