在范式和技术堆栈不断变化的世界里,保持竞争力、提高生产力和质量的斗争有时被证明是挑战。
本文首先展示了函数编程(FP )的优越性,特别希望加强Java编码体验。 当我尝试将范式转换为函数型编程时,我将重复几个最重要的理由。 请记住,这绝不是一个大的创新。 我相信FP自70年代以来一直存在,但只在这几年获得了吸引力,增加了人们的兴趣。 让我们看看为什么!
随着
并发
多核/多线程处理器的出现,函数型编程变得更加引人注目。 这绝不是简单的巧合。 函数式编程鼓励使用不可变对象,属性和变量必须是不能更改值的数据容器。)。 请看以下代码:私有inta编号;
公共编号参数{1} {2}
this.a编号=编号参数;
}
很简单吧? 你以前可能看过很多次。 但是,如果两个线程同时访问setNumber方法会怎么样? 可以想象可能会发生某种阻滞。 最后,只有访问此方法的最后一个线程对aNumber的值有最终决定权。 但是,这是不确定的,取决于各种因素,因此可以说方法setNumber不是引用透明的(详细情况将在后面叙述)。 在这种情况下,不变性有助于推理代码。 因为我确信无论线程访问多少其一部分,其值始终相同。
的透明度和可测试性
在函数型编程中,推荐使用引用透明函数。 那是什么意思? 是的,这意味着函数总是被其值取代,一切都不会改变。 让我们看看下面的代码块。
导入Java.util.random;
publicclassrandomvalueprovider {
公共int几何随机值(
随机随机=新随机(;
returnrand.nextint(50;
}
}
getSomeRandomValue ()方法的引用是透明的吗? 请试着换成那个值。 那个总是没有变化吗? 可能做不到。 尽可能地尝试使用参考透明函数可能是个好习惯。 想象一下,测试上面的getSomeRandomValue方法比测试下面的方法要困难得多。
公共获取(内部,内部b ) {
返回甲乙;
}
具有隐含名称的小函数通常优于表示返回值的表达式。 好处是,我们可以保证我们建立的(至少大部分)函数是确定的。 这样可以提高代码推理的方便性和测试性。
应用
函数组合
原则后,操作变得更简单,更可靠。 根据这个事实,通过组合各种功能,可以创建更复杂的行为。 将其他函数作为参数或返回函数一起接收的函数称为高阶函数。下面的一些示例来自Java 8流API。 2014年成为JDK的一部分以来,已经在流中写入了大量的内容。 现在,我想用Consumer函数界面给大家举个简单的例子:
publicvoidprocesslistofnumbers (监听器集成处理器) {
返回列表编号. stream (
. foreach (编号处理器.接受)编号;
}
客户端代码:
listintegernumbers=arrays.as list (5,6,7,8 );
consumerintegernumberprinter=n-system.out.println (
处理序列号(编号,编号打印机);
方法processListOfNumbers是函数组合的示例,有时也称为高阶函数。 在Java中,函数(包括suppliers、consumers )是对象。 这意味着可以应用它们,然后将其组合起来作为参数传递。
以FP风格编写的应用程序更加强大
在用函数表达式编写代码时,不容易发生APP本身的错误。 这是因为移动组件可以使APP更容易预测,更容易推理,更能适应逆境。 函数组合和不变性的常见用法确保了由于APP的不同部分的状态发生变化而导致的所有错误在缺省情况下消失 。该应用程序将更加强大,可以提供更短的开发 - >测试 - >调试迭代循环。专注于“什么”而不是“如何”
假设我们有一个getUserById方法(在同一个类中)负责从数据库中获取相应的User对象,请使用以下Java流的经典应用程序:
public List<User> getAdultUsers(List<Integer> listOfUserIds) {
return listOfUserIds.stream().map(this::getUserById)
.filter(user -> user.getAge() >= 18)
.collect(Collectors.toList());
}
现在让我们看看非函数风格的相同代码:
public List<User> getAdultUsers(List<Integer> listOfUserIds) {
List<User> adultUsers = new ArrayList<>();
for(int id: listOfUserIds) {
User user = getUserById(id);
if (user.getAge() >= 18) {
adultUsers.add(user);
}
}
return adultUsers;
}
除了第二段略长外,我们还可以注意到这段代码需要花时间来“解释”此操作的每个步骤是如何完成的:创建一个空白列表,迭代id,获取每个用户,添加一些基于条件表达式的用户到空白列表,完成并返回收集的用户。
另一方面,在第一段中,功能方法更侧重于“什么”。代码在做什么?它将一些ID映射到某些用户,将其过滤掉并将其余用户收集到列表中。有人可能会争辩说,通过在第二种情况下提取小方法可以实现同样的目的,但我相信第一段的流和函数作为数据方法仍然更好。它将我们的函数置于业务逻辑的最前沿,具有与在我们的应用程序中移动的任何其他数据相同的状态。
更好看的方法签名
当我们的功能从命令式转变为函数式时,命名也一目了然,以下方法很难通过其签名来阅读:
public void executeProcess() {
// executing some mysterious stuff!
}
代码做了什么?为什么它不想要我们的任何输入参数,为什么它不想返回任何结果?你能测试一下吗?你能读懂吗?不容易吧。如果像下面这样看起来如何?
public ExecutionStatus executeProcess(Process processToBeExecuted) {
// execute "processToBeExecuted" and return some status
}
只需采用一些FP概念,并在这个简单的情况下使用它们,代码就变得更具可读性。函数现在是可通过查看它的方法签名来说明自己(虽然方法名称可能仍然可以改进)。它需要一个Process输入并以某种ExecutionStatus状态返回。除了直接在代码中提供更好的“文档”之外,签名变得更有意义。执行什么Process?我们可以查看Process对象并在运行时查看它。它发挥作用后会发生什么?我然后可在我们的流程中使用该函数的返回结果。
结论
如今,无论我们是在处理遗留代码还是新建绿地项目,我们都可以使用一些东西来提高日常工作的质量和生产率。函数编程从不同的角度进行编码。它通常意味着更简洁,但如果给予适当的照顾,也会提高可读性。它还帮助我们解决一些常见的痛苦,例如并发编程中的竞争条件,老实的保温杯对象状态错误或难以遵循的代码。