java 函数式编程_Java函数式编程:Javaslang入门
java 函數(shù)式編程
Java是一門古老的語言,并且該領(lǐng)域中有很多新手在他們自己的領(lǐng)域(JVM)上挑戰(zhàn)Java。 但是Java 8到來并帶來了一些有趣的功能。 這些有趣的功能使編寫新的驚人框架(例如Spark Web框架或Javaslang)成為可能 。
在本文中,我們將介紹將函數(shù)式編程引入Java的Javaslang。
函數(shù)式編程:這有什么用?
如今,似乎所有出色的開發(fā)人員都希望進(jìn)行一些功能編程。 因?yàn)樗麄円郧跋胧褂妹嫦驅(qū)ο蟮木幊獭?我個(gè)人認(rèn)為函數(shù)式編程可以很好地解決某些問題,而其他范例則更好。
在以下情況下,函數(shù)式編程非常有用:
- 您可以將其與不變性配對:純函數(shù)沒有副作用,并且更容易推理。 純函數(shù)意味著不變性,從而極大地簡化了測試和調(diào)試。 但是,并非所有解決方案都能很好地代表不變性。 有時(shí)您只是擁有大量數(shù)據(jù),這些數(shù)據(jù)在多個(gè)用戶之間共享,并且您想就地進(jìn)行更改。 在這種情況下,可變性是解決方法。
- 您擁有的代碼取決于輸入,而不取決于狀態(tài):如果某些東西取決于狀態(tài)而不是輸入,那么聽起來對我來說更像是一個(gè)函數(shù)。 理想情況下,功能代碼應(yīng)非常明確地說明正在使用的信息(因此,僅應(yīng)使用參數(shù))。 這也意味著更多通用和可重用的功能。
- 您具有獨(dú)立的邏輯,這些邏輯之間的耦合程度不高:以小型,通用和可重用功能組織的功能代碼非常有用
- 您擁有要轉(zhuǎn)換的數(shù)據(jù)流:我認(rèn)為這是最容易看到函數(shù)式編程值的地方。 實(shí)際上,流在Java 8中引起了很多關(guān)注。
討論圖書館
正如您可以在javaslang.com上閱讀的那樣 :
Java 8在我們的程序中引入了λc,但“顯然,JDK API不會(huì)幫助您編寫簡潔的功能邏輯(…)” – jOOQ?博客
Javaslang?是編寫全面的功能性Java 8+程序的缺失部分和最佳解決方案。
就像我看到的Javaslang一樣:Java 8為我們提供了啟用功能,以構(gòu)建更簡潔和可組合的代碼。 但是它沒有做最后一步。 它打開了一個(gè)空間,Javaslang到達(dá)了它。
Javaslang帶來了許多功能:
- currying: currying是功能的部分應(yīng)用
- 模式匹配:讓我們將其視為函數(shù)式編程的動(dòng)態(tài)調(diào)度
- 故障處理:因?yàn)楫惓2焕诠δ芙M合
- 要么:這是函數(shù)編程中非常常見的另一種結(jié)構(gòu)。 典型的示例是一個(gè)函數(shù),當(dāng)事情進(jìn)展順利時(shí)返回一個(gè)值,而當(dāng)事情進(jìn)展不好時(shí)返回錯(cuò)誤消息
- 元組:元組是對象的一種很好的輕量級替代方案,非常適合返回多個(gè)值。 只是不要偷懶,并在合理的時(shí)候使用類
- 備注:這是功能的緩存
對于具有函數(shù)式編程經(jīng)驗(yàn)的開發(fā)人員來說,這一切都是眾所周知的。 對于我們其余的人,讓我們看一下如何在實(shí)踐中使用這些東西。
好的,但是實(shí)際上我們?nèi)绾问褂眠@些東西?
顯然,為Javaslang的每個(gè)功能顯示一個(gè)示例遠(yuǎn)遠(yuǎn)超出了本文的范圍。 讓我們看看如何使用其中的一些,尤其是讓我們專注于函數(shù)式編程的精髓:函數(shù)操縱。
鑒于我沉迷于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" }我們將實(shí)現(xiàn)非常簡單的查詢。 僅查看AST即可獲得解答,而無需求解符號。 如果您想使用Java AST并求解符號,則可能需要看一下我的這個(gè)項(xiàng)目: java-symbol-solver 。
例如:
- 用給定名稱的方法查找類
- 使用具有給定數(shù)量參數(shù)的方法查找類
- 查找具有給定名稱的類
- 結(jié)合previos查詢
讓我們從給出CompilationUnit的函數(shù)開始,方法名稱返回一個(gè)TypeDeclarations列表,該列表定義了使用該名稱的方法。 對于從未使用過JavaParser的用戶: CompilationUnit代表整個(gè)Java文件,可能包含幾個(gè)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() )中的所有類型,并對它們進(jìn)行過濾,僅選擇具有該名稱的方法的類型。 真正的工作在hasMethodNamed中完成。
在hasMethodNamed寬 E從我們的java.util.List(List.ofAll(typeDeclaration.getMembers())創(chuàng)建一個(gè)javaslang.collection.List開始,然后我們認(rèn)為我們只是在MethodDeclarations感興趣:我們不是在外地感興趣聲明或類型聲明中包含的其他內(nèi)容,因此,如果方法名稱與所需的methodName相匹配,則將每個(gè)方法聲明映射到Option.of(true) ,否則將其映射到Option.of(false) 。不是MethodDeclaration映射到Option.none() 。
因此,例如,如果我們在一個(gè)具有三個(gè)字段的類中尋找方法名稱“ 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,同時(shí)將Option.of(true)映射為true 。 請注意,我們可以立即將其連接起來,而不是同時(shí)連接兩個(gè)map操作。 但是我更喜歡分步進(jìn)行。 一旦我們獲得了一個(gè)true和false的列表,我們需要從中導(dǎo)出一個(gè)單一值,如果該列表包含至少一個(gè)true,則應(yīng)該為true,否則為false 。 從列表中獲取單個(gè)值稱為減少操作。 這種操作有不同的變體:我將讓您詳細(xì)研究:)
我們可以這樣重寫最新的方法:
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));}為什么我們要這樣做? 看起來(而且確實(shí))更加復(fù)雜,但是它向我們展示了如何操作函數(shù),這是獲取更靈活,更強(qiáng)大的代碼的中間步驟。 因此,讓我們嘗試了解我們在做什么。
首先快速注意一下:類Function1表示一個(gè)帶有一個(gè)參數(shù)的函數(shù)。 第一個(gè)泛型參數(shù)是函數(shù)接受的參數(shù)的類型,而第二個(gè)泛型參數(shù)是函數(shù)返回的值的類型。 Function2取而代之的是2個(gè)參數(shù)。 您可以了解這是怎么回事:)
我們:
- 反轉(zhuǎn)參數(shù)可以傳遞給函數(shù)的順序
- 我們創(chuàng)建一個(gè)部分應(yīng)用的函數(shù):這??是一個(gè)函數(shù),其中第一個(gè)參數(shù)是“固定的”
所以我們創(chuàng)建originalFunctionReversedAndCurriedAndAppliedToMethodName只運(yùn)用原有的功能hasMethodNamed。 原始函數(shù)有2個(gè)參數(shù): TypeDeclaration和方法名稱。 我們精心設(shè)計(jì)的函數(shù)只接受TypeDeclaration。 它仍然返回一個(gè)布爾值。
然后,我們可以簡單地用這個(gè)小函數(shù)將謂詞轉(zhuǎn)換為函數(shù),然后可以反復(fù)使用:
private static <T> Predicate<T> asPredicate(Function1<T, Boolean> function) {return v -> function.apply(v);}現(xiàn)在,這就是我們可以使其更通用的方法:
/** * 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)); }好的,現(xiàn)在我們可以泛化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));}經(jīng)過一些重構(gòu),我們得到以下代碼:
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));}}現(xiàn)在讓我們看看如何使用它:
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() { } }當(dāng)然,這是對Javaslang潛力的非常非常非常有限的介紹。 對于那些剛接觸函數(shù)式編程的人來說,我認(rèn)為重要的是傾向于編寫很小的函數(shù) ,這些函數(shù)可以組合和操縱以獲得非常靈活和強(qiáng)大的代碼。 當(dāng)我們開始使用函數(shù)式編程時(shí),它似乎顯得晦澀難懂,但是如果您看一下我們編寫的測試,我認(rèn)為它們相當(dāng)清晰和具有描述性。
函數(shù)式編程:炒作是否合理?
我認(rèn)為對函數(shù)式編程有很多興趣,但是如果過分炒作,可能會(huì)導(dǎo)致糟糕的設(shè)計(jì)決策。 考慮一下OOP成為新的冉冉升起的新星的時(shí)間:Java設(shè)計(jì)人員一路走低,迫使程序員將每個(gè)代碼都放在一個(gè)類中,現(xiàn)在我們有了帶有一堆靜態(tài)方法的實(shí)用程序類。 換句話說,我們參加了活動(dòng),并要求他們假裝成為獲得OOP獎(jiǎng)牌的班級。 是否有意義? 我不這么認(rèn)為。 強(qiáng)烈鼓勵(lì)人們學(xué)習(xí)OOP原則可能有點(diǎn)極端主義。 這就是為什么如果您想學(xué)習(xí)函數(shù)式編程,那么可能會(huì)想要使用像Haskell這樣的僅函數(shù)式語言:因?yàn)樗鼈兇_實(shí),真的,真的將您推向了函數(shù)式編程。 這樣您就可以學(xué)習(xí)原理并在有意義的時(shí)候使用它們。
結(jié)論
我認(rèn)為函數(shù)式編程是一個(gè)功能強(qiáng)大的工具,它可以產(chǎn)生非常有表現(xiàn)力的代碼。 當(dāng)然,它不是解決每種問題的正確工具。 不幸的是,Java 8沒有在標(biāo)準(zhǔn)庫中正確支持功能編程模式。 但是,一些啟用功能已經(jīng)以該語言引入,并且Javaslang現(xiàn)在使編寫出色的功能代碼成為可能。 我認(rèn)為以后會(huì)出現(xiàn)更多的庫,也許它們可以使Java保持健康和健康的狀態(tài)。
翻譯自: https://www.javacodegeeks.com/2015/11/functional-programming-for-java-getting-started-with-javaslang.html
java 函數(shù)式編程
總結(jié)
以上是生活随笔為你收集整理的java 函数式编程_Java函数式编程:Javaslang入门的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件安卓系统管家(安卓系统管家)
- 下一篇: 启动盘linux(启动盘 linux)