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