Java函数式编程:Javaslang入门

Java是一门古老的语言,并且有很多新手在Java(JVM)领域挑战它们。 但是Java 8到来并带来了一些有趣的功能。 这些有趣的功能使编写新的惊人框架(如Spark Web框架或Javaslang)成为可能 。

在本文中,我们将介绍将函数式编程引入Java的Javaslang。

函数式编程:这有什么用?

如今,似乎所有优秀的开发人员都希望进行一些功能编程。 因为他们以前想使用面向对象的编程。 我个人认为函数式编程可以很好地解决某些问题,而其他范例则更好。

在以下情况下,函数式编程非常有用:

  • 您可以将其与不变性配对:纯函数没有副作用,并且更容易推理。 纯函数意味着不变性,从而极大地简化了测试和调试。 但是,并非所有解决方案都能很好地代表不变性。 有时您只是拥有大量数据,这些数据在多个用户之间共享,并且您想就地进行更改。 在这种情况下,可变性是解决方法。
  • 您的代码取决于输入,而不取决于状态:如果某物取决于状态而不是输入,那么听起来对我来说更像是一个函数。 理想情况下,功能代码应非常明确地说明正在使用的信息(因此,仅应使用参数)。 这也意味着更多通用和可重用的功能。
  • 您具有独立的逻辑,这些逻辑之间的耦合程度不高:以小型,通用和可重用功能组织的功能代码非常有用
  • 您拥有要转换的数据流:我认为这是最容易看到函数式编程值的地方。 实际上,流在Java 8中引起了很多关注。

讨论图书馆

正如您可以在javaslang.com上阅读的那样 :

Java 8在我们的程序中引入了λc,但是“显然,JDK API不会帮助您编写简洁的功能逻辑(…)” – jOOQ™博客

Javaslang™是编写全面的功能性Java 8+程序的缺失部分和最佳解决方案。

正如我所看到的Javaslang一样:Java 8为我们提供了启用功能,以构建更简洁和可组合的代码。 但是它没有做最后一步。 它打开了一个空间,Javaslang到达了它。

Javaslang带来了许多功能:

  • currying: currying是功能的部分应用
  • 模式匹配:让我们将其视为函数式编程的动态调度
  • 故障处理:因为异常不利于功能组合
  • 要么:这是函数编程中非常常见的另一种结构。 典型的示例是一个函数,当事情进展顺利时返回一个值,而当事情进展不好时返回错误消息
  • 元组:元组是对象的一种很好的轻量级替代方案,非常适合返回多个值。 只是不要偷懒,并在有意义的时候使用类
  • 备注:这是功能的缓存

对于具有函数式编程经验的开发人员来说,这一切都是众所周知的。 对于我们其余的人,让我们看一下如何在实践中使用这些东西。

好的,但是实际上我们如何使用这些东西?

显然,为Javaslang的每个功能显示一个示例远远超出了本文的范围。 让我们看看如何使用其中的一些,尤其是让我们专注于函数式编程的基本内容:函数操纵。

鉴于我沉迷于Java代码的操作,我们将了解如何使用Javaslang检查某些Java代码的抽象语法树(AST)。 使用心爱的JavaParser可以轻松获得AST。

如果使用gradle,则build.gradle文件可能如下所示:

apply plugin: 'java'
apply plugin: 'idea'sourceCompatibility = 1.8repositories {mavenCentral()
}dependencies {compile "com.javaslang:javaslang:2.0.0-beta"compile "com.github.javaparser:javaparser-core:2.3.0"testCompile "junit:junit:4.12"
}

我们将实现非常简单的查询。 仅查看AST即可获得解答,而无需求解符号。 如果您想使用Java AST并求解符号,则可能需要看一下我的这个项目: java-symbol-solver 。

例如:

  • 用给定名称的方法查找类
  • 使用具有给定数量参数的方法查找类
  • 查找具有给定名称的类
  • 结合previos查询

让我们从给出CompilationUnit的函数开始,方法名称返回一个TypeDeclarations列表,该列表定义了使用该名称的方法。 对于从未使用过JavaParser的用户: CompilationUnit表示整个Java文件,可能包含几个TypeDeclaration。 TypeDeclaration可以是类,接口,枚举或注释声明。

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import javaslang.Function1;
import javaslang.Function2;
import javaslang.collection.List;.../*** Helper method*/public static boolean hasMethodNamed(TypeDeclaration typeDeclaration, String methodName) {return List.ofAll(typeDeclaration.getMembers()).map(Match.whenType(MethodDeclaration.class).then((t)-> Option.of(t.getName())).otherwise(() -> Option.none())).map((n)->n.isDefined() && n.get().equals(methodName)).reduce((a, b)->a || b);}public static List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) {return List.ofAll(cu.getTypes()).filter((t) -> hasMethodNamed(t, methodName));}

getTypesWithThisMethod非常简单:我们在CompilationUnit( cu.getTypes() )中获取所有类型,并对它们进行过滤,仅选择具有该名称的方法的类型。 真正的工作在hasMethodNamed中完成。

hasMethodNamed宽 E从我们java.util.List(List.ofAll(typeDeclaration.getMembers())创建一个javaslang.collection.List开始,然后我们认为我们只是在MethodDeclarations感兴趣我们不是在外地感兴趣声明或类型声明中包含的其他内容,因此,如果方法名称与所需的methodName相匹配,则将每个方法声明映射到Option.of(true) ,否则将其映射到Option.of(false) 。不是MethodDeclaration映射到Option.none()

因此,例如,如果我们在一个具有三个字段的类中寻找一个名为“ foo”的方法,然后是名为“ bar”,“ foo”和“ baz”的方法,我们将得到以下列表:

Option.none(), Option.none(), Option.none(), Option.of(false) Option.of(true) Option.of(false)

下一步是将Option.none()Option.of(false)映射false,Option.of(true)则映射true 。 请注意,我们可以立即将其连接起来,而不是同时连接两个map操作。 但是我更喜欢分步做事。 一旦我们获得了一个truefalse的列表,我们需要从中得出一个单一值,如果该列表包含至少一个true,则应该为true,否则为false 。 从列表中获取单个值称为减少操作。 这种操作有不同的变体:我将让您详细研究:)

我们可以这样重写最新的方法:

public List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) {Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed;Function2<String, TypeDeclaration, Boolean> originalFunctionReversed = originalFunction.reversed();Function1<String, Function1<TypeDeclaration, Boolean>> originalFunctionReversedAndCurried = originalFunction.reversed().curried();Function1<TypeDeclaration, Boolean> originalFunctionReversedAndCurriedAndAppliedToMethodName =originalFunction.reversed().curried().apply(methodName);return List.ofAll(cu.getTypes()).filter(asPredicate(originalFunctionReversedAndCurriedAndAppliedToMethodName));}

为什么我们要这样做? 看起来(而且确实)复杂得多,但是它向我们展示了如何操作函数,这是获取更灵活,更强大的代码的中间步骤。 因此,让我们尝试了解我们在做什么。

首先快速注意一下:类Function1表示一个带有一个参数的函数。 第一个泛型参数是函数接受的参数的类型,而第二个泛型参数是函数返回的值的类型。 Function2取2个参数。 您可以了解这是怎么回事:)

我们:

  • 反转参数可以传递给函数的顺序
  • 我们创建了一个部分应用的函数:这​​是一个函数,其中第一个参数是“固定的”

所以我们创建originalFunctionReversedAndCurriedAndAppliedToMethodName只运用原有的功能hasMethodNamed。 原始函数具有2个参数: TypeDeclaration和方法名称。 我们精心设计的函数仅接受TypeDeclaration。 它仍然返回一个布尔值。

然后,我们可以简单地用这个微小的函数将谓词转换为函数,然后可以反复使用:

private static <T> Predicate<T> asPredicate(Function1<T, Boolean> function) {return v -> function.apply(v);}

现在,这就是我们可以使其更通用的方法:

/** * Get all the types in a CompilationUnit which satisfies the given condition */
public List<TypeDeclaration> getTypes(CompilationUnit cu, Function1<TypeDeclaration, Boolean> condition) {return List.ofAll(cu.getTypes()).filter(asPredicate(condition));
}/*** It returns a function which tells has if a given TypeDeclaration has a method with a given name.*/
public Function1<TypeDeclaration, Boolean> hasMethodWithName(String methodName) {Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed;return originalFunction.reversed().curried().apply(methodName);
}/*** We could combine previous function to get this one and solve our original question.*/
public List<TypeDeclaration> getTypesWithThisMethod(CompilationUnit cu, String methodName) {return getTypes(cu, hasMethodWithName(methodName));
}

好的,现在我们可以泛化hasMethodWithName了:

/*** This function returns true if the TypeDeclaration has at * least one method satisfying the given condition.*/public static boolean hasAtLeastOneMethodThat(TypeDeclaration typeDeclaration, Function1<MethodDeclaration, Boolean> condition) {return List.ofAll(typeDeclaration.getMembers()).map(Match.whenType(MethodDeclaration.class).then(m -> condition.apply(m)).otherwise(false)).reduce((a, b)->a || b);}/*** We refactor this function to reuse hasAtLeastOneMethodThat*/public static boolean hasMethodWithName(TypeDeclaration typeDeclaration, String methodName) {return hasAtLeastOneMethodThat(typeDeclaration, m -> m.getName().equals(methodName));}

经过一些重构,我们得到以下代码:

package me.tomassetti.javaast;import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import javaslang.Function1;
import javaslang.Function2;
import javaslang.collection.List;
import javaslang.control.Match;import java.util.function.Predicate;public class AstExplorer {public static boolean hasAtLeastOneMethodThat(TypeDeclaration typeDeclaration, Function1<MethodDeclaration, Boolean> condition) {return hasAtLeastOneMethodThat(condition).apply(typeDeclaration);}public static Function1<TypeDeclaration, Boolean> hasAtLeastOneMethodThat(Function1<MethodDeclaration, Boolean> condition) {return t -> List.ofAll(t.getMembers()).map(Match.whenType(MethodDeclaration.class).then(m -> condition.apply(m)).otherwise(false)).reduce((a, b)-> a || b);}public static boolean hasMethodNamed(TypeDeclaration typeDeclaration, String methodName) {return hasAtLeastOneMethodThat(typeDeclaration, m -> m.getName().equals(methodName));}private static <T> Predicate<T> asPredicate(Function1<T, Boolean> function) {return v -> function.apply(v);}public static List<TypeDeclaration> typesThat(CompilationUnit cu, Function1<TypeDeclaration, Boolean> condition) {return List.ofAll(cu.getTypes()).filter(asPredicate(condition));}public static Function1<TypeDeclaration, Boolean> methodHasName(String methodName) {Function2<TypeDeclaration, String, Boolean> originalFunction = AstExplorer::hasMethodNamed;return originalFunction.reversed().curried().apply(methodName);}public static List<TypeDeclaration> typesWithThisMethod(CompilationUnit cu, String methodName) {return typesThat(cu, methodHasName(methodName));}}

现在让我们看看如何使用它:

package me.tomassetti.javaast;import com.github.javaparser.JavaParser;
import com.github.javaparser.ParseException;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import javaslang.Function1;
import javaslang.collection.List;
import org.junit.Test;import java.io.InputStream;
import static me.tomassetti.javaast.AstExplorer.*;
import static org.junit.Assert.*;public class AstExplorerTest {@Testpublic void typesNamedA() throws ParseException {InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java");CompilationUnit cu = JavaParser.parse(is);Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar");List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(isNamedBar));assertEquals(2, res.length());assertEquals("A", res.get(0).getName());assertEquals("B", res.get(1).getName());}@Testpublic void typesHavingAMethodNamedBar() throws ParseException {InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java");CompilationUnit cu = JavaParser.parse(is);Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar");List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(isNamedBar));assertEquals(2, res.length());assertEquals("A", res.get(0).getName());assertEquals("B", res.get(1).getName());}@Testpublic void typesHavingAMethodNamedBarWhichTakesZeroParams() throws ParseException {InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java");CompilationUnit cu = JavaParser.parse(is);Function1<MethodDeclaration, Boolean> hasZeroParam = m -> m.getParameters().size() == 0;Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar");List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(m -> hasZeroParam.apply(m) && isNamedBar.apply(m)));assertEquals(1, res.length());assertEquals("A", res.get(0).getName());}@Testpublic void typesHavingAMethodNamedBarWhichTakesOneParam() throws ParseException {InputStream is = AstExplorerTest.class.getResourceAsStream("/SomeJavaFile.java");CompilationUnit cu = JavaParser.parse(is);Function1<MethodDeclaration, Boolean> hasOneParam = m -> m.getParameters().size() == 1;Function1<MethodDeclaration, Boolean> isNamedBar = m -> m.getName().equals("bar");List<TypeDeclaration> res = typesThat(cu, hasAtLeastOneMethodThat(m -> hasOneParam.apply(m) && isNamedBar.apply(m)));assertEquals(1, res.length());assertEquals("B", res.get(0).getName());}}

我们在此测试中使用的源文件是以下文件:

class A {void foo() { }void bar() { }
}class B {void bar(int x) { }void baz() { }
}

当然,这是对Javaslang潜力的非常非常非常有限的介绍。 对于那些刚接触函数式编程的人来说,我认为重要的是倾向于编写非常小的函数 ,这些函数可以组合和操纵以获得非常灵活和强大的代码。 当我们开始使用函数式编程时,它似乎显得晦涩难懂,但是如果您看一下我们编写的测试,我认为它们相当清晰且具有描述性。

函数式编程:是否大肆宣传?

我认为对函数式编程有很多兴趣,但是如果过分炒作,可能会导致糟糕的设计决策。 考虑一下OOP成为新的冉冉升起的新星的时间:Java设计人员一路走低,迫使程序员将每个代码都放在一个类中,现在我们有了带有一堆静态方法的实用程序类。 换句话说,我们参加了活动,并要求他们假装成为获得我们的OOP奖牌的班级。 是否有意义? 我不这么认为。 强烈鼓励人们学习OOP原则可能有点极端主义。 这就是为什么如果您想学习函数式编程,那么可能会想要使用像Haskell这样的仅函数式语言:因为它们确实,真的,真的推动了您进行函数式编程。 这样您就可以学习原理并在有意义的时候使用它们。

结论

我认为函数式编程是一个功能强大的工具,它可以产生非常有表现力的代码。 当然,它不是解决每种问题的正确工具。 不幸的是,Java 8没有对标准库中的功能编程模式提供适当的支持。 但是,一些启用功能已经以该语言引入,并且Javaslang使现在可以编写出色的功能代码。 我认为以后会出现更多的库,也许它们将使Java保持健康和更长的寿命。

翻译自: https://www.javacodegeeks.com/2015/11/functional-programming-for-java-getting-started-with-javaslang.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/355730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql语句表名大小写敏感_Mysql 表名大小写敏感

默认情况下&#xff0c;mysql在创建数据库对象时&#xff0c;是区分大小写的。故与microsoft之间进行移植时&#xff0c;要特别注意是否存在问题。在mysql中大小写与参数lower_case_table_names有很大关系。以下为官方解释&#xff1a;If set to 0, table names are stored as …

python json是什么_python json详解

在写某狗屁不通文章生成器的时候&#xff0c;发现有个json类型的文件看不懂。 于是先来搞懂什么是json json是一种储存数据的文件类型。 说人话&#xff0c;就是一种文件&#xff0c;能够数据 类似于DOC&#xff0c;excel 之类的。 json能储存半结构化数据。 简单来说&#xff…

mobx中跟新了数据视图没变化_【第1781期】MobX 简明教程

前言SEEConf&#xff0c;2020年1月4号杭州见&#xff0c;C位抢票见文章末尾。今日早读文章由腾讯whinc投稿分享。正文从这开始&#xff5e;&#xff5e;导读&#xff1a;MobX 是一个优秀的响应式状态管理库&#xff0c;在流行的状态管理库 Redux 之外为我们提供了其他选择。如果…

excel导航窗格_Excel小技巧16:在每天的某个时刻自动打开特定工作簿

学习Excel技术&#xff0c;关注微信公众号&#xff1a;excelperfect我想要在每天下班前&#xff0c;将自已一天的工作进行整理并简短记录在一个Excel工作簿中。然而&#xff0c;有时候忙&#xff0c;可能会忘记&#xff1b;有时候到下班时间了&#xff0c;急于下班&#xff0c;…

pdf secured_使您的Spring Security @Secured注释更干燥

pdf secured最近&#xff0c;Grails用户邮件列表中的一个用户想知道在定义Secured注释时如何减少重复 。 在Java批注中指定属性的规则非常严格&#xff0c;因此我看不到直接执行他所要求的方法的方法。 使用Groovy并没有真正的帮助&#xff0c;因为Groovy类中的注释大部分与Ja…

51单片机按键控制数码管0~9_51单片机外部中断

前面为大家介绍的点亮LED灯、数码管、按键只用到了51单片机的IO资源&#xff0c;我们要是仅仅用单片机点灯、操作数码管&#xff0c;那可真是大才小用了。这些都只是51单片机资源的冰山一角&#xff0c;51单片机还有好多的功能&#xff0c;我后面将为大家一一介绍。今天为大家介…

IIS7开启gZip动态压缩

1.安装动态压缩模块&#xff1a; 安装过程可能的报错&#xff1a;This application has requested the Runtime to terminate it in an unusual way. 解决办法>> 报错&#xff1a;错误: 尝试安装 动态内容压缩 失败&#xff0c;错误代码为 0x8007000E。 存储空间不足&am…

mysql查询不重复记录数_mysql查询不重复的行内容,不重复的记录数.count,distinct

有这么一个表 记录了id, p_id, p_name , p_content , p_time 1 343 aaa aaaaaa 2012-09-01 2 344 bbb bbbbbb 2012-09-02 3 321 ccc cccccccc 2012-09-03 4 343 aaa aaaaaa 2012-09-04 想查询不重复的行的内容,并且输出 p_sum ( 产品p_id出现重复的次数) sele有这么一个表记录了…

使用Java中的FileChannel和ByteBuffer在文件中读取/写入文件

过去&#xff0c;我讨论过RandomAccessFile以及如何将其用于在Java中进行更快的IO&#xff0c;在本Java NIO教程中&#xff0c;我们将了解如何通过使用FileChannel和ByteBuffer来使用读/写数据。 Channel提供了一种从文件读取数据的替代方法&#xff0c;它提供了比InputStream…

字符,字符串,int之间互相转换

字符转换成字符串&#xff1a;String str String.valueOf(ch); 字符转换成int&#xff1a; int a ch; 字符串转换成字符&#xff1a;char ch str.charAt(0); 字符串转换成Int&#xff1a;只包含数字的字符串可以通过Integer.parseInt(str)转换为int&#xff0c;但是包含字母…

mysql 导入百万级数据 几种 java_Java 修行第034天--执行计划及其使用--Oracle数据导入导出--第三章MySQL使用...

执行计划中牢记几句话:-- 尽量避免是*代替所有列,编写查询语句时使用具体列名代替*,可以防止全表扫描-- 尽可能少的使用like关键字进行模糊查询-- 建立适当的索引可以提高查询效率十三. 执行计划--通过PL/SQL Developer查看查询的1 执行计划是一条查询语句在Oracle中的执行过程…

10g添加用户 oracle_oracle 10g中如何创建用户

登录身份 说明sys/change_on_install SYSDBA或SYSOPER 不能以NORMAL登录&#xff0c;可作为默认的系统管理员system/manager SYSDBA或NORMAL 不能以SYSOPER登录&#xff0c;可作为默认的系统管理员sysman/oem_temp sysman 为oms的用户名scott/tiger NORMAL 普通用户aqadm /aqad…

摄像头分辨率怎么调整_亿联CAM50 - 智能话机专属高清摄像头

亿联CAM50是通过USB连接的高清视频摄像机&#xff0c;适用于亿联桌面高端智能话机&#xff08;SIP-T58V和SIP-T58A&#xff09;&#xff0c;致力于提供一流的个人视频通信体验&#xff0c;优化视频协作。CAM50高清摄像机即插即用&#xff0c;无需额外安装驱动软件&#xff0c;也…

C orm mysql_Simple MySQL-C ORM

当你需要在纯C语言的应用程序中访问 MySQL 表中的数据时&#xff0c;是非常繁琐的事情&#xff0c;而该框架可以帮你大量的简化编码的工作&#xff0c;该框架采用 Python 开发&#xff0c;适用于 C 语言程序。示例代码&#xff1a;#include #include #include #include int mai…

jdbc:log4jdbc_使用Log4jdbc记录JDBC操作

jdbc:log4jdbc当我们开发任何应用程序&#xff0c;完成它或结束其任何模块时&#xff0c;我们都会开始优化过程。 大多数应用程序都包含数据库访问权限&#xff0c;如果您使用的是ORM &#xff0c;则可能会使用hibernate 。 优化Hibernate持久层&#xff0c;要求准备阅读&#…

Mac 编译安装 Redis-3.2.3

Redis官方下载地址&#xff1a;http://redis.io/download Redis安装 cd /usr/local/src/redis-3.2.3 sudo make sudo make installcp ./src/redis-benchmark /usr/local/redis/redis-3.2 cp ./src/redis-check-aof /usr/local/redis/redis-3.2 cp ./src/redis-check-rdb /usr/l…

linux c mysql教程_linux下c操作mysql之增删改查

书接上文,继续进行linux 下c操作mysql。1.创建表/插入数据mysql> desc children-> ;----------------------------------------------------------| Field | Type| Null | Key | Default | Extra |----------------------------------------------------------| childno …

ios系统python编译器_MacBook如何安装Python编译器-百度经验

编程是一门需要动手实践的技能&#xff0c;由于Python的性能&#xff0c;许多人都将其作为学习编程的入门语言。而要想学好Python&#xff0c;首先要在电脑上安装Python&#xff0c;并安装一个可以解释Python的文本编辑器。在此以在MacBook上安装Sublime Text为例。 工具/原料 …

jvm需要多长时间进行转义分析? 可能比您想象的要长。

这篇文章着眼于转义分析&#xff0c;特别是jvm在运行的程序中执行转义分析需要多长时间。 我做了一些观察&#xff0c;但目前还没有全部解释。 作为介绍&#xff0c;让我们绕道看看jvm -Xcomp中一个鲜为人知且使用更少的标志&#xff08;这将是一件好事&#xff09;。 该标志…

Java补漏(一)

&#xfeff;&#xfeff;第一章前言 在学长的建议下&#xff0c;为了弥补之前学Java漏下的或者不是非常清楚的知识点&#xff0c;买了本蛮好的教科书-《Java学习笔记&#xff08;JDK6&#xff09;》&#xff0c;正式又一次学习。为了记下一些让我恍然大悟的知识。写了本文档。…