├── .gitignore ├── README.md ├── assets └── a-functional-approach-to-java.png ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── part-1 ├── 01-an-introduction-to-functional-programming │ └── README.md ├── 02-functional-java │ ├── README.md │ ├── build.gradle │ ├── example-2-1 │ │ ├── LambdaVariants.java │ │ └── lambda-variants.jsh │ ├── example-2-10 │ │ ├── FirstClassCitizenship.java │ │ └── first-class-citizenship.jsh │ ├── example-2-2 │ │ └── Predicate.java │ ├── example-2-3 │ │ ├── LambdaCapture.java │ │ └── lambda-capture.jsh │ ├── example-2-4 │ │ └── ReassignFinalVariable.java │ ├── example-2-5 │ │ └── RefinalizeReference.java │ ├── example-2-6 │ │ ├── HelloWorld.java │ │ ├── HelloWorldAnonymous.java │ │ ├── HelloWorldLambda.java │ │ └── hello-world.jsh │ ├── example-2-7 │ │ ├── HelloWorldAnonymous.bytecode │ │ └── HelloWorldLambda.bytecode │ ├── example-2-8 │ │ ├── MethodReferencesAndStreams.java │ │ └── method-references-and-streams.jsh │ ├── example-2-9 │ │ ├── Memoization.java │ │ └── memoization.jsh │ ├── jshell │ │ ├── ad-hoc-created-lambdas-type-inference.jsh │ │ └── incompatible-functional-interfaces.jsh │ └── method-references │ │ ├── BoundNonStatic.java │ │ ├── BoundNonStaticThisSuper.java │ │ ├── Constructor.java │ │ ├── Static.java │ │ ├── UnboundNonStatic.java │ │ ├── bound-non-static-this-super.jsh │ │ ├── bound-non-static.jsh │ │ ├── constructor.jsh │ │ ├── static.jsh │ │ └── unbound-non-static.jsh └── 03-functional-jdk │ ├── README.md │ ├── bridging-functional-interfaces │ ├── BridgingFunctionalInterfaces.java │ └── bridging-functional-interfaces.jsh │ ├── build.gradle │ ├── example-3-1 │ ├── ArityCompatibility.java │ └── arity-compatibility.jsh │ ├── example-3-2 │ ├── FunctionalComposition.java │ └── functional-composition.jsh │ ├── example-3-3 │ └── README.md │ ├── example-3-4 │ └── README.md │ ├── example-3-5 │ └── Compositor.java │ ├── example-3-6 │ └── CompositorUsage.java │ ├── figure-3-1 │ ├── FunctionExample.java │ └── function-example.jsh │ ├── figure-3-2 │ ├── ConsumerExample.java │ └── consumer-example.jsh │ ├── figure-3-3 │ ├── SupplierExample.java │ └── supplier-example.jsh │ └── figure-3-4 │ ├── PredicateExample.java │ └── predicate-example.jsh ├── part-2 ├── 04-immutability │ ├── README.md │ ├── build.gradle │ ├── example-4-1 │ │ └── CollectionsFinalReferences.java │ ├── immutable-copy │ │ ├── ImmutableCopy.java │ │ └── immutable-copy.jsh │ ├── immutable-math │ │ ├── ImmutableMath.java │ │ └── immutable-math.jsh │ └── unmodifiable-collections │ │ ├── ExceptionThrown.java │ │ ├── UnmodifiableListModifyOriginal.java │ │ ├── exception-thrown.jsh │ │ └── modify-original.jsh ├── 05-working-with-records │ ├── README.md │ ├── build.gradle │ ├── example-5-1 │ │ └── UserPOJO.java │ ├── example-5-10 │ │ └── SealedGroups.java │ ├── example-5-11 │ │ └── SealedGroups.java │ ├── example-5-2 │ │ └── UserImmutable.java │ ├── example-5-3 │ │ ├── UserImmutable.disassembled │ │ └── UserRecord.disassembled │ ├── example-5-4 │ │ ├── Inheritance.java │ │ └── inheritance.jsh │ ├── example-5-5 │ │ └── ComposedDataStructures.java │ ├── example-5-6 │ │ ├── CustomConstructorsDefaultValues.java │ │ └── custom-constructors-default-values.jsh │ ├── example-5-7 │ │ ├── UserBuilder.java │ │ └── user-builder.jsh │ ├── example-5-8 │ │ └── User.java │ ├── example-5-9 │ │ ├── LocalNominalTuple.java │ │ └── local-nominal-tuple.jsh │ ├── missing-record-features │ │ ├── FactoryMethods.java │ │ └── factory-methods.jsh │ ├── record-features │ │ ├── CanonicalConstructor.java │ │ ├── CompactConstructor.java │ │ ├── DerivedState.java │ │ ├── Generics.java │ │ └── OverrideComponentAccessor.java │ ├── record-optional-data-handling │ │ ├── ConvenienceConstructor.java │ │ ├── NonNullContainer.java │ │ ├── convenience-constructor.jsh │ │ └── non-null-container.jsh │ ├── record-use-cases-common-practices │ │ ├── BuilderPatternCopy.java │ │ ├── DataScrubbing.java │ │ ├── IncreaseImmutability.java │ │ ├── Validation.java │ │ ├── builder-pattern-copy.jsh │ │ ├── data-scrubbing.jsh │ │ ├── increase-immutability.jsh │ │ ├── pattern-matching.jsh │ │ ├── sealed-types.jsh │ │ └── validation.jsh │ ├── record-wither │ │ ├── Wither.java │ │ ├── WitherNested.java │ │ ├── wither-nested.jsh │ │ └── wither.jsh │ ├── serializing-evolving-records │ │ ├── PointSerialization.java │ │ └── PointSerializationIdentical.java │ ├── tuples │ │ ├── tuples.py │ │ └── tuples.swift │ └── user-record │ │ ├── UserRecord.java │ │ └── user-record.jsh ├── 06-data-processing-with-streams │ ├── README.md │ ├── build.gradle │ ├── example-6-1 │ │ ├── FindingBooksForLoop.java │ │ └── finding-books-for-loop.jsh │ ├── example-6-10 │ │ ├── ReduceLikeForLoop.java │ │ └── reduce-like-for-loop.jsh │ ├── example-6-11 │ │ ├── ReduceVsMapReduce.java │ │ └── reduce-vs-map-reduce.jsh │ ├── example-6-12 │ │ ├── ReduceAggregate.java │ │ └── reduce-aggregate.jsh │ ├── example-6-13 │ │ ├── ReduceImmutableAggregation.java │ │ └── reduce-immutable-aggregation.jsh │ ├── example-6-14 │ │ ├── ReduceVsCollect.java │ │ └── reduce-vs-collect.jsh │ ├── example-6-15 │ │ ├── FruitsNaive.java │ │ └── fruits-naive.jsh │ ├── example-6-16 │ │ ├── FruitsImproved.java │ │ └── fruits-improved.jsh │ ├── example-6-2 │ │ ├── FindingBooksStream.java │ │ └── finding-books-stream.jsh │ ├── example-6-3 │ │ └── README.md │ ├── example-6-4 │ │ └── README.md │ ├── example-6-5 │ │ └── README.md │ ├── example-6-6 │ │ └── Shape.java │ ├── example-6-7 │ │ ├── FlatMapMapMulti.java │ │ └── flat-map-map-multi.jsh │ ├── example-6-8 │ │ ├── Peek.java │ │ └── peek.jsh │ ├── example-6-9 │ │ ├── MaxInCollection.java │ │ └── max-in-collection.jsh │ └── short-circuit │ │ ├── Dropped.java │ │ ├── NotDropped.java │ │ ├── dropped.jsh │ │ └── not-dropped.jsh ├── 07-working-with-streams │ ├── README.md │ ├── build.gradle │ ├── example-7-1 │ │ ├── FilesListing.java │ │ └── files-listing.jsh │ ├── example-7-2 │ │ ├── FilesWalking.java │ │ └── files-walking.jsh │ ├── example-7-3 │ │ ├── FilesSearching.java │ │ └── files-searching.jsh │ ├── example-7-4 │ │ ├── FilesLines.java │ │ ├── files-lines.jsh │ │ └── war-and-peace.txt │ ├── example-7-5 │ │ ├── Teeing.java │ │ └── teeing.jsh │ ├── example-7-6 │ │ ├── Joinector.java │ │ └── joinector.jsh │ ├── streams-downstream-collectors │ │ ├── Filter.java │ │ ├── Flatten.java │ │ ├── GroupByMapping.java │ │ ├── GroupBySimple.java │ │ ├── ReduceSum.java │ │ ├── filter.jsh │ │ ├── flatten.jsh │ │ ├── groupby-mapping.jsh │ │ ├── groupby-simple.jsh │ │ └── reduce-sum.jsh │ ├── streams-infinite │ │ ├── InfiniteStreams.java │ │ └── infinite-streams.jsh │ ├── streams-iteration │ │ ├── Java8.java │ │ ├── Java9.java │ │ ├── java-8.jsh │ │ └── java-9.jsh │ ├── streams-temporal-query │ │ ├── TemporalQuery.java │ │ └── temporal-query.jsh │ └── streams-toarray │ │ ├── ObjectStreamToArray.java │ │ ├── PrimitiveStreamToarray.java │ │ ├── object-stream-toarray.jsh │ │ └── primitive-stream-toarray.jsh ├── 08-parallel-data-processing-with-streams │ ├── README.md │ ├── build.gradle │ ├── example-8-1 │ │ ├── WarAndPeaceSequential.java │ │ └── war-and-peace-sequential.jsh │ ├── example-8-2 │ │ ├── WarAndPeaceParallel.java │ │ └── war-and-peace-parallel.jsh │ ├── example-8-3 │ │ ├── MutableAccumulation.java │ │ └── mutable-accumulation.jsh │ ├── example-8-4 │ │ ├── ImmutableAccumulation.java │ │ └── immutable-accumulation.jsh │ ├── example-8-5 │ │ ├── RandomNumberStats.java │ │ └── random-number-stats.jsh │ └── other │ │ └── war-and-peace.txt ├── 09-handling-null-with-optionals │ ├── README.md │ ├── build.gradle │ ├── dos-and-donts │ │ └── dos-and-donts.jsh │ ├── example-9-1 │ │ ├── Minefield.java │ │ └── minefield.jsh │ ├── example-9-10 │ │ ├── StreamFlatMap.java │ │ └── stream-flatmap.jsh │ ├── example-9-11 │ │ └── pseudo-reduce.java │ ├── example-9-12 │ │ └── User.java │ ├── example-9-2 │ │ ├── NullTypeAmbiguity.java │ │ └── null-type-ambiguity.jsh │ ├── example-9-3 │ │ └── NullHandlingAnnotations.java │ ├── example-9-4 │ │ ├── LoadingContentWithoutOptionals.java │ │ └── loading-content-without-optionals.jsh │ ├── example-9-5 │ │ ├── CallChain.java │ │ └── call-chain.jsh │ ├── example-9-6 │ │ ├── CheckOptional.java │ │ └── check-optional.jsh │ ├── example-9-7 │ │ ├── FilterMapOptional.java │ │ └── filter-map-optional.jsh │ ├── example-9-8 │ │ ├── FilterMapWithoutOptionals.java │ │ └── filter-map-without-optionals.jsh │ └── example-9-9 │ │ ├── StreamFilterMap.java │ │ └── stream-filter-map.jsh ├── 10-functional-exception-handling │ ├── README.md │ ├── build.gradle │ ├── errors-as-values │ │ └── errors-as-values.go │ ├── example-10-1 │ │ ├── SafeMethodExtraction.java │ │ └── safe-method-extraction.jsh │ ├── example-10-10 │ │ ├── TryHandling.java │ │ └── try-handling.jsh │ ├── example-10-11 │ │ ├── TryApply.java │ │ └── try-apply.jsh │ ├── example-10-12 │ │ ├── TryFunction.java │ │ └── try-function.jsh │ ├── example-10-2 │ │ ├── UncheckingExceptions.java │ │ └── unchecking-exceptions.jsh │ ├── example-10-4 │ │ └── not-throwing-exceptions.java │ ├── example-10-5 │ │ └── Result.java │ ├── example-10-6 │ │ ├── ResultAsReturnType.java │ │ └── result-as-return-type.jsh │ ├── example-10-7 │ │ ├── ResultTransformers.java │ │ └── result-transformers.jsh │ ├── example-10-8 │ │ └── TrySuccessFailure.scala │ ├── example-10-9 │ │ ├── TryMinimal.java │ │ └── try-minimal.jsh │ ├── files-read-string │ │ ├── FilesReadString.java │ │ ├── FilesReadStringTryCatch.java │ │ ├── files-read-string-try-catch.java │ │ └── files-read-string.java │ ├── result-reactions-fallback │ │ ├── ResultReactionsFallback.java │ │ └── result-reactions-fallback.jsh │ ├── sneaky-throws │ │ └── SneakyThrows.java │ └── try-catch │ │ ├── TryCatch.java │ │ └── try-catch.java ├── 11-lazy-evaluation │ ├── README.md │ ├── build.gradle │ ├── example-11-1 │ │ ├── LazyVersusStrict.java │ │ └── lazy-versus-strict.jsh │ ├── example-11-2 │ │ ├── ShortCircuit.java │ │ └── short-circuit.jsh │ ├── example-11-3 │ │ ├── UpdateUserEager.java │ │ └── update-user-eager.jsh │ ├── example-11-4 │ │ ├── UpdateUserLambda.java │ │ └── update-user-lambda.jsh │ ├── example-11-5 │ │ └── ThunkSimple.java │ ├── example-11-6 │ │ └── ThunkFunctional.java │ ├── example-11-7 │ │ └── ThunkWithHolder.java │ └── lazy-maps │ │ ├── Lazy.java │ │ ├── NonLazy.java │ │ ├── lazy.jsh │ │ └── non-lazy.jsh ├── 12-recursion │ ├── README.md │ ├── build.gradle │ ├── example-12-1 │ │ ├── HeadRecursion.java │ │ └── head-recursion.jsh │ ├── example-12-2 │ │ ├── TailRecursion.java │ │ └── tail-recursion.jsh │ ├── example-12-3 │ │ ├── TreeNodeStructure.java │ │ └── tree-node-structure.jsh │ ├── example-12-4 │ │ ├── TreeTraversalIterative.java │ │ └── tree-traversal-iterative.jsh │ ├── example-12-5 │ │ ├── TreeTraversalRecursive.java │ │ └── tree-traversal-recursive.jsh │ ├── example-12-6 │ │ ├── TreeTraversalRecursiveNode.java │ │ └── tree-traversal-recursive-node.jsh │ └── stream-recursion │ │ └── stream-recursion.md ├── 13-asynchronous-tasks │ ├── README.md │ ├── build.gradle │ ├── example-13-1 │ │ ├── FutureFlowOfExecution.java │ │ └── future-flow-of-execution.jsh │ ├── example-13-2 │ │ ├── CreationConvenience.java │ │ └── creation-convenience.jsh │ ├── example-13-4 │ │ ├── NestedStages.java │ │ └── nested-stages.jsh │ ├── example-13-5 │ │ ├── EitherRejected.java │ │ └── either-rejected.jsh │ ├── example-13-6 │ │ └── CompletableFutures.java │ ├── example-13-7 │ │ └── CompletableFutures.java │ ├── example-13-8 │ │ └── WeatherService.java │ └── example-13-9 │ │ └── ImageProcessor.java ├── 14-design-patterns │ ├── README.md │ ├── build.gradle │ ├── builder-pattern-fp │ │ ├── Main.java │ │ ├── User.java │ │ └── UserWith.java │ ├── builder-pattern-oo │ │ ├── Main.java │ │ └── User.java │ ├── decorator-pattern-fp │ │ ├── AddMilkDecorator.java │ │ ├── AddSugarDecorator.java │ │ ├── Barista.java │ │ ├── BlackCoffee.java │ │ ├── BlackCoffeeMaker.java │ │ ├── Coffee.java │ │ ├── CoffeeMaker.java │ │ ├── Decorations.java │ │ ├── Decorator.java │ │ ├── Main.java │ │ └── MilkCarton.java │ ├── decorator-pattern-oo │ │ ├── AddMilkDecorator.java │ │ ├── AddSugarDecorator.java │ │ ├── BlackCoffee.java │ │ ├── BlackCoffeeMaker.java │ │ ├── Coffee.java │ │ ├── CoffeeMaker.java │ │ ├── Decorator.java │ │ ├── Main.java │ │ └── MilkCarton.java │ ├── factory-pattern-fp │ │ ├── Circle.java │ │ ├── Color.java │ │ ├── Main.java │ │ ├── Pentagon.java │ │ ├── Shape.java │ │ ├── ShapeType.java │ │ ├── Square.java │ │ └── Triangle.java │ ├── factory-pattern-oo │ │ ├── Circle.java │ │ ├── Color.java │ │ ├── Main.java │ │ ├── Pentagon.java │ │ ├── Shape.java │ │ ├── ShapeFactory.java │ │ ├── ShapeType.java │ │ ├── Square.java │ │ └── Triangle.java │ ├── strategy-pattern-fp │ │ ├── ExpeditedShipping.java │ │ ├── Main.java │ │ ├── Parcel.java │ │ ├── ShippingService.java │ │ ├── ShippingStrategies.java │ │ ├── ShippingStrategy.java │ │ └── StandardShipping.java │ └── strategy-pattern-oo │ │ ├── ExpeditedShipping.java │ │ ├── Main.java │ │ ├── Parcel.java │ │ ├── ShippingService.java │ │ ├── ShippingStrategy.java │ │ └── StandardShipping.java └── 15-a-functional-approach │ └── README.md └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | point.data 3 | .gradle 4 | **/build/ 5 | !src/**/build/ 6 | 7 | gradle-app.setting 8 | !gradle-wrapper.jar 9 | !gradle-wrapper.properties 10 | .gradletasknamecache 11 | .project 12 | .classpath 13 | .idea 14 | .DS_Store 15 | -------------------------------------------------------------------------------- /assets/a-functional-approach-to-java.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benweidig/a-functional-approach-to-java/b34ddb9944d79f9631a7b6ff1cfb741d3081e8d2/assets/a-functional-approach-to-java.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | java { 6 | toolchain { 7 | languageVersion = JavaLanguageVersion.of(17) 8 | } 9 | } 10 | 11 | ext.addExample = { subproject, location, name, exampleMainClass, taskDescription -> 12 | sourceSets.create(name) { 13 | java.srcDirs = ["${subproject.projectDir}/${location}"] 14 | } 15 | 16 | subproject.tasks.create(name: name, type: JavaExec) { 17 | group = subproject.exampleGroup 18 | description = taskDescription 19 | mainClass = exampleMainClass 20 | classpath = sourceSets[name].runtimeClasspath 21 | } 22 | } 23 | 24 | subprojects { 25 | afterEvaluate { subproject -> 26 | subproject.examples.each { location, examples -> 27 | examples.each { mainClass, description -> 28 | def exampleName = examples.size() > 1 ? "${location}-${mainClass}" : location 29 | addExample(subproject, location, exampleName, mainClass, description) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benweidig/a-functional-approach-to-java/b34ddb9944d79f9631a7b6ff1cfb741d3081e8d2/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /part-1/02-functional-java/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 1 - Chapter 2: Functional Java' 2 | 3 | ext.examples = [ 4 | 'example-2-1': ['LambdaVariants': 'Different ways of writing the same lambda'], 5 | 'example-2-3': ['LambdaCapture': 'Lambda variable capture'], 6 | 'example-2-4': ['ReassignFinalVariable': 'Change data behind a final variable'], 7 | 'example-2-5': ['RefinalizeReference': 'Re-finalize a variable'], 8 | 'example-2-6': [ 9 | 'HelloWorldAnonymous': 'Anonymous class versus lambda expression: Anonymous', 10 | 'HelloWorldLambda': 'Anonymous class versus lambda expression: Lambda', 11 | ], 12 | 'example-2-8': ['MethodReferencesAndStreams': 'Method references and Streams'], 13 | 'example-2-9': ['Memoization': 'Memoization with Map#computeIfAbsent'], 14 | 'example-2-10': ['FirstClassCitizenship': 'First-class Java Lambdas'], 15 | 'method-references': [ 16 | 'BoundNonStatic': 'Bound non-static method references', 17 | 'BoundNonStaticThisSuper': 'Bound non-static method references (this/super)', 18 | 'Constructor': 'Constructor references', 19 | 'Static': 'Static method references', 20 | 'UnboundNonStatic': 'Unbound non-static method references', 21 | ] 22 | ] 23 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-1/LambdaVariants.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-1. Different ways of writing the same lambda 6 | // 7 | 8 | import java.util.function.Predicate; 9 | 10 | 11 | public class LambdaVariants { 12 | 13 | public static void main(String... args) { 14 | 15 | // MOST VERBOSE OPTION 16 | // - Typed parameters 17 | // - Block body 18 | Predicate verbose = (String input) -> { 19 | return input != null; 20 | }; 21 | 22 | // MIXED VARIANT 1 23 | // - Type infered parameters 24 | // - Block body 25 | Predicate mixed1 = input -> { 26 | return input != null; 27 | }; 28 | 29 | // MIXED VARIANT 2 30 | // - Typed parameters 31 | // - Single expression body 32 | Predicate mixed2 = (String input) -> input != null; 33 | 34 | // SHORTEST VARIANT 35 | // - Type infered parameters (without parentheses) 36 | // - Single expression body 37 | Predicate shortest = input -> input != null; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-1/lambda-variants.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-1. Different ways of writing the same lambda 6 | // 7 | 8 | // MOST VERBOSE OPTION 9 | // - Typed parameters 10 | // - Block body 11 | Predicate verbose = (String input) -> { 12 | return input != null; 13 | } 14 | 15 | // MIXED VARIANT 1 16 | // - Type infered parameters 17 | // - Block body 18 | Predicate mixed1 = input -> { 19 | return input != null; 20 | } 21 | 22 | // MIXED VARIANT 2 23 | // - Typed parameters 24 | // - Single expression body 25 | Predicate mixed2 = (String input) -> input != null 26 | 27 | // SHORTEST VARIANT 28 | // - Type infered parameters (without parentheses) 29 | // - Single expression body 30 | Predicate shortest = input -> input != null 31 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-10/FirstClassCitizenship.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-10. First-class Java Lambdas 6 | // 7 | 8 | import java.util.function.UnaryOperator; 9 | 10 | public class FirstClassCitizenship { 11 | 12 | public static void main(String... args) { 13 | // VARIABLE ASSIGNMENT 14 | 15 | UnaryOperator quadraticFn = x -> x * x; 16 | 17 | var quadratic = quadraticFn.apply(5); 18 | System.out.println("quadratic = " + quadratic); 19 | 20 | UnaryOperator multiplyWithFive = multiplyWith(5); 21 | 22 | var result = multiplyWithFive.apply(6); 23 | System.out.println("multiplyWithFive = " + result); 24 | } 25 | 26 | // METHOD ARGUMENT 27 | 28 | public static Integer apply(Integer input, UnaryOperator operation) { 29 | 30 | return operation.apply(input); 31 | } 32 | 33 | // RETURN VALUE 34 | 35 | public static UnaryOperator multiplyWith(Integer multiplier) { 36 | 37 | return x -> multiplier * x; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-10/first-class-citizenship.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-10. First-class Java Lambdas 6 | // 7 | 8 | import java.util.function.UnaryOperator 9 | 10 | // VARIABLE ASSIGNMENT 11 | 12 | UnaryOperator quadraticFn = x -> x * x 13 | var quadratic = quadraticFn.apply(5) 14 | System.out.println("quadratic = " + quadratic) 15 | 16 | 17 | // METHOD ARGUMENT 18 | 19 | Integer apply(Integer input, UnaryOperator operation) { 20 | return operation.apply(input); 21 | } 22 | 23 | 24 | // RETURN VALUE 25 | 26 | UnaryOperator multiplyWith(Integer multiplier) { 27 | return x -> multiplier * x; 28 | } 29 | 30 | UnaryOperator multiplyWithFive = multiplyWith(5) 31 | var result = multiplyWithFive.apply(6) 32 | System.out.println("multiplyWithFive = " + multiplyWithFive) 33 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-3/LambdaCapture.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-3. Lambda variable capture 6 | // 7 | 8 | public class LambdaCapture { 9 | 10 | public static void main(String... args) { 11 | Runnable r = capture(); 12 | r.run(); 13 | } 14 | 15 | private static Runnable capture() { 16 | // The variable is declared in the scope of capture 17 | var theAnswer = 42; 18 | 19 | Runnable printAnswer = 20 | // The lambda capture theAnswer for its operation 21 | () -> System.out.println("the answer is " + theAnswer); 22 | 23 | return printAnswer; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-3/lambda-capture.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-3. Lambda variable capture 6 | // 7 | 8 | Runnable capture() { 9 | // The variable is declared in the scope of capture 10 | var theAnswer = 42; 11 | 12 | Runnable printAnswer = 13 | // The lambda capture theAnswer for its operation 14 | () -> System.out.println("the answer is " + theAnswer); 15 | 16 | return printAnswer; 17 | } 18 | 19 | 20 | Runnable r = capture() 21 | r.run() 22 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-4/ReassignFinalVariable.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-4. Change data behind a final variable 6 | // 7 | // THERE'S NO JSHELL VARIANT AS THE KEYWORD FINAL DOESN'T 8 | // WORK AS EXPECTED IN THE REPL 9 | // 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | public class ReassignFinalVariable { 15 | 16 | public static void main(String... args) { 17 | final List wordList = new ArrayList<>(); 18 | 19 | // COMPILES FINE 20 | Runnable addItemInLambda = () -> 21 | wordList.add("adding is fine"); 22 | 23 | 24 | // WON'T COMPILE: 25 | wordList = List.of("assigning", "another", "List", "is", "not"); 26 | } 27 | } 28 | 29 | // 30 | // COMPILER ERROR: 31 | // ReassignFinalVariable.java:21: error: cannot assign a value to final variable wordList 32 | // wordList = List.of("assigning", "another", "List", "is", "not"); 33 | // ^ 34 | // 35 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-5/RefinalizeReference.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-5. Re-finalize a variable 6 | 7 | // THERE'S NO JSHELL VARIANT AS THE KEYWORD FINAL DOESN'T 8 | // WORK AS EXPECTED IN THE REPL 9 | // 10 | 11 | import java.util.function.Predicate; 12 | 13 | public class RefinalizeReference { 14 | 15 | public static void main(String... args) { 16 | 17 | var nonEffectivelyFinal = 1_000L; 18 | nonEffectivelyFinal = 9_000L; 19 | 20 | var finalAgain = nonEffectivelyFinal; 21 | 22 | // NO COMPILE ERROR 23 | Predicate isOver9000 = input -> input > finalAgain; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-6/HelloWorld.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-6. Anonymous class versus lambda expression 6 | // 7 | 8 | // FUNCTIONAL INTERFACE (implicit) 9 | public interface HelloWorld { 10 | 11 | String sayHello(String name); 12 | } 13 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-6/HelloWorldAnonymous.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-6. Anonymous class versus lambda expression 6 | // 7 | 8 | public class HelloWorldAnonymous { 9 | 10 | public static void main(String... args) { 11 | 12 | var helloWorld = new HelloWorld() { 13 | 14 | @Override 15 | public String sayHello(String name) { 16 | return "hello, " + name + "!"; 17 | } 18 | }; 19 | 20 | var greeting = helloWorld.sayHello("Ben"); 21 | System.out.println(greeting); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-6/HelloWorldLambda.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-6. Anonymous class versus lambda expression 6 | // 7 | 8 | public class HelloWorldLambda { 9 | 10 | public static void main(String... args) { 11 | 12 | HelloWorld helloWorld = name -> "hello, " + name + "!"; 13 | 14 | var greeting = helloWorld.sayHello("Ben"); 15 | System.out.println(greeting); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-6/hello-world.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-6. Anonymous class vs. lambda expression 6 | // 7 | 8 | // FUNCTIONAL INTERFACE (implicit) 9 | 10 | interface HelloWorld { 11 | String sayHello(String name); 12 | } 13 | 14 | 15 | // AS ANONYMOUS CLASS 16 | 17 | var helloWorld = new HelloWorld() { 18 | 19 | @Override 20 | public String sayHello(String name) { 21 | return "hello, " + name + "!"; 22 | } 23 | }; 24 | 25 | 26 | // AS LAMBDA 27 | 28 | HelloWorld helloWorldLambda = name -> "hello, " + name + "!"; 29 | 30 | 31 | // RUN 32 | 33 | var anonymous = helloWorld.sayHello("Ben") 34 | System.out.println("anonymous = " + anonymous) 35 | 36 | var lambda = helloWorldLambda.sayHello("Ben") 37 | System.out.println("lambda = " + lambda) 38 | -------------------------------------------------------------------------------- /part-1/02-functional-java/example-2-9/memoization.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Example 2-9. Memoization with Map#computeIfAbsent 6 | // 7 | 8 | import java.util.HashMap 9 | import java.util.Map 10 | import java.util.function.Supplier 11 | 12 | Map CACHE = new HashMap<>(); 13 | 14 | Integer expensiveCall(String arg0, int arg1) { 15 | try { 16 | System.out.println("Sleeping for 3 secs"); 17 | Thread.sleep(3_000); 18 | } 19 | catch (InterruptedException e) { 20 | // ... 21 | } 22 | 23 | return arg1; 24 | } 25 | 26 | T memoize(String identifier, Supplier fn) { 27 | return (T) CACHE.computeIfAbsent(identifier, key -> fn.get()); 28 | } 29 | 30 | Integer memoizedCall(String arg0, int arg1) { 31 | var compoundKey = String.format("expensiveCall:%s-%d", arg0, arg1); 32 | return memoize(compoundKey, () -> expensiveCall(arg0, arg1)); 33 | } 34 | 35 | System.out.println("before first call") 36 | var calculated = memoizedCall("hello, world!", 42) 37 | System.out.println("after first call") 38 | 39 | System.out.println("before second call") 40 | var memoized = memoizedCall("hello, world!", 42) 41 | System.out.println("after second call") 42 | -------------------------------------------------------------------------------- /part-1/02-functional-java/jshell/ad-hoc-created-lambdas-type-inference.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // As this example is supposed to illustrate type inference 6 | // of ad-hoc created lambdas, the actual implementation 7 | // of filter1 and filter2 doesn't matter and is a placeholder 8 | // so the code compiles. 9 | // 10 | 11 | interface LikePredicate { 12 | 13 | boolean test(T value); 14 | } 15 | 16 | 17 | List filter1(List values, 18 | Predicate predicate) { 19 | return values; 20 | } 21 | 22 | 23 | List filter2(List values, 24 | LikePredicate predicate) { 25 | return values; 26 | } 27 | 28 | 29 | var values = Arrays.asList("a", null, "c") 30 | 31 | var result1 = filter1(values, str -> str != null) 32 | System.out.println("result1 = " + result1) 33 | 34 | var result2 = filter2(values, str -> str != null) 35 | System.out.println("result2 = " + result2) 36 | -------------------------------------------------------------------------------- /part-1/02-functional-java/jshell/incompatible-functional-interfaces.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | 6 | // Functional interface that an equivalent to java.util.Predicate 7 | interface LikePredicate { 8 | 9 | boolean test(T value); 10 | } 11 | 12 | 13 | LikePredicate isNull = $ -> $ == null; 14 | 15 | // Assign isNull it to a functional interface with an identical SAM won't compile 16 | Predicate wontCompile = isNull; 17 | 18 | // Error: 19 | // incompatible types: LikePredicate cannot be converted 20 | // to java.util.function.Predicate 21 | // Predicate wontCompile = isNull; 22 | // ^----^ 23 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/BoundNonStatic.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Bound non-static method references 6 | // 7 | 8 | import java.time.LocalDate; 9 | import java.util.function.Function; 10 | import java.util.function.Predicate; 11 | 12 | public class MethodReferencesBoundNonStatic { 13 | 14 | public static void main(String... args) { 15 | 16 | boundNonStatic(); 17 | 18 | boundNonStaticNoIntermediate(); 19 | } 20 | 21 | private static void boundNonStatic() { 22 | // LAMBDA BASED ON EXISTING OBJECT 23 | var now = LocalDate.now(); 24 | Predicate isAfterNowAsLambda = $ -> $.isAfter(now); 25 | 26 | // BOUND NON-STATIC METHOD REFERENCE 27 | Predicate isAfterNowAsRef = now::isAfter; 28 | } 29 | 30 | private static void boundNonStaticNoIntermediate() { 31 | // BIND RETURN VALUE 32 | Predicate isAfterNowAsRef = LocalDate.now()::isAfter; 33 | 34 | // BIND STATIC FIELD 35 | Function castToStr = String.class::cast; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/BoundNonStaticThisSuper.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Bound non-static method references this/super 6 | // 7 | 8 | import java.time.LocalDate; 9 | import java.util.function.Function; 10 | import java.util.function.Predicate; 11 | 12 | public class MethodReferencesBoundNonStaticThisSuper { 13 | 14 | static class SuperClass { 15 | 16 | String doWork(String input) { 17 | return "super: " + input; 18 | } 19 | } 20 | 21 | static class SubClass extends SuperClass { 22 | 23 | @Override 24 | String doWork(String input) { 25 | return "this: " + input; 26 | } 27 | 28 | void superAndThis(String input) { 29 | 30 | Function thisWorker = this::doWork; 31 | var thisResult = thisWorker.apply(input); 32 | System.out.println(thisResult); 33 | 34 | Function superWorker = SubClass.super::doWork; 35 | var superResult = superWorker.apply(input); 36 | System.out.println(superResult); 37 | } 38 | } 39 | 40 | public static void main(String... args) { 41 | 42 | new SubClass().superAndThis("hello, World!"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/Constructor.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Constructor references 6 | // 7 | 8 | import java.util.Locale; 9 | import java.util.function.Function; 10 | 11 | public class MethodReferencesConstructor { 12 | 13 | public static void main(String... args) { 14 | 15 | // LAMBDA 16 | Function newLocaleLambda = language -> new Locale(language); 17 | 18 | // CONSTRUCTOR REFERENCE 19 | Function newLocaleRef = Locale::new; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/Static.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Static method references 6 | // 7 | 8 | import java.util.function.Function; 9 | 10 | public class MethodReferencesStatic { 11 | 12 | public static void main(String... args) { 13 | 14 | // LAMBDA 15 | Function asLambda = i -> Integer.toHexString(i); 16 | 17 | // STATIC METHOD REFERENCE 18 | Function asRef = Integer::tohexString; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/UnboundNonStatic.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Unbound non-static method references 6 | // 7 | 8 | import java.util.function.Function; 9 | 10 | public class MethodReferencesUnboundNonStatic { 11 | 12 | public static void main(String... args) { 13 | // LAMBDA 14 | Function toLowerCaseLambda = str -> str.toLowerCase(); 15 | 16 | // UNBOUND NON-STATIC METHOD REFERENCE 17 | Function toLowerCaseRef = String::toLowerCase; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/bound-non-static-this-super.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Bound non-static method references this/super 6 | // 7 | 8 | import java.time.LocalDate 9 | import java.util.function.Function 10 | import java.util.function.Predicate 11 | 12 | 13 | class SuperClass { 14 | 15 | String doWork(String input) { 16 | return "super: " + input; 17 | } 18 | } 19 | 20 | 21 | class SubClass extends SuperClass { 22 | 23 | @Override 24 | String doWork(String input) { 25 | return "this: " + input; 26 | } 27 | 28 | void superAndThis(String input) { 29 | 30 | Function thisWorker = this::doWork; 31 | var thisResult = thisWorker.apply(input); 32 | System.out.println(thisResult); 33 | 34 | Function superWorker = SubClass.super::doWork; 35 | var superResult = superWorker.apply(input); 36 | System.out.println(superResult); 37 | } 38 | } 39 | 40 | new SubClass().superAndThis("hello, World!") 41 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/bound-non-static.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Bound non-static method references 6 | // 7 | 8 | import java.time.LocalDate 9 | import java.util.function.Function 10 | import java.util.function.Predicate 11 | 12 | 13 | // LAMBDA BASED ON EXISTING OBJECT 14 | var now = LocalDate.now() 15 | Predicate isAfterNowAsLambda = $ -> $.isAfter(now) 16 | 17 | // BOUND NON-STATIC METHOD REFERENCE 18 | Predicate isAfterNowAsRef = now::isAfter 19 | 20 | 21 | // NO INTERMEDIATE 22 | 23 | // BIND RETURN VALUE 24 | Predicate isAfterNowAsRef = LocalDate.now()::isAfter 25 | 26 | // BIND STATIC FIELD 27 | Function castToStr = String.class::cast 28 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/constructor.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Constructor references 6 | // 7 | 8 | import java.util.Locale 9 | import java.util.function.Function 10 | 11 | // LAMBDA 12 | Function newLocaleLambda = language -> new Locale(language) 13 | 14 | // CONSTRUCTOR REFERENCE 15 | Function newLocaleRef = Locale::new 16 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/static.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Static method references 6 | // 7 | 8 | import java.util.function.Function 9 | 10 | 11 | // LAMBDA 12 | Function asLambda = i -> Integer.toHexString(i); 13 | 14 | // STATIC METHOD REFERENCE 15 | Function asRef = Integer::tohexString; 16 | -------------------------------------------------------------------------------- /part-1/02-functional-java/method-references/unbound-non-static.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 2 - Functional Java 4 | // 5 | // Unbound non-static method references 6 | // 7 | 8 | import java.util.function.Function 9 | 10 | 11 | // LAMBDA 12 | Function toLowerCaseLambda = str -> str.toLowerCase() 13 | 14 | var lambda = toLowerCaseLambda.apply("LAMBDA") 15 | System.out.println("lambda = " + lambda) 16 | 17 | 18 | // UNBOUND NON-STATIC METHOD REFERENCE 19 | Function toLowerCaseRef = String::toLowerCase 20 | 21 | var methodRef = toLowerCaseRef.apply("METHOD REF") 22 | System.out.println("methodRef = " + methodRef) 23 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/bridging-functional-interfaces/BridgingFunctionalInterfaces.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Why So Many Functional Interface Variants? 6 | // > Bridging Functional Interfaces 7 | // 8 | 9 | import java.util.function.Predicate; 10 | 11 | public class BridgingFunctionalInterfaces { 12 | 13 | interface LikePredicate { 14 | 15 | boolean test(T value); 16 | } 17 | 18 | public static void main(String[] args) { 19 | 20 | LikePredicate isNull = str -> str == null; 21 | 22 | /* 23 | Predicate wontCompile = isNull; 24 | // Error: 25 | // incompatible types: LikePredicate cannot be 26 | // converted to java.util.function.Predicate 27 | 28 | Predicate wontCompileEither = (Predicate) isNull; 29 | // Exception java.lang.ClassCastException: class LikePredicate 30 | // cannot be cast to class java.util.function.Predicate 31 | */ 32 | 33 | Predicate thisIsFine = isNull::test; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/bridging-functional-interfaces/bridging-functional-interfaces.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Why So Many Functional Interface Variants? 6 | // > Bridging Functional Interfaces 7 | // 8 | 9 | interface LikePredicate { 10 | boolean test(T value); 11 | } 12 | 13 | 14 | LikePredicate isNull = str -> str == null; 15 | 16 | /* 17 | Predicate wontCompile = isNull; 18 | // Error: 19 | // incompatible types: LikePredicate cannot be 20 | // converted to java.util.function.Predicate 21 | 22 | Predicate wontCompileEither = (Predicate) isNull; 23 | // Exception java.lang.ClassCastException: class LikePredicate 24 | // cannot be cast to class java.util.function.Predicate 25 | */ 26 | 27 | Predicate thisIsFine = isNull::test; 28 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 1 - Chapter 3: Functional JDK' 2 | 3 | ext.examples = [ 4 | 'example-3-1': ['ArityCompatibility': 'Java arity compatibility'], 5 | 'example-3-2': ['FunctionalComposition': 'Functional composition direction'], 6 | 'example-3-6': ['CompositorUsage': 'Using the functional compositor'], 7 | 'bridging-functional-interfaces': ['BridgingFunctionalInterfaces': 'Bridge equal but incompatible functional interfaces'], 8 | ] 9 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-1/ArityCompatibility.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Example 3-1. Java arity compatibility 6 | // 7 | 8 | import java.util.function.Function; 9 | import java.util.function.UnaryOperator; 10 | 11 | public class ArityCompatibility { 12 | 13 | public static void main(String[] args) { 14 | UnaryOperator unaryOp = String::toUpperCase; 15 | 16 | Function func = String::toUpperCase; 17 | 18 | // THESE TWO CALLS ARE OK 19 | acceptsUnary(unaryOp); 20 | acceptsFunction(func); 21 | acceptsFunction(unaryOp); 22 | 23 | // THIS CALL WON'T COMPILE 24 | acceptsUnary(func); 25 | 26 | // 27 | // COMPILER ERROR: 28 | // The method acceptsUnary(UnaryOperator) in the type ArityCompatibility is 29 | // not applicable for the arguments (Function) 30 | // 31 | } 32 | 33 | private static void acceptsUnary(UnaryOperator unaryOp) { 34 | // ... 35 | } 36 | 37 | private static void acceptsFunction(Function func) { 38 | // ... 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-1/arity-compatibility.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Example 3-1. Java arity compatibility 6 | // 7 | 8 | UnaryOperator unaryOp = String::toUpperCase; 9 | 10 | Function func = String::toUpperCase; 11 | 12 | 13 | void acceptsUnary(UnaryOperator unaryOp) { 14 | // ... 15 | } 16 | 17 | void acceptsFunction(Function func) { 18 | // ... 19 | } 20 | 21 | // THESE TWO CALLS ARE OK 22 | acceptsUnary(unaryOp) 23 | acceptsFunction(func) 24 | acceptsFunction(unaryOp) 25 | 26 | // THIS CALL WON'T COMPILE 27 | acceptsUnary(func) 28 | 29 | // 30 | // COMPILER ERROR: 31 | // incompatible types: java.util.function.Function cannot be converted to java.util.function.UnaryOperator 32 | // acceptsUnary(func); 33 | // 34 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-2/FunctionalComposition.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Example 3-2. Functional composition direction 6 | // 7 | 8 | import java.util.function.Function; 9 | 10 | public class FunctionalComposition { 11 | 12 | public static void main(String[] args) { 13 | Function removeLowerCaseA = str -> str.replace("a", ""); 14 | Function upperCase = String::toUpperCase; 15 | 16 | var input = "abcd"; 17 | 18 | String leftToRight = removeLowerCaseA.andThen(upperCase).apply(input); 19 | 20 | String rightToLeft = upperCase.compose(removeLowerCaseA).apply(input); 21 | 22 | boolean result = leftToRight.equals(rightToLeft); 23 | System.out.println("is equal = " + result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-2/functional-composition.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Example 3-2. Functional composition direction 6 | // 7 | 8 | Function removeLowerCaseA = str -> str.replace("a", "") 9 | Function upperCase = String::toUpperCase 10 | 11 | 12 | String input = "abcd" 13 | 14 | String leftToRight = removeLowerCaseA.andThen(upperCase).apply(input) 15 | 16 | String rightToLeft = upperCase.compose(removeLowerCaseA).apply(input) 17 | 18 | var result = leftToRight.equals(rightToLeft) 19 | System.out.println("is equal = " + result) 20 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-3/README.md: -------------------------------------------------------------------------------- 1 | # Example 3-3. default method hierarchy 2 | 3 | This example is for illustrating purposes only 4 | 5 | ```java 6 | public interface Iterable { 7 | 8 | default Spliterator spliterator() { 9 | return Spliterators.spliteratorUnknownSize(iterator(), 0); 10 | } 11 | 12 | // ... 13 | } 14 | 15 | 16 | public interface Collection extends Iterable { 17 | 18 | @Override 19 | default Spliterator spliterator() { 20 | return Spliterators.spliterator(this, 0); 21 | } 22 | 23 | // ... 24 | } 25 | 26 | 27 | public class ArrayList extends AbstractList implements List, ... { 28 | 29 | @Override 30 | public Spliterator spliterator() { 31 | return new ArrayListSpliterator(0, -1, 0); 32 | } 33 | 34 | // ... 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/example-3-4/README.md: -------------------------------------------------------------------------------- 1 | # Example 3-4. Simplified Function interface 2 | 3 | This example is for illustrating purposes only 4 | 5 | ```java 6 | @FunctionalInterface 7 | public interface Function { 8 | 9 | default Function compose(Function before) { 10 | Objects.requireNonNull(before); 11 | 12 | return (V v) -> { 13 | T result = before.apply(v); 14 | return apply(result); 15 | }; 16 | } 17 | 18 | // ... 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-1/FunctionExample.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-1. java.util.function.Function 6 | // 7 | 8 | import java.util.function.Function; 9 | 10 | public class FunctionExample { 11 | 12 | public static void main(String[] args) { 13 | 14 | Function stringLength = str -> str != null ? str.length() : 0; 15 | 16 | var result = stringLength.apply("Hello, Function!"); 17 | 18 | System.out.println("length = " + result); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-1/function-example.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-1. java.util.function.Function 6 | // 7 | 8 | import java.util.function.Function 9 | 10 | Function stringLength = str -> str != null ? str.length() : 0 11 | 12 | var result = stringLength.apply("Hello, Function!") 13 | System.out.println("length = " + result) -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-2/ConsumerExample.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-2. java.util.function.Consumer 6 | // 7 | 8 | import java.util.function.Consumer; 9 | 10 | public class ConsumerExample { 11 | 12 | public static void main(String[] args) { 13 | 14 | Consumer println = str -> System.out.println(str); 15 | 16 | println.accept("Hello, Consumer!"); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-2/consumer-example.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-2. java.util.function.Consumer 6 | // 7 | 8 | import java.util.function.Consumer; 9 | 10 | Consumer println = str -> System.out.println(str) 11 | 12 | println.accept("Hello, Consumer!") 13 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-3/SupplierExample.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-3. java.util.function.Supplier 6 | // 7 | 8 | import java.util.function.Supplier; 9 | 10 | public class SupplierExample { 11 | 12 | public static void main(String[] args) { 13 | 14 | Supplier random = () -> Math.random(); 15 | 16 | Double result = random.get(); 17 | 18 | System.out.println("random = " + result); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-3/supplier-example.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-3. java.util.function.Supplier 6 | // 7 | 8 | import java.util.function.Supplier 9 | 10 | Supplier random = () -> Math.random() 11 | 12 | Double result = random.get() 13 | System.out.println("random = " + result) 14 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-4/PredicateExample.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-4. java.util.function.Predicate 6 | // 7 | 8 | import java.util.function.Predicate; 9 | 10 | public class PredicateExample { 11 | 12 | public static void main(String[] args) { 13 | 14 | Predicate over9000 = i -> i > 9_000; 15 | 16 | boolean result = over9000.test(1_234); 17 | 18 | System.out.println("over 9000 = " + result); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-1/03-functional-jdk/figure-3-4/predicate-example.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 3 - Functional JDK 4 | // 5 | // Figure 3-4. java.util.function.Predicate 6 | // 7 | 8 | import java.util.function.Predicate 9 | 10 | Predicate over9000 = i -> i > 9_000 11 | 12 | boolean result = over9000.test(1_234) 13 | System.out.println("over 9000 = " + result) -------------------------------------------------------------------------------- /part-2/04-immutability/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4: Immutability 2 | 3 | ## The State of Java Immutability 4 | 5 | ### Immutable Collections 6 | 7 | #### Unmodifiable Collections 8 | 9 | Example of unmodifiable Collections. 10 | 11 | [unmodifiable-collections](unmodifiable-collections) 12 | 13 | #### Immutable Copies 14 | 15 | How to create immutable copies of Collections. 16 | 17 | [immutable-copy](immutable-copy) 18 | 19 | 20 | ### Immutable Math 21 | 22 | Doing immutable math with `BigDecimal`. 23 | 24 | [immutable-math](immutable-math) 25 | 26 | 27 | ### The final keyword 28 | 29 | Java class demonstrating `final` references. 30 | 31 | [Example 4-1](example-4-1) 32 | -------------------------------------------------------------------------------- /part-2/04-immutability/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 4: Functional JDK' 2 | 3 | ext.examples = [ 4 | 'unmodifiable-collections': [ 5 | 'ExceptionThrown': 'Unmodifiable List Exception', 6 | 'ModifyOriginal': 'Modify Original' 7 | ], 8 | 'example-4-1': ['CollectionsFinalReferences': 'Collections final references'], 9 | ] 10 | -------------------------------------------------------------------------------- /part-2/04-immutability/example-4-1/CollectionsFinalReferences.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // Example 4-1. Collections final references 6 | // 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class CollectionsFinalReferences { 12 | 13 | public static void main(String... args) { 14 | final List fruits = new ArrayList<>(); 15 | 16 | System.out.println(fruits.isEmpty()); 17 | 18 | fruits.add("Apple"); 19 | 20 | System.out.println(fruits.isEmpty()); 21 | 22 | // WON'T COMPILE: 23 | // fruits = List.of("Mango", "Melon"); 24 | // 25 | // ERROR 26 | // The final local variable fruits cannot be assigned. 27 | // It must be blank and not using a compound assignment 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /part-2/04-immutability/immutable-copy/ImmutableCopy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immtuability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | public class ImmutableCopy { 12 | 13 | public static void main(String[] args) { 14 | 15 | // SETUP ORIGINAL LIST 16 | List original = new ArrayList<>(); 17 | original.add("blue"); 18 | original.add("red"); 19 | 20 | // CREATE COPY 21 | List copiedList = List.copyOf(original); 22 | 23 | // ADD NEW ITEM TO ORIGINAL LIST 24 | original.add("green"); 25 | 26 | // CHECK CONTENT 27 | System.out.println("original = " + original); 28 | 29 | System.out.println("copy = " + copiedList); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/04-immutability/immutable-copy/immutable-copy.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immtuability 6 | // > Immutable Collections 7 | // 8 | 9 | // SETUP ORIGINAL LIST 10 | List original = new ArrayList<>() 11 | original.add("blue") 12 | original.add("red") 13 | 14 | // CREATE COPY 15 | List copiedList = List.copyOf(original) 16 | 17 | // ADD NEW ITEM TO ORIGINAL LIST 18 | original.add("green") 19 | 20 | // CHECK CONTENT 21 | System.out.println("original = " + original) 22 | System.out.println("copy = " + copiedList) 23 | -------------------------------------------------------------------------------- /part-2/04-immutability/immutable-math/ImmutableMath.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immtuability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.math.BigDecimal; 10 | 11 | public class ImmutableMath { 12 | 13 | public static void main(String[] args) { 14 | 15 | BigDecimal theAnswer = new BigDecimal(42); 16 | 17 | BigDecimal result = theAnswer.add(BigDecimal.ONE); 18 | 19 | System.out.println("result = " + result); 20 | System.out.println("theAnswer = " + theAnswer); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /part-2/04-immutability/immutable-math/immutable-math.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immtuability 6 | // > Immutable Collections 7 | // 8 | 9 | BigDecimal theAnswer = new BigDecimal(42) 10 | 11 | BigDecimal result = theAnswer.add(BigDecimal.ONE) 12 | 13 | System.out.println("result = " + result) 14 | System.out.println("theAnswer = " + theAnswer) 15 | -------------------------------------------------------------------------------- /part-2/04-immutability/unmodifiable-collections/ExceptionThrown.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immutability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class ExceptionThrown { 14 | 15 | public static void main(String[] args) { 16 | List modifiable = new ArrayList<>(); 17 | modifiable.add("blue"); 18 | modifiable.add("red"); 19 | 20 | List unmodifiable = Collections.unmodifiableList(modifiable); 21 | 22 | unmodifiable.clear(); 23 | // Exception in thread "main" java.lang.UnsupportedOperationException 24 | // at java.base/java.util.Collections$UnmodifiableCollection.clear(Collections.java:1086) 25 | // at UnmodifiableListException.main(UnmodifiableListException.java:23) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part-2/04-immutability/unmodifiable-collections/UnmodifiableListModifyOriginal.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immutability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class UnmodifiableListModifyOriginal { 14 | 15 | public static void main(String[] args) { 16 | List original = new ArrayList<>(); 17 | original.add("blue"); 18 | original.add("red"); 19 | 20 | List unmodifiable = Collections.unmodifiableList(original); 21 | 22 | original.add("green"); 23 | 24 | int size = unmodifiable.size(); 25 | System.out.println("unmodifiable = " + size); 26 | // => 3 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part-2/04-immutability/unmodifiable-collections/exception-thrown.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immutability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.util.ArrayList 10 | import java.util.Collections 11 | import java.util.List 12 | 13 | 14 | List modifiable = new ArrayList<>() 15 | modifiable.add("blue") 16 | modifiable.add("red") 17 | 18 | List unmodifiable = Collections.unmodifiableList(modifiable) 19 | unmodifiable.clear() 20 | 21 | // Exception java.lang.UnsupportedOperationException 22 | // at Collections$UnmodifiableCollection.clear (Collections.java:1086) 23 | // at (#5:1) 24 | -------------------------------------------------------------------------------- /part-2/04-immutability/unmodifiable-collections/modify-original.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 4 - Immutabilty 4 | // 5 | // The State of Java Immutability 6 | // > Immutable Collections 7 | // 8 | 9 | import java.util.ArrayList 10 | import java.util.Collections 11 | import java.util.List 12 | 13 | 14 | List original = new ArrayList<>() 15 | original.add("blue") 16 | original.add("red") 17 | 18 | List unmodifiable = Collections.unmodifiableList(original) 19 | 20 | original.add("green") 21 | 22 | System.out.println("unmodifiable = " + unmodifiable.size()) 23 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-10/SealedGroups.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | // Example 5-10. Group membership as sealed types instead of Optional 6 | // 7 | 8 | import java.time.LocalDateTime; 9 | 10 | public class SealedGroups { 11 | 12 | sealed interface Membership permits Group, NoGroup { 13 | // NO BODY 14 | } 15 | 16 | record Group(String name) implements Membership { 17 | // NO BODY 18 | } 19 | 20 | record NoGroup() implements Membership { 21 | // NO BODY 22 | } 23 | 24 | record User(String username, 25 | boolean active, 26 | Membership membership, 27 | LocalDateTime lastLogin) { 28 | // NO BODY 29 | } 30 | 31 | public static void main(String... args) { 32 | 33 | User user = new User("ben", 34 | true, 35 | new Group("admin"), 36 | LocalDateTime.now()); 37 | 38 | System.out.println("user = " + user); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-3/UserImmutable.disassembled: -------------------------------------------------------------------------------- 1 | Compiled from "UserImmutable.java" 2 | public final class UserImmutable { 3 | public User(java.lang.String, boolean, java.time.LocalDateTime); 4 | public java.lang.String getUsername(); 5 | public boolean isActive(); 6 | public java.time.LocalDateTime getLastLogin(); 7 | public int hashCode(); 8 | public boolean equals(java.lang.Object); 9 | public java.lang.String toString(); 10 | } 11 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-3/UserRecord.disassembled: -------------------------------------------------------------------------------- 1 | Compile form "UserRecord.java" 2 | public final class UserRecord extends java.lang.Record { 3 | public User(java.lang.String, boolean, java.time.LocalDateTime); 4 | public java.lang.String username(); 5 | public boolean active(); 6 | public java.time.LocalDateTime lastLogin(); 7 | public final int hashCode(); 8 | public final boolean equals(java.lang.Object); 9 | public final java.lang.String toString(); 10 | } 11 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-5/ComposedDataStructures.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | // Example 5-5. Canonical constructors in composed data structures 6 | // 7 | 8 | public class ComposedDataStructures { 9 | 10 | record Origin(int x, int y) { 11 | // NO BODY 12 | } 13 | 14 | record Rectangle(Origin origin, int width, int height) { 15 | // NO BODY 16 | } 17 | 18 | public static void main(String[] args) { 19 | var rectangle = new Rectangle(new Origin(23, 42), 300, 400); 20 | 21 | System.out.println("rectangle = " + rectangle); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-6/custom-constructors-default-values.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | // Example 5-6. Custom constructors for default values 6 | // 7 | 8 | record Origin(int x, int y) { 9 | 10 | // Additional constructor for convenience 11 | Origin() { 12 | this(0, 0); 13 | } 14 | } 15 | 16 | record Rectangle(Origin origin, int width, int height) { 17 | 18 | // Multiple convenience constructors 19 | 20 | Rectangle(int x, int y, int width, int height) { 21 | this(new Origin(x, y), width, height); 22 | } 23 | 24 | Rectangle(int width, int height) { 25 | this(new Origin(), width, height); 26 | } 27 | } 28 | 29 | var rect1 = new Rectangle(300, 400) 30 | var rect2 = new Rectangle(new Origin(), 300, 400) 31 | var rect3 = new Rectangle(new Origin(0, 0), 300, 400) 32 | 33 | System.out.println("rect1 eq rect2 = " + rect1.equals(rect2)) 34 | System.out.println("rect2 eq rect3 = " + rect2.equals(rect3)) 35 | System.out.println("rect3 eq rect1 = " + rect3.equals(rect1)) 36 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-7/user-builder.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | // Example 5-7. User Builder 6 | // 7 | 8 | import java.time.LocalDateTime 9 | 10 | record User(String username, boolean active, LocalDateTime lastLogin) { 11 | // NO BODY 12 | } 13 | 14 | final class Builder { 15 | 16 | private final String username; 17 | 18 | private boolean active; 19 | private LocalDateTime lastLogin; 20 | 21 | Builder(String username) { 22 | this.username = username; 23 | this.active = true; 24 | } 25 | 26 | Builder active(boolean isActive) { 27 | if (this.active == false) { 28 | throw new IllegalArgumentException("..."); 29 | } 30 | 31 | this.active = isActive; 32 | 33 | return this; 34 | } 35 | 36 | Builder lastLogin(LocalDateTime lastLogin) { 37 | this.lastLogin = lastLogin; 38 | 39 | return this; 40 | } 41 | 42 | User build() { 43 | return new User(this.username, this.active, this.lastLogin); 44 | } 45 | } 46 | 47 | var builder = new Builder("ben").active(false).lastLogin(LocalDateTime.now()) 48 | User user = builder.build() 49 | 50 | System.out.println("user = " + user) 51 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/example-5-8/User.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | // Example 5-8. Neste builder 6 | // 7 | 8 | import java.time.LocalDateTime; 9 | 10 | public record User(String username, boolean active, LocalDateTime lastLogin) { 11 | 12 | // NO BODY 13 | public static final class Builder { 14 | 15 | private final String username; 16 | 17 | private boolean active; 18 | private LocalDateTime lastLogin; 19 | 20 | public Builder(String username) { 21 | this.username = username; 22 | this.active = true; 23 | } 24 | 25 | public Builder active(boolean isActive) { 26 | if (this.active == false) { 27 | throw new IllegalArgumentException("..."); 28 | } 29 | 30 | this.active = isActive; 31 | 32 | return this; 33 | } 34 | 35 | public Builder lastLogin(LocalDateTime lastLogin) { 36 | this.lastLogin = lastLogin; 37 | 38 | return this; 39 | } 40 | 41 | public User build() { 42 | return new User(this.username, this.active, this.lastLogin); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/missing-record-features/FactoryMethods.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class FactoryMethods { 7 | 8 | record Origin(int x, int y) { 9 | // NO BODY 10 | } 11 | 12 | record Rectangle(Origin origin, int width, int height) { 13 | 14 | Rectangle(int x, int y, int width, int height) { 15 | this(new Origin(x, y), width, height); 16 | } 17 | 18 | // FACTORY METHODS 19 | 20 | static Rectangle atX(int x, int width, int height) { 21 | return new Rectangle(x, 0, width, height); 22 | } 23 | 24 | static Rectangle atY(int y, int width, int height) { 25 | return new Rectangle(0, y, width, height); 26 | } 27 | } 28 | 29 | public static void main(String... args) { 30 | 31 | var rectangle1 = new Rectangle(0, 123, 300, 400); 32 | var rectangle2 = Rectangle.atY(123, 300, 400); 33 | 34 | var isEqual = rectangle1.equals(rectangle2); 35 | System.out.println("rect1 eq rect2 = " + isEqual); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/missing-record-features/factory-methods.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record Origin(int x, int y) { 7 | // NO BODY 8 | } 9 | 10 | 11 | record Rectangle(Origin origin, int width, int height) { 12 | 13 | Rectangle(int x, int y, int width, int height) { 14 | this(new Origin(x, y), width, height); 15 | } 16 | 17 | 18 | // FACTORY METHODS 19 | 20 | static Rectangle atX(int x, int width, int height) { 21 | return new Rectangle(x, 0, width, height); 22 | } 23 | 24 | static Rectangle atY(int y, int width, int height) { 25 | return new Rectangle(0, y, width, height); 26 | } 27 | } 28 | 29 | 30 | var rect1 = new Rectangle(0, 123, 300, 400) 31 | var rect2 = Rectangle.atY(123, 300, 400) 32 | 33 | var isEqual = rect1.equals(rect2) 34 | System.out.println("rect1 eq rect2 = " + isEqual) 35 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-features/CanonicalConstructor.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Objects; 8 | 9 | public class CanonicalConstructor { 10 | 11 | record User(String username, 12 | boolean active, 13 | LocalDateTime lastLogin) { 14 | 15 | public User(String username, 16 | boolean active, 17 | LocalDateTime lastLogin) { 18 | 19 | Objects.requireNonNull(username); 20 | Objects.requireNonNull(lastLogin); 21 | 22 | this.username = username.toLowerCase(); 23 | this.active = active; 24 | this.lastLogin = lastLogin; 25 | } 26 | } 27 | 28 | public static void main(String... args) { 29 | 30 | var user = new User("BEN", true, LocalDateTime.now()); 31 | 32 | System.out.println("user = " + user); 33 | 34 | // THIS CALL THROWS 35 | // var throwingUser = new User(null, true, LocalDateTime.now()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-features/CompactConstructor.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Objects; 8 | 9 | public class CompactConstructor { 10 | record User(String username, 11 | boolean active, 12 | LocalDateTime lastLogin) { 13 | 14 | public User { 15 | 16 | Objects.requireNonNull(username); 17 | Objects.requireNonNull(lastLogin); 18 | 19 | username = username.toLowerCase(); 20 | } 21 | } 22 | 23 | 24 | public static void main(String... args) { 25 | 26 | var user = new User("BEN", true, LocalDateTime.now()); 27 | 28 | System.out.println("user = " + user); 29 | 30 | // THIS CALL THROWS 31 | // var throwingUser = new User(null, true, LocalDateTime.now()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-features/DerivedState.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public class DerivedState { 9 | 10 | record User(String username, 11 | boolean active, 12 | LocalDateTime lastLogin) { 13 | 14 | boolean hasLoggedInAtLeastOnce() { 15 | return this.lastLogin != null; 16 | } 17 | } 18 | 19 | public static void main(String[] args) { 20 | 21 | var neverLoggedIn = new User("ben", 22 | true, 23 | null); 24 | 25 | System.out.println("Logged in before = " + neverLoggedIn.hasLoggedInAtLeastOnce()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-features/Generics.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class Generics { 7 | 8 | record Container(T content, String identifier) { } 9 | 10 | public static void main(String[] args) { 11 | 12 | Container stringContainer = new Container<>("hello, String!", 13 | "a String container"); 14 | 15 | System.out.println("content = " + stringContainer.content()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-features/OverrideComponentAccessor.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public class OverrideComponentAccessor { 9 | 10 | record User(String username, boolean active, LocalDateTime lastLogin) { 11 | 12 | @Override 13 | public String username() { 14 | if (this.username == null) { 15 | return "n/a"; 16 | } 17 | 18 | return this.username; 19 | } 20 | } 21 | 22 | public static void main(String[] args) { 23 | 24 | var user = new User(null, true, LocalDateTime.now()); 25 | 26 | System.out.println("username via accessor = " + user.username()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-optional-data-handling/NonNullContainer.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | import java.time.LocalDateTime; 9 | 10 | public class NonNullContainer { 11 | 12 | record User(String username, 13 | boolean active, 14 | Optional group, 15 | LocalDateTime lastLogin) { 16 | 17 | public User { 18 | Objects.requireNonNull(group, "Optional group must not be null"); 19 | } 20 | } 21 | 22 | public static void main(String... args) { 23 | 24 | var throwingUser = new User("ben", true, null, LocalDateTime.now()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-optional-data-handling/convenience-constructor.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.util.Optional 7 | import java.time.LocalDateTime 8 | 9 | record User(String username, 10 | boolean active, 11 | Optional group, 12 | LocalDateTime lastLogin) { 13 | 14 | User(String username, 15 | boolean active, 16 | String group, 17 | LocalDateTime lastLogin) { 18 | this(username, 19 | active, 20 | Optional.ofNullable(group), 21 | lastLogin); 22 | } 23 | 24 | User(String username, 25 | boolean active, 26 | LocalDateTime lastLogin) { 27 | this(username, 28 | active, 29 | Optional.empty(), 30 | lastLogin); 31 | } 32 | } 33 | 34 | var userWithGroup = new User("ben", true, "admin", LocalDateTime.now()) 35 | System.out.println("With group: " + userWithGroup) 36 | 37 | var userWithoutGroup = new User("jane", true, LocalDateTime.now()) 38 | System.out.println("Without group: " + userWithoutGroup) 39 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-optional-data-handling/non-null-container.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.util.Objects 7 | import java.util.Optional 8 | import java.time.LocalDateTime 9 | 10 | 11 | record User(String username, 12 | boolean active, 13 | Optional group, 14 | LocalDateTime lastLogin) { 15 | 16 | public User { 17 | Objects.requireNonNull(group, "Optional group must not be null"); 18 | } 19 | } 20 | 21 | var throwingUser = new User("ben", true, null, LocalDateTime.now()) 22 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/BuilderPatternCopy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class BuilderPatternCopy { 7 | 8 | record Point(int x, int y) { 9 | 10 | static final class Builder { 11 | 12 | private int x; 13 | private int y; 14 | 15 | Builder(Point point) { 16 | this.x = point.x(); 17 | this.y = point.y(); 18 | } 19 | 20 | Builder x(int x) { 21 | this.x = x; 22 | return this; 23 | } 24 | 25 | Builder y(int y) { 26 | this.y = y; 27 | return this; 28 | } 29 | 30 | Point build() { 31 | return new Point(this.x, this.y); 32 | } 33 | } 34 | } 35 | 36 | public static void main(String... args) { 37 | 38 | var original = new Point(23, 42); 39 | System.out.println("original = " + original); 40 | 41 | var updated = new Point.Builder(original) 42 | .x(5) 43 | .build(); 44 | 45 | System.out.println("updated = " + updated); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/DataScrubbing.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class DataScrubbing { 7 | 8 | record Time(int minutes, int seconds) { 9 | 10 | Time { 11 | if (seconds >= 60) { 12 | int additionalMinutes = seconds / 60; 13 | minutes += additionalMinutes; 14 | seconds -= additionalMinutes * 60; 15 | } 16 | } 17 | } 18 | 19 | public static void main(String... args) { 20 | 21 | var time = new Time(12, 67); 22 | System.out.println("scrubbed = " + time); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/IncreaseImmutability.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.util.Collections; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class IncreaseImmutability { 11 | 12 | record Container(List values) { 13 | 14 | public Container { 15 | values = Collections.unmodifiableList(values); 16 | } 17 | } 18 | 19 | public static void main(String... args) { 20 | 21 | List mutableList = new ArrayList<>(); 22 | mutableList.add("first item"); 23 | 24 | var container = new Container(mutableList); 25 | 26 | 27 | container.values().add("second items"); 28 | // throws UnsupportedOperationException 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/Validation.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class Validation { 7 | 8 | record NeedsValidation(int x, int y) { 9 | 10 | NeedsValidation { 11 | if (x < y) { 12 | throw new IllegalArgumentException("x must be equal or greater than y"); 13 | } 14 | } 15 | } 16 | 17 | public static void main(String... args) { 18 | var throwsAnException = new NeedsValidation(23, 42); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/builder-pattern-copy.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record Point(int x, int y) { 7 | 8 | static final class Builder { 9 | 10 | private int x; 11 | private int y; 12 | 13 | Builder(Point point) { 14 | this.x = point.x(); 15 | this.y = point.y(); 16 | } 17 | 18 | Builder x(int x) { 19 | this.x = x; 20 | return this; 21 | } 22 | 23 | Builder y(int y) { 24 | this.y = y; 25 | return this; 26 | } 27 | 28 | Point build() { 29 | return new Point(this.x, this.y); 30 | } 31 | } 32 | } 33 | 34 | { 35 | var original = new Point(23, 42); 36 | System.out.println("original = " + original); 37 | 38 | 39 | var updated = new Point.Builder(original) 40 | .x(5) 41 | .build(); 42 | 43 | System.out.println("updated = " + updated); 44 | } 45 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/data-scrubbing.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record Time(int minutes, int seconds) { 7 | 8 | public Time { 9 | if (seconds >= 60) { 10 | int additionalMinutes = seconds / 60; 11 | minutes += additionalMinutes; 12 | seconds -= additionalMinutes * 60; 13 | } 14 | } 15 | } 16 | 17 | var time = new Time(12, 67) 18 | System.out.println("scrubbed = " + time) 19 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/increase-immutability.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | 10 | record Container(List values) { 11 | 12 | public Container { 13 | values = Collections.unmodifiableList(values); 14 | } 15 | } 16 | 17 | List mutableList = new ArrayList<>() 18 | mutableList.add("first item") 19 | 20 | var container = new Container(mutableList) 21 | 22 | 23 | container.values().add("second items") 24 | // Exception java.lang.UnsupportedOperationException 25 | // at Collections$UnmodifiableCollection.add (Collections.java:1067) 26 | // at (#7:1) 27 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/pattern-matching.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record Point(int x, int y) { 7 | // NO BODY 8 | } 9 | 10 | var point = new Point(23, 42); 11 | 12 | if (point instanceof Point(int x, int y)) { 13 | System.out.println("point x+y = " + (x + y)); 14 | // => 65 15 | } 16 | 17 | record Point3D(int x, int y, int z) { 18 | // NO BODY 19 | } 20 | 21 | Object anyObject = new Point3D(1, 2, 3) 22 | 23 | int result = switch (anyObject) { 24 | case Point(var x, var y) -> x + y; 25 | case Point3D(var x, var y, var z) -> x + y + z; 26 | default -> 0; 27 | } 28 | 29 | System.out.println("result = " + result) 30 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/sealed-types.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | 7 | sealed interface Origin permits Point, Rectangle, Circle { 8 | 9 | int x(); 10 | int y(); 11 | 12 | default String origin() { 13 | return String.format("(%d/%d)", x(), y()); 14 | } 15 | } 16 | 17 | 18 | record Point(int x, int y) implements Origin { 19 | // ... 20 | } 21 | 22 | record Rectangle(int x, int y, int width, int height) implements Origin { 23 | // ... 24 | } 25 | 26 | 27 | record Circle(int x, int y, int radius) implements Origin { 28 | // ... 29 | } 30 | 31 | record Triangle(int x1, int y1, 32 | int x2, int y2, 33 | int x3, int y3) implements Origin { 34 | // ... 35 | } 36 | // Error: 37 | // class is not allowed to extend sealed class: Origin (as it is not 38 | // listed in its 'permits' clause) 39 | // record Triangle(int x1, int y1, 40 | // ^------------------------------... 41 | 42 | 43 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-use-cases-common-practices/validation.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record NeedsValidation(int x, int y) { 7 | 8 | public NeedsValidation { 9 | if (x < y) { 10 | throw new IllegalArgumentException("x must be equal or greater than y"); 11 | } 12 | } 13 | } 14 | 15 | var throwsAnException = new NeedsValidation(23, 42); 16 | // Exception java.lang.IllegalArgumentException: x must be equal or greater than y 17 | // at NeedsValidation. (#1:5) 18 | // at do_it$Aux (#2:1) 19 | // at (#2:1) 20 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-wither/Wither.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class Wither { 7 | 8 | record Point(int x, int y) { 9 | 10 | Point withX(int newX) { 11 | return new Point(newX, y()); 12 | } 13 | 14 | Point withY(int newY) { 15 | return new Point(x(), newY); 16 | } 17 | } 18 | 19 | public static void main(String... args) { 20 | 21 | Point source = new Point(23, 42); 22 | System.out.println("source = " + source); 23 | 24 | Point modified = source.withX(5); 25 | System.out.println("modified = " + modified); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-wither/WitherNested.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public class WitherNested { 7 | 8 | record Point(int x, int y) { 9 | 10 | With with() { 11 | return new With(this); 12 | } 13 | 14 | record With(Point source) { 15 | 16 | Point x(int x) { 17 | return new Point(x, this.source.y()); 18 | } 19 | 20 | Point y(int y) { 21 | return new Point(this.source.x(), y); 22 | } 23 | } 24 | } 25 | 26 | public static void main(String... args) { 27 | 28 | var source = new Point(23, 42); 29 | System.out.println("source = " + source); 30 | 31 | var modified = sourcePoint.with().x(5); 32 | System.out.println("source = " + source); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-wither/wither-nested.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | record Point(int x, int y) { 7 | 8 | With with() { 9 | return new With(this); 10 | } 11 | 12 | record With(Point source) { 13 | 14 | Point x(int x) { 15 | return new Point(x, source.y()); 16 | } 17 | 18 | Point y(int y) { 19 | return new Point(source.x(), y); 20 | } 21 | } 22 | } 23 | 24 | var source = new Point(23, 42) 25 | System.out.println("source = " + source) 26 | 27 | var modified = sourcePoint.with().x(5) 28 | System.out.println("source = " + source) 29 | 30 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/record-wither/wither.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | public record Point(int x, int y) { 7 | 8 | public Point withX(int newX) { 9 | return new Point(newX, y()); 10 | } 11 | 12 | public Point withY(int newY) { 13 | return new Point(x(), newY); 14 | } 15 | } 16 | 17 | var source = new Point(23, 42); 18 | System.out.println("source = " + source) 19 | 20 | var modified = source.with().x(5) 21 | System.out.println("modified = " + modified) 22 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/serializing-evolving-records/PointSerializationIdentical.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.io.FileInputStream; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.ObjectInputStream; 10 | import java.io.ObjectOutputStream; 11 | import java.io.Serializable; 12 | 13 | public class PointSerializationIdentical { 14 | 15 | record Point(int x, int y) implements Serializable { 16 | } 17 | 18 | record IdenticalPoint(int x, int y) implements Serializable { 19 | } 20 | 21 | public static void main(String... args) throws ClassNotFoundException, IOException { 22 | 23 | var point = new Point(23, 42); 24 | try (var out = new ObjectOutputStream(new FileOutputStream("point.data"))) { 25 | out.writeObject(point); 26 | } 27 | 28 | var in = new ObjectInputStream(new FileInputStream("point.data")); 29 | IdenticalPoint identicalPoint = in.readObject(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/tuples/tuples.py: -------------------------------------------------------------------------------- 1 | # 2 | # A FUNCTIONAL APPROACH TO JAVA 3 | # Chapter 5 - Working With Records 4 | # 5 | # Data Aggregation Types 6 | # > Tuples 7 | # 8 | 9 | apple = ("apple", "green") 10 | banana = ("banana", "yellow") 11 | cherry = ("cherry", "red") 12 | 13 | fruits = [apple, banana, cherry] 14 | 15 | for fruit in fruits: 16 | print "The", fruit[0], "is", fruit[1] 17 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/tuples/tuples.swift: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working With Records 4 | // 5 | // Data Aggregation Types 6 | // > Tuples 7 | // 8 | 9 | typealias Fruit = (name: String, color: String) 10 | 11 | let fruits: [Fruit] = [ 12 | (name: "apple", color: "green"), 13 | (name: "banana", color: "yellow"), 14 | (name: "cherry", color: "red") 15 | ] 16 | 17 | for fruit in fruits { 18 | println("The \(fruit.name) is \(fruit.color)") 19 | } 20 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/user-record/UserRecord.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Objects; 8 | 9 | public record UserRecord(String username, 10 | boolean active, 11 | LocalDateTime lastLogin) { 12 | // NO BODY 13 | } 14 | -------------------------------------------------------------------------------- /part-2/05-working-with-records/user-record/user-record.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 5 - Working with Records 4 | // 5 | 6 | import java.time.LocalDateTime 7 | 8 | 9 | record User(String username, 10 | boolean active, 11 | LocalDateTime lastLogin) { 12 | // NO BODY 13 | } 14 | 15 | var user = new User("ben", true, LocalDateTime.now()); 16 | 17 | System.out.println("username = " + user.username()); 18 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 6: Data Processing with Streams' 2 | 3 | ext.examples = [ 4 | 'example-6-1': ['FindingBooksForLoop': 'Finding books with a for-loop'], 5 | 'example-6-2': ['FindingBooksStream': 'Finding books with Streams'], 6 | 'example-6-7': ['FlatMapMapMulti': 'Shape flatMap versus mapMulti'], 7 | 'example-6-8': ['Peek': 'Peeking into a Stream'], 8 | 'example-6-9': ['MaxInCollection': 'Finding the max number of a collection'], 9 | 'example-6-10': ['ReduceLikeForLoop': 'Reduce-like for-loop'], 10 | 'example-6-11': ['ReduceVsMapReduce': 'Three-arguments reduce() versus map + two-arguments reduce'], 11 | 'example-6-12': ['ReduceAggregate': 'Aggregate elements with the reduce operation'], 12 | 'example-6-13': ['ReduceImmutableAggregation': 'Immutable accumulation of numbers with a Stream'], 13 | 'example-6-14': ['ReduceVsCollect': 'Concatenating String elements with reduce and collect'], 14 | 'example-6-15': ['FruitsNaive': 'Fruit pipeline (naïve)'], 15 | 'example-6-16': ['FruitsImproved': 'Fruit pipeline (improved)'], 16 | 'short-circuit': [ 17 | 'Dropped': 'Map operation gets dropped', 18 | 'NotDropped': 'Map operation is not dropped' 19 | ] 20 | ] 21 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-10/ReduceLikeForLoop.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-10. Reduce-like for-loop 6 | // 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.function.BinaryOperator; 11 | 12 | public class ReduceLikeForLoop { 13 | 14 | private static T reduce(Collection elements, 15 | T initialValue, 16 | BinaryOperator accumulator) { 17 | 18 | T result = initialValue; 19 | 20 | for (T element : elements) { 21 | result = accumulator.apply(result, element); 22 | } 23 | 24 | return result; 25 | } 26 | 27 | private static Integer max(Collection numbers) { 28 | return reduce(numbers, 29 | Integer.MIN_VALUE, 30 | Math::max); 31 | } 32 | 33 | public static void main(String[] args) { 34 | 35 | List numbers = List.of(3, 1_234, 999, 42, -23, 0); 36 | 37 | var result = max(numbers); 38 | 39 | System.out.println("max number = " + result); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-10/reduce-like-for-loop.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-10. Reduce-like for-loop 6 | // 7 | 8 | import java.util.Collection 9 | import java.util.List 10 | import java.util.function.BinaryOperator 11 | 12 | T reduce(Collection elements, 13 | T initialValue, 14 | BinaryOperator accumulator) { 15 | 16 | T result = initialValue; 17 | 18 | for (T element : elements) { 19 | result = accumulator.apply(result, element); 20 | } 21 | 22 | return result; 23 | } 24 | 25 | Integer max(Collection numbers) { 26 | return reduce(numbers, 27 | Integer.MIN_VALUE, 28 | Math::max); 29 | } 30 | 31 | var numbers = List.of(3, 1_234, 999, 42, -23, 0) 32 | 33 | var result = max(numbers) 34 | 35 | System.out.println("max number = " + result) 36 | 37 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-11/ReduceVsMapReduce.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-11. Three-arguments reduce() versus map + two-arguments reduce 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class ReduceVsMapReduce { 11 | 12 | public static void main(String... args) { 13 | 14 | Integer reduceOnly = Stream.of("apple", "orange", "banana") 15 | .reduce(0, 16 | (acc, str) -> acc + str.length(), 17 | Integer::sum); 18 | 19 | System.out.println("reduceOnly: " + reduceOnly); 20 | 21 | int mapReduce = Stream.of("apple", "orange", "banana") 22 | .mapToInt(String::length) 23 | .reduce(0, (acc, length) -> acc + length); 24 | 25 | System.out.println("mapReduce: " + mapReduce); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-11/reduce-vs-map-reduce.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-11. Three-arguments reduce() versus map + two-arguments reduce 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | { 11 | Integer reduceOnly = Stream.of("apple", "orange", "banana") 12 | .reduce(0, 13 | (acc, str) -> acc + str.length(), 14 | Integer::sum); 15 | 16 | System.out.println("reduce only = " + reduceOnly); 17 | } 18 | 19 | { 20 | int mapReduce = Stream.of("apple", "orange", "banana") 21 | .mapToInt(String::length) 22 | .reduce(0, (acc, length) -> acc + length); 23 | 24 | System.out.println("map+reduce = " + mapReduce); 25 | } 26 | 27 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-12/reduce-aggregate.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-12. Aggregate elements with the reduce operation 6 | // 7 | 8 | import java.util.ArrayList; 9 | import java.util.stream.Stream; 10 | 11 | { 12 | var fruits = Stream.of("apple", "orange", "banana", "peach") 13 | // Any additional operations would be here. 14 | // This example only highlights the 15 | // aggregation aspect. 16 | .reduce(new ArrayList<>(), 17 | (acc, fruit) -> { 18 | var list = new ArrayList<>(acc); 19 | list.add(fruit); 20 | return list; 21 | }, 22 | (lhs, rhs) -> { 23 | var list = new ArrayList<>(lhs); 24 | list.addAll(rhs); 25 | return list; 26 | }); 27 | 28 | System.out.println("aggregated = " + fruits); 29 | } -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-13/ReduceImmutableAggregation.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-13. Immutable accumulation of numbers with a Stream 6 | // 7 | 8 | import java.util.List; 9 | 10 | public class ReduceImmutableAggregation { 11 | 12 | public static void main(String[] args) { 13 | 14 | var numbers = List.of(1, 2, 3, 4, 5, 6); 15 | 16 | int total = numbers.stream() 17 | .reduce(0, Integer::sum); 18 | 19 | System.out.println("total = " + total); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-13/reduce-immutable-aggregation.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-13. Immutable accumulation of numbers with a Stream 6 | // 7 | 8 | import java.util.List 9 | 10 | var numbers = List.of(1, 2, 3, 4, 5, 6) 11 | 12 | int total = numbers.stream() .reduce(0, Integer::sum); 13 | 14 | System.out.println("total = " + total) 15 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-14/reduce-vs-collect.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-14. Concatenating String elements with reduce and collect 6 | // 7 | 8 | import java.util.List 9 | import java.util.StringJoiner 10 | import java.util.stream.Collector 11 | import java.util.stream.Collectors 12 | 13 | var strings = List.of("a", "b", "c", "d", "e") 14 | 15 | // STREAM REDUCE 16 | var reduced = strings.stream() .reduce("", String::concat) 17 | System.out.println("reduced = " + reduced) 18 | 19 | 20 | // STREAM COLLECT - CUSTOM 21 | { 22 | var joiner = strings.stream() 23 | .collect(Collector.of(() -> new StringJoiner(""), 24 | StringJoiner::add, 25 | StringJoiner::merge, 26 | StringJoiner::toString)); 27 | 28 | System.out.println("joiner = " + joiner); 29 | } 30 | 31 | // STREAM COLLECT - PRE-DEFINED 32 | { 33 | var collectWithCollectors = strings.stream() 34 | .collect(Collectors.joining()); 35 | 36 | System.out.println("collectWithCollectors = " + collectWithCollectors); 37 | } 38 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-15/FruitsNaive.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-15. Fruit pipeline (naïve) 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class FruitsNaive { 11 | 12 | public static void main(String... args) { 13 | Stream.of("ananas", "oranges", "apple", "pear", "banana") 14 | .map(String::toUpperCase) 15 | .sorted() 16 | .filter(s -> s.startsWith("A")) 17 | .forEach(System.out::println); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-15/fruits-naive.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-15. Fruit pipeline (naïve) 6 | // 7 | 8 | import java.util.stream.Stream 9 | 10 | { 11 | Stream.of("ananas", "oranges", "apple", "pear", "banana") 12 | .map(String::toUpperCase) 13 | .sorted() 14 | .filter(s -> s.startsWith("A")) 15 | .forEach(System.out::println); 16 | } 17 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-16/FruitsImproved.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-15. Fruit pipeline (improved) 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class FruitsImproved { 11 | 12 | public static void main(String... args) { 13 | Stream.of("ananas", "oranges", "apple", "pear", "banana") 14 | .filter(s -> s.startsWith("a")) 15 | .map(String::toUpperCase) 16 | .sorted() 17 | .forEach(System.out::println); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-16/fruits-improved.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-15. Fruit pipeline (improved) 6 | // 7 | 8 | import java.util.stream.Stream 9 | 10 | { 11 | Stream.of("ananas", "oranges", "apple", "pear", "banana") 12 | .filter(s -> s.startsWith("a")) 13 | .map(String::toUpperCase) 14 | .sorted() 15 | .forEach(System.out::println); 16 | } 17 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-3/README.md: -------------------------------------------------------------------------------- 1 | # Example 6-3. The java.util.Spliterator interface 2 | 3 | This example only shows a simplified but non-compilable version of the `Spliterator` interface. 4 | 5 | ```java 6 | public interface Spliterator { 7 | 8 | // CHARACTERISTICS 9 | int characteristics(); 10 | default boolean hasCharacteristics(int characteristics) { 11 | // ... 12 | } 13 | 14 | // ITERATION 15 | boolean tryAdvance(Consumer action); 16 | default void forEachRemaining(Consumer action) { 17 | // ... 18 | } 19 | 20 | // SPLITTING 21 | Spliterator trySplit(); 22 | 23 | // SIZE 24 | long estimateSize(); 25 | default long getExactSizeIfKnown() { 26 | // ... 27 | } 28 | 29 | // COMPARATOR 30 | default Comparator getComparator() { 31 | // ... 32 | } 33 | } 34 | ``` -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-4/README.md: -------------------------------------------------------------------------------- 1 | # Example 6-4. Spliterator characteristics of HashSet 2 | 3 | This example only shows a single method extracted from `java.util.HashSet`. 4 | 5 | ```java 6 | public int characteristics() { 7 | return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | 8 | Spliterator.DISTINCT; 9 | } 10 | ``` 11 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-5/README.md: -------------------------------------------------------------------------------- 1 | # Example 6-5. Simplified Stream creation for Collection-based types 2 | 3 | This example highlights the barebone setup for creating Streams with `Collection`-based data structures, provided by `default` methods. 4 | 5 | ```java 6 | public interface Collection extends Iterable { 7 | 8 | default Stream stream() { 9 | return StreamSupport.stream(spliterator(), false); 10 | } 11 | 12 | default Stream parallelStream() { 13 | return StreamSupport.stream(spliterator(), true); 14 | } 15 | 16 | @Override 17 | default Spliterator spliterator() { 18 | return Spliterators.spliterator(this, 0); 19 | } 20 | 21 | // ... 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-6/Shape.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-6. A simple Shape type 6 | // 7 | 8 | import java.util.List; 9 | 10 | public record Shape(int corners) implements Comparable { 11 | 12 | // HELPER METHODS 13 | 14 | public boolean hasCorners() { 15 | return corners() > 0; 16 | } 17 | 18 | public List twice() { 19 | return List.of(this, this); 20 | } 21 | 22 | @Override 23 | public int compareTo(Shape o) { 24 | return Integer.compare(corners(), o.corners()); 25 | } 26 | 27 | // FACTORY METHODS 28 | 29 | public static Shape circle() { 30 | return new Shape(0); 31 | } 32 | 33 | public static Shape triangle() { 34 | return new Shape(3); 35 | } 36 | 37 | public static Shape square() { 38 | return new Shape(4); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-8/peek.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-8. Peeking into a Stream 6 | // 7 | 8 | record Shape(int corners) implements Comparable { 9 | 10 | // HELPER METHODS 11 | 12 | public boolean hasCorners() { 13 | return corners() > 0; 14 | } 15 | 16 | public List twice() { 17 | return List.of(this, this); 18 | } 19 | 20 | @Override 21 | public int compareTo(Shape o) { 22 | return Integer.compare(corners(), o.corners()); 23 | } 24 | 25 | // FACTORY METHODS 26 | 27 | public static Shape circle() { 28 | return new Shape(0); 29 | } 30 | 31 | public static Shape triangle() { 32 | return new Shape(3); 33 | } 34 | 35 | public static Shape square() { 36 | return new Shape(4); 37 | } 38 | } 39 | 40 | List result = 41 | Stream.of(Shape.square(), Shape.triangle(), Shape.circle()) 42 | .map(Shape::twice) 43 | .flatMap(List::stream) 44 | .peek(shape -> System.out.println("current: " + shape)) 45 | .filter(shape -> shape.corners() < 4) 46 | .collect(Collectors.toList()); 47 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-9/MaxInCollection.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-9. Finding the max number of a collection 6 | // 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | 11 | public class MaxInCollection { 12 | 13 | private static Integer max(Collection numbers) { 14 | int result = Integer.MIN_VALUE; 15 | 16 | for (var value : numbers) { 17 | result = Math.max(result, value); 18 | } 19 | 20 | return result; 21 | } 22 | 23 | public static void main(String[] args) { 24 | 25 | List numbers = List.of(3, 1_234, 999, 42, -23, 0); 26 | 27 | var result = max(numbers); 28 | 29 | System.out.println("max number = " + result); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/example-6-9/max-in-collection.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | // Example 6-9. Finding the max number of a collection 6 | // 7 | 8 | import java.util.Collection 9 | import java.util.List 10 | 11 | Integer max(Collection numbers) { 12 | int result = Integer.MIN_VALUE; 13 | 14 | for (var value : numbers) { 15 | result = Math.max(result, value); 16 | } 17 | 18 | return result; 19 | } 20 | 21 | 22 | var numbers = List.of(3, 1_234, 999, 42, -23, 0) 23 | 24 | var result = max(numbers) 25 | 26 | System.out.println("Max number: " + result) 27 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/short-circuit/Dropped.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | 6 | import java.util.stream.Stream; 7 | 8 | public class Dropped { 9 | 10 | public static void main(String... args) { 11 | var result = Stream.of("apple", "orange", "banana", "melon") 12 | .peek(str -> System.out.println("peek 1: " + str)) 13 | .map(str -> { 14 | System.out.println("map: " + str); 15 | return str.toUpperCase(); 16 | }) 17 | .peek(str -> System.out.println("peek 2: " + str)) 18 | .count(); 19 | 20 | System.out.println("count = " + result); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/short-circuit/NotDropped.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | 6 | import java.util.stream.Stream; 7 | 8 | public class NotDropped { 9 | 10 | public static void main(String... args) { 11 | 12 | var result = Stream.of("apple", "orange", "banana", "melon") 13 | .filter(str -> str.contains("e")) 14 | .peek(str -> System.out.println("peek 1: " + str)) 15 | .map(str -> { 16 | System.out.println("map: " + str); 17 | return str.toUpperCase(); 18 | }) 19 | .peek(str -> System.out.println("peek 2: " + str)) 20 | .count(); 21 | 22 | System.out.println("count = " + result); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/short-circuit/dropped.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | 6 | import java.util.stream.Stream 7 | 8 | { 9 | var result = Stream.of("apple", "orange", "banana", "melon") 10 | .peek(str -> System.out.println("peek 1: " + str)) 11 | .map(str -> { 12 | System.out.println("map: " + str); 13 | return str.toUpperCase(); 14 | }) 15 | .peek(str -> System.out.println("peek 2: " + str)) 16 | .count(); 17 | 18 | System.out.println("count = " + result); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /part-2/06-data-processing-with-streams/short-circuit/not-dropped.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 6 - Data Processing with Streams 4 | // 5 | 6 | import java.util.stream.Stream 7 | 8 | { 9 | var result = Stream.of("apple", "orange", "banana", "melon") 10 | .filter(str -> str.contains("e")) 11 | .peek(str -> System.out.println("peek 1: " + str)) 12 | .map(str -> { 13 | System.out.println("map: " + str); 14 | return str.toUpperCase(); 15 | }) 16 | .peek(str -> System.out.println("peek 2: " + str)) 17 | .count(); 18 | 19 | System.out.println("count = " + result); 20 | } 21 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 7: Working with Streams' 2 | 3 | ext.examples = [ 4 | 'streams-iteration': [ 5 | 'Java8': 'for-loop Vs Stream.iterate (Java 8)', 6 | 'Java9': 'for-loop Vs Stream.iterate (Java 9+)', 7 | ], 8 | 'streams-infinite': ['InfiniteStreams': 'Infinit Streams'], 9 | 'streams-toarray': [ 10 | 'ObjectStreamToArray': 'Object-based Stream to Array', 11 | 'PrimitiveStreamToArray': 'Primitive-based Stream to Array', 12 | ], 13 | 'example-7-1': ['FilesListing': 'Listing directories in a folder'], 14 | 'example-7-2': ['FilesWalking': 'Walking the Filesystem'], 15 | 'example-7-3': ['FilesSearching': 'Finding Files'], 16 | 'example-7-4': ['FilesLines': 'Counting words in "War and Peace"'], 17 | 'streams-temporal-query': ['TemporalQuery': 'JSR310'], 18 | 'streams-downstream-collectors': [ 19 | 'Filter': 'Intermediate Vs collect filter op', 20 | 'Flatten': 'Flat-mapping', 21 | 'ReduceSum': 'Building a sum with reduce op', 22 | 'GroupBySimple': 'Simple elements grouping', 23 | 'GroupByMapping': 'Grouping with the help of mapping', 24 | ], 25 | 'example-7-5': ['Teeing': 'Counting logins'], 26 | ] 27 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-1/FilesListing.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-1. Listing directories in a folder 6 | // 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Predicate; 14 | 15 | public class FilesListing { 16 | 17 | public static void main(String... args) { 18 | 19 | // ADAPT AS NECESARY 20 | var dir = Paths.get("../04-immutability"); 21 | 22 | try (var stream = Files.list(dir)) { 23 | stream.map(Path::toFile) 24 | .filter(Predicate.not(File::isFile)) 25 | .map(File::getName) 26 | .forEach(System.out::println); 27 | } catch (IOException e) { 28 | System.err.println(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-1/files-listing.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-1. Listing directories in a folder 6 | // 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Predicate; 14 | 15 | // ADAPT AS NECESARY 16 | var dir = Paths.get("../../04-immutability"); 17 | 18 | try (var stream = Files.list(dir)) { 19 | stream.map(Path::toFile) 20 | .filter(Predicate.not(File::isFile)) 21 | .map(File::getName) 22 | .forEach(System.out::println); 23 | } catch (IOException e) { 24 | System.err.println(e); 25 | } 26 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-2/FilesWalking.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-2. Walking the Filesystem 6 | // 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Predicate; 14 | 15 | public class FilesWalking { 16 | 17 | public static void main(String[] args) { 18 | 19 | // ADAPT AS NECESARY 20 | var start = Paths.get("../04-immutability"); 21 | 22 | try (var stream = Files.walk(start)) { 23 | stream.map(Path::toFile) 24 | .filter(Predicate.not(File::isFile)) 25 | .sorted() 26 | .forEach(System.out::println); 27 | } catch (IOException e) { 28 | System.err.println(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-2/files-walking.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-2. Walking the Filesystem 6 | // 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.function.Predicate; 14 | 15 | // ADAPT AS NECESARY 16 | var start = Paths.get("../../04-immutability"); 17 | 18 | try (var stream = Files.walk(start)) { 19 | stream.map(Path::toFile) 20 | .filter(Predicate.not(File::isFile)) 21 | .sorted() 22 | .forEach(System.out::println); 23 | } catch (IOException e) { 24 | System.err.println(e); 25 | } 26 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-3/FilesSearching.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-3. Finding Files 6 | // 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.nio.file.attribute.BasicFileAttributes; 13 | import java.util.function.BiPredicate; 14 | 15 | public class FilesSearching { 16 | 17 | public static void main(String... args) { 18 | 19 | var start = Paths.get("../04-immutability"); 20 | 21 | BiPredicate matcher = 22 | (path, attr) -> attr.isDirectory(); 23 | 24 | try (var stream = Files.find(start, Integer.MAX_VALUE, matcher)) { 25 | stream.sorted() 26 | .forEach(System.out::println); 27 | } catch (IOException e) { 28 | System.err.println(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-3/files-searching.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-3. Finding Folders 6 | // 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.nio.file.attribute.BasicFileAttributes; 13 | import java.util.function.BiPredicate; 14 | 15 | var start = Paths.get("../04-immutability"); 16 | 17 | BiPredicate matcher = 18 | (path, attr) -> attr.isDirectory(); 19 | 20 | try (var stream = Files.find(start, Integer.MAX_VALUE, matcher)) { 21 | stream.sorted() 22 | .forEach(System.out::println); 23 | } catch (IOException e) { 24 | System.err.println(e); 25 | } -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-5/teeing.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | // Example 7-5. Counting logins 6 | // 7 | 8 | import java.time.LocalDate; 9 | import java.time.LocalDateTime; 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.UUID; 13 | import java.util.stream.Collectors; 14 | 15 | record User(UUID id, 16 | String group, 17 | LocalDateTime lastLogin, 18 | List logEntries) { }; 19 | 20 | List users = List.of( 21 | new User(UUID.randomUUID(), "admin", LocalDateTime.now().minusDays(23L), List.of("1", "2")), 22 | new User(UUID.randomUUID(), "user", LocalDate.now().atStartOfDay(), List.of("Z", "Y")), 23 | new User(UUID.randomUUID(), "user", null, Collections.emptyList()) 24 | ); 25 | 26 | record UserStats(long total, long neverLoggedIn) { }; 27 | 28 | UserStats result = 29 | users.stream() 30 | .collect(Collectors.teeing(Collectors.counting(), 31 | Collectors.filtering(user -> user.lastLogin() == null, 32 | Collectors.counting()), 33 | UserStats::new)); 34 | 35 | System.out.println("stats = " + result) 36 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/example-7-6/joinector.jsh: -------------------------------------------------------------------------------- 1 | /* 2 | * A FUNCTIONAL APPROACH TO JAVA 3 | * Chapter 7 - Working With Streams 4 | */ 5 | 6 | import java.util.StringJoiner; 7 | import java.util.stream.Collector; 8 | 9 | var delimiter = ","; 10 | 11 | Collector joinector = 12 | Collector.of(() -> new StringJoiner(delimiter), // supplier 13 | StringJoiner::add, // accumulator 14 | StringJoiner::merge, // combiner 15 | StringJoiner::toString); // finisher 16 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-downstream-collectors/GroupBySimple.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | import java.util.stream.Collectors; 12 | 13 | public class GroupBySimple { 14 | 15 | record User(UUID id, String group, LocalDateTime lastLogin, List logEntries) { 16 | } 17 | 18 | public static void main(String[] args) { 19 | 20 | List users = List.of( 21 | new User(UUID.randomUUID(), "admin", LocalDateTime.now().minusDays(23L), List.of("1", "2")), 22 | new User(UUID.randomUUID(), "user", null, Collections.emptyList()), 23 | new User(UUID.randomUUID(), "user", LocalDateTime.now().minusDays(42L), List.of("A", "B")) 24 | ); 25 | 26 | Map> lookup = users.stream().collect(Collectors.groupingBy(User::group)); 27 | 28 | lookup.forEach((group, groupUsers) -> System.out.println(String.format("%s: %s", group, groupUsers))); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-downstream-collectors/flatten.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | import java.util.stream.Collectors; 12 | 13 | record User(UUID id, 14 | String group, 15 | LocalDateTime lastLogin, 16 | List logEntries) { } 17 | 18 | List users = List.of( 19 | new User(UUID.randomUUID(), "admin", LocalDateTime.now().minusDays(23L), List.of("1", "2")), 20 | new User(UUID.randomUUID(), "user", null, List.of("Z", "Y")), 21 | new User(UUID.randomUUID(), "user", LocalDateTime.now().minusDays(42L), List.of("A", "B")) 22 | ); 23 | 24 | 25 | var downstream = Collectors.flatMapping((User user) -> user.logEntries().stream(), 26 | Collectors.toList()); 27 | 28 | Map> result = 29 | users.stream() 30 | .collect(Collectors.groupingBy(User::group, downstream)); 31 | 32 | System.out.println("flatMapped = " + result); 33 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-downstream-collectors/groupby-mapping.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import static java.util.stream.Collectors.*; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | import java.util.stream.Collectors; 14 | 15 | record User(UUID id, 16 | String group, 17 | LocalDateTime lastLogin, 18 | List logEntries) { } 19 | 20 | List users = List.of( 21 | new User(UUID.randomUUID(), "admin", LocalDateTime.now().minusDays(23L), List.of("1", "2")), 22 | new User(UUID.randomUUID(), "user", null, Collections.emptyList()), 23 | new User(UUID.randomUUID(), "user", LocalDateTime.now().minusDays(42L), List.of("A", "B")) 24 | ) 25 | 26 | {} 27 | var collectIdsToSet = mapping(User::id, toSet()); 28 | 29 | Map> lookup = 30 | users.stream().collect(groupingBy(User::group, collectIdsToSet)) 31 | 32 | lookup.forEach((group, groupUsers) -> System.out.println(String.format("%s: %s", group, groupUsers))) 33 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-downstream-collectors/groupby-simple.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import static java.util.stream.Collectors.*; 7 | 8 | import java.time.LocalDateTime; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | import java.util.stream.Collectors; 14 | 15 | record User(UUID id, 16 | String group, 17 | LocalDateTime lastLogin, 18 | List logEntries) { } 19 | 20 | List users = List.of( 21 | new User(UUID.randomUUID(), "admin", LocalDateTime.now().minusDays(23L), List.of("1", "2")), 22 | new User(UUID.randomUUID(), "user", null, Collections.emptyList()), 23 | new User(UUID.randomUUID(), "user", LocalDateTime.now().minusDays(42L), List.of("A", "B")) 24 | ) 25 | 26 | Map> lookup = users.stream().collect(groupingBy(User::group)); 27 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-infinite/InfiniteStreams.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.stream.Stream; 8 | 9 | public class InfiniteStreams { 10 | 11 | public static void main(String... args) { 12 | Stream.generate(new AtomicInteger()::incrementAndGet) 13 | .parallel() 14 | .limit(1_000L) 15 | .mapToInt(Integer::valueOf) 16 | .max() 17 | .ifPresent(System.out::println); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-infinite/infinite-streams.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | import java.util.stream.Stream; 8 | 9 | Stream.generate(new AtomicInteger()::incrementAndGet) 10 | .parallel() 11 | .limit(1_000L) 12 | .mapToInt(Integer::valueOf) 13 | .max() 14 | .ifPresent(System.out::println); 15 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-iteration/Java8.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.stream.IntStream; 7 | 8 | public class Java8 { 9 | 10 | public static void main(String... args) { 11 | 12 | System.out.println("for-loop"); 13 | 14 | for (int idx = 1; idx < 5; idx++) { 15 | System.out.println(idx); 16 | } 17 | 18 | System.out.println("\nIntStream.iterate"); 19 | 20 | IntStream.iterate(1, idx -> idx + 1) 21 | .limit(4L) 22 | .forEachOrdered(System.out::println); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-iteration/Java9.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.stream.IntStream; 7 | 8 | public class Java9 { 9 | 10 | public static void main(String... args) { 11 | 12 | System.out.println("for-loop"); 13 | 14 | for (int idx = 1; idx < 5; idx++) { 15 | System.out.println(idx); 16 | } 17 | 18 | System.out.println("\nIntStream.iterate"); 19 | 20 | IntStream.iterate(1, 21 | idx -> idx < 5, 22 | idx -> idx + 1) 23 | .forEachOrdered(System.out::println); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-iteration/java-8.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.stream.IntStream; 7 | 8 | System.out.println("for-loop") 9 | 10 | for (int idx = 1; idx < 5; idx++) { 11 | System.out.println(idx); 12 | } 13 | 14 | 15 | System.out.println("\nIntStream.iterate"); 16 | 17 | { 18 | IntStream.iterate(1, idx -> idx + 1) 19 | .limit(4L) 20 | .forEachOrdered(System.out::println); 21 | } 22 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-iteration/java-9.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.stream.IntStream; 7 | 8 | System.out.println("for-loop") 9 | 10 | for (int idx = 1; idx < 5; idx++) { 11 | System.out.println(idx); 12 | } 13 | 14 | System.out.println("\nIntStream.iterate"); 15 | { 16 | IntStream.iterate(1, 17 | idx -> idx < 5, 18 | idx -> idx + 1) 19 | .forEachOrdered(System.out::println); 20 | } 21 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-temporal-query/TemporalQuery.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.LocalTime; 8 | 9 | public class TemporalQuery { 10 | 11 | public static void main(String... args) { 12 | 13 | boolean isItTeaTime = 14 | LocalDateTime.now() 15 | .query(temporal -> { 16 | var time = LocalTime.from(temporal); 17 | return time.getHour() >= 16; 18 | }); 19 | System.out.println("Time for tea? " + isItTeaTime); 20 | 21 | // TemporalQuery == Function 22 | LocalTime time = LocalDateTime.now() 23 | .query(LocalTime::from); 24 | System.out.println("current time = " + time); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-temporal-query/temporal-query.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.time.LocalDateTime; 7 | import java.time.LocalTime; 8 | 9 | boolean isItTeaTime = LocalDateTime.now() 10 | .query(temporal -> { 11 | var time = LocalTime.from(temporal); 12 | return time.getHour() >= 16; 13 | }); 14 | 15 | // TemporalQuery == Function 16 | LocalTime time = LocalDateTime.now().query(LocalTime::from) 17 | System.out.println("current time = " + time) 18 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-toarray/ObjectStreamToArray.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.Arrays; 7 | 8 | public class ObjectStreamToArray { 9 | 10 | public static void main(String[] args) { 11 | 12 | String[] fruits = new String[] { 13 | "Banana", 14 | "Melon", 15 | "Orange" 16 | }; 17 | 18 | String[] result = Arrays.stream(fruits) 19 | .filter(fruit -> fruit.contains("a")) 20 | .toArray(String[]::new); 21 | 22 | for (var fruit : result) { 23 | System.out.println(fruit); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-toarray/PrimitiveStreamToarray.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.Arrays; 7 | 8 | public class PrimitiveStreamToarray { 9 | 10 | public static void main(String... args) { 11 | 12 | int[] fibonacci = new int[] { 13 | 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 14 | }; 15 | 16 | int[] evenNumbers = Arrays.stream(fibonacci) 17 | .filter(value -> value % 2 == 0) 18 | .toArray(); 19 | 20 | for (var number : evenNumbers) { 21 | System.out.println(number); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-toarray/object-stream-toarray.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.Arrays; 7 | 8 | String[] fruits = new String[] { 9 | "Banana", 10 | "Melon", 11 | "Orange" 12 | }; 13 | 14 | String[] result = Arrays.stream(fruits).filter(fruit -> fruit.contains("a")).toArray(String[]::new) 15 | 16 | for (var fruit : result) { 17 | System.out.println(fruit); 18 | } 19 | -------------------------------------------------------------------------------- /part-2/07-working-with-streams/streams-toarray/primitive-stream-toarray.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 7 - Working With Streams 4 | // 5 | 6 | import java.util.Arrays; 7 | 8 | int[] fibonacci = new int[] { 9 | 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 10 | }; 11 | 12 | int[] evenNumbers = Arrays.stream(fibonacci).filter(value -> value % 2 == 0).toArray(); 13 | 14 | for (var number : evenNumbers) { 15 | System.out.println(number); 16 | } 17 | -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8: Parallel Data Processing with Streams 2 | 3 | ## Parallel Streams in Action 4 | 5 | ### Example 8-1. Sequentially counting words in "War and Peace" 6 | 7 | Illustrating a naïve approach to counting words sequentially. 8 | 9 | [Example 8-1](example-8-1) 10 | 11 | ### Example 8-2. Parallel counting words in "War and Peace" 12 | 13 | Showing how to improve the previous example with a parallel Stream. 14 | 15 | [Example 8-2](example-8-2) 16 | 17 | 18 | ## When to Use and When to Avoid Parallel Streams 19 | 20 | ### Stream Operations 21 | 22 | #### Example 8-3. Mutable accumulation with a for-loop 23 | 24 | Simple for-loop summing up numbers. 25 | 26 | [Example 8-3](example-8-3) 27 | 28 | #### Example 8-4. Immutable accumulation of numbers with a Stream 29 | 30 | Previous example with a Stream and a `reduce` operation. 31 | 32 | [Example 8-4](example-8-4) 33 | 34 | #### Example 8-5. Random number statistics 35 | 36 | Generating `IntSummaryStatistics` from different data sources. 37 | 38 | [Example 8-5](example-8-5) 39 | -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 9: Parallel Data Processing with Streams' 2 | 3 | ext.examples = [ 4 | 'example-8-1': ['WarAndPeaceSequential': 'Sequentially counting words in "War and Peace"'], 5 | 'example-8-2': ['WarAndPeaceParallel': 'Parallel counting words in "War and Peace"'], 6 | 'example-8-3': ['MutableAccumulation': 'Mutable accumulation with a for-loop'], 7 | 'example-8-4': ['ImmutableAccumulation': 'Immutable accumulation of numbers with a Stream'], 8 | 'example-8-5': ['RandomNumberStats' : 'Random number statistics'], 9 | ] 10 | -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/example-8-3/MutableAccumulation.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 8 - Parallel Data Processing with Streams 4 | // 5 | // Example 8-3. Mutable accumulation with a for-loop 6 | // 7 | 8 | import java.util.List; 9 | 10 | public class MutableAccumulation { 11 | 12 | public static void main(String... args) { 13 | 14 | var numbers = List.of(1, 2, 3, 4, 5, 6); 15 | 16 | int total = 0; 17 | 18 | for (int value : numbers) { 19 | total += value; 20 | } 21 | 22 | System.out.println("total = " + total); 23 | } 24 | } -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/example-8-3/mutable-accumulation.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 8 - Parallel Data Processing with Streams 4 | // 5 | // Example 8-3. Mutable accumulation with a for-loop 6 | // 7 | 8 | import java.util.List; 9 | 10 | var numbers = List.of(1, 2, 3, 4, 5, 6); 11 | 12 | int total = 0; 13 | 14 | for (int value : numbers) { 15 | total += value; 16 | } 17 | 18 | System.out.println("total = " + total) -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/example-8-4/ImmutableAccumulation.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 8 - Parallel Data Processing with Streams 4 | // 5 | // Example 8-4. Immutable accumulation of numbers with a Stream 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | public class ImmutableAccumulation { 11 | 12 | public static void main(String... args) { 13 | int total = Stream.of(1, 2, 3, 4, 5, 6) 14 | .parallel() 15 | .reduce(0, 16 | Integer::sum); 17 | 18 | System.out.println("total = " + total); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-2/08-parallel-data-processing-with-streams/example-8-4/immutable-accumulation.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 8 - Parallel Data Processing with Streams 4 | // 5 | // Example 8-4. Immutable accumulation of numbers with a Stream 6 | // 7 | 8 | import java.util.stream.Stream; 9 | 10 | int total = Stream.of(1, 2, 3, 4, 5, 6) 11 | .parallel() 12 | .reduce(0, 13 | Integer::sum); 14 | 15 | System.out.println("total = " + total) 16 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 9: Handling Null with Optionals' 2 | 3 | ext.examples = [ 4 | 'example-9-1': ['Minefield': 'A minefield of possible nulls'], 5 | 'example-9-2': ['NullTypeAmbiguity': 'null type ambiguity'], 6 | 'example-9-4': ['LoadingContentWithoutOptionals': 'Loading content without Optionals'], 7 | 'example-9-5': ['CallChain': 'Loading content with an Optional call chain'], 8 | 'example-9-6': ['CheckOptional': 'Checking for Optional values'], 9 | 'example-9-7': ['FilterMapOptional': 'Intermediate operations to find an active admin'], 10 | 'example-9-8': ['FilterMapWithoutOptionals': 'Finding an active admin without Optionals'], 11 | 'example-9-9': ['StreamFilterMap': 'Optionals as Stream elements'], 12 | 'example-9-10': ['StreamFlatMap': 'Optionals as Stream elements with flatMap'], 13 | ] 14 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/dos-and-donts/dos-and-donts.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | 6 | // DON'T INITIALIZE A VARIABLE TO NULL 7 | 8 | // DON'T 9 | 10 | String value = null; 11 | 12 | if (condition) { 13 | value = "Condition is true"; 14 | } else { 15 | value = "Fallback if false"; 16 | } 17 | 18 | // DO 19 | 20 | String asTernary = condition ? "Condition is true" 21 | : "Fallback if false"; 22 | 23 | String asRefactored = refactoredMethod(condition); 24 | 25 | 26 | // ================================================================ 27 | 28 | // DON'T PASS, ACCEPT, OR RETURN NULL 29 | 30 | public record User(long id, String firstname, String lastname) { 31 | 32 | // DO: Additional constructor with default values to avoid null values 33 | public User(long id) { 34 | this(id, "n/a", "n/a"); 35 | } 36 | } 37 | 38 | public record User(long id, String firstname, String lastname) { 39 | 40 | // DO: Validate arguments against null 41 | public User { 42 | Objects.requireNonNull(firstname); 43 | Objects.requireNonNull(lastname); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-1/Minefield.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-1. A minefield of possible nulls 6 | // 7 | 8 | public class Minefield { 9 | 10 | record User(long id, 11 | String firstname, 12 | String lastname) { 13 | 14 | String fullname() { 15 | return String.format("%s %s", 16 | firstname(), 17 | lastname()); 18 | } 19 | 20 | String initials() { 21 | return String.format("%s%s", 22 | firstname().substring(0, 1), 23 | lastname().substring(0, 1)); 24 | } 25 | } 26 | 27 | public static void main(String... args) { 28 | 29 | var user = new User(42L, "Ben", null); 30 | 31 | var fullname = user.fullname(); 32 | System.out.println("fullname = " + fullname); 33 | 34 | // => NullPointerException 35 | var initials = user.initials(); 36 | System.out.println("initials = " + initials); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-1/minefield.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-1. A minefield of possible nulls 6 | // 7 | 8 | record User(long id, 9 | String firstname, 10 | String lastname) { 11 | 12 | String fullname() { 13 | return String.format("%s %s", firstname(), lastname()); 14 | } 15 | 16 | String initials() { 17 | return String.format("%s%s", 18 | firstname().substring(0, 1), 19 | lastname().substring(0, 1)); 20 | } 21 | } 22 | 23 | var user = new User(42L, "Ben", null); 24 | 25 | var fullname = user.fullname(); 26 | System.out.println("fullname = " + fullname); 27 | 28 | var initials = user.initials(); 29 | // => NullPointerException 30 | System.out.println("initials = " + initials); 31 | 32 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-10/stream-flatmap.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-10. Optionals as Stream elements with flatMap 6 | // 7 | 8 | import java.util.List 9 | import java.util.Optional 10 | import java.util.function.Predicate 11 | 12 | record Permissions(List permissions, Group group) { 13 | 14 | boolean isEmpty() { 15 | return permissions.isEmpty(); 16 | } 17 | } 18 | 19 | record Group(Optional admin) { } 20 | 21 | record User(boolean isActive) { } 22 | 23 | User admin = new User(true) 24 | 25 | Group group = new Group(Optional.of(admin)) 26 | 27 | List permissions = List.of(new Permissions(List.of("A", "B", "C"), group)) 28 | 29 | { 30 | List activeUsers = 31 | permissions.stream() 32 | .filter(Predicate.not(Permissions::isEmpty)) 33 | .map(Permissions::group) 34 | .map(Group::admin) 35 | .flatMap(Optional::stream) 36 | .filter(User::isActive) 37 | .toList(); 38 | 39 | System.out.println("activeUsers = " + activeUsers); 40 | } -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-11/pseudo-reduce.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-11. Pseudo-code equivalent to the reduce operation 6 | // 7 | 8 | import java.util.functional.BinaryOperator; 9 | import java.util.Optional; 10 | 11 | Optional pseudoReduce(BinaryOperator accumulator) { 12 | boolean foundAny = false; 13 | T result = null; 14 | 15 | for (T element : elements) { 16 | if (!foundAny) { 17 | foundAny = true; 18 | result = element; 19 | } else { 20 | result = accumulator.apply(result, element); 21 | } 22 | } 23 | 24 | return foundAny ? Optional.of(result) 25 | : Optional.empty(); 26 | } 27 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-12/User.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-12. Using Optionals in Serializable types 6 | // 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | import java.util.Optional; 11 | 12 | public class User implements Serializable { 13 | 14 | private UUID id; 15 | private String username; 16 | private LocalDateTime lastLogin; 17 | 18 | // ... usual getter/setter for id and username 19 | 20 | public Optional getLastLogin() { 21 | return Optional.ofNullable(this.lastLogin); 22 | } 23 | 24 | public void setLastLogin(LocalDateTime lastLogin) { 25 | this.lastLogin = lastLogin; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-2/NullTypeAmbiguity.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-2. null type ambiguity 6 | // 7 | 8 | public class NullTypeAmbiguity { 9 | 10 | static void methodAcceptingString(String str) { 11 | // ... 12 | } 13 | 14 | public static void main(String... args) { 15 | 16 | // "TYPE-LESS" NULL AS AN ARGUMENT 17 | 18 | methodAcceptingString(null); 19 | 20 | // ACCESSING A "TYPED" NULL 21 | 22 | String name = null; 23 | 24 | var lowerCaseName = name.toLowerCase(); 25 | // => NullPointerException 26 | 27 | // TEST TYPE OF NULL 28 | 29 | var notString = name instanceof String; 30 | System.out.println("notString ? " + notString); 31 | 32 | var stillNotString = (name) instanceof String; 33 | System.out.println("stillNotString = " + stillNotString); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-2/null-type-ambiguity.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-2. null type ambiguity 6 | // 7 | 8 | // "TYPE-LESS" NULL AS AN ARGUMENT 9 | 10 | void methodAcceptingString(String str) { 11 | // ... 12 | } 13 | 14 | methodAcceptingString(null) 15 | 16 | 17 | // ACCESSING A "TYPED" NULL 18 | 19 | String name = null 20 | 21 | var lowerCaseName = name.toLowerCase() 22 | // => NullPointerException 23 | 24 | 25 | // TEST TYPE OF NULL 26 | 27 | var notString = name instanceof String 28 | System.out.println("notString = " + notString) 29 | 30 | var stillNotString = ((String) name) instanceof String 31 | System.out.println("stillNotString = " + stillNotString) -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-3/NullHandlingAnnotations.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-3. null handling with annotations 6 | // 7 | // This example requires a dependency containing the nullability 8 | // annotations, therefore, it's just for illustration purposes. 9 | 10 | interface Example { 11 | 12 | @NonNull List<@Nullable String> getListOfNullableStrings(); 13 | 14 | @Nullable List<@NonNull String> getNullableListOfNonNullStrings(); 15 | 16 | void doWork(@Nullable String identifier); 17 | } -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-4/loading-content-without-optionals.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-4. Loading content without Optionals 6 | // 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | record Content(String identifier, 12 | boolean isPublished) { } 13 | 14 | Content loadFromDB(String identifier) { 15 | return new Content(identifier, true); 16 | } 17 | 18 | Map cache = new HashMap<>() 19 | 20 | Content get(String contentId) { 21 | 22 | if (contentId == null) { 23 | return null; 24 | } 25 | 26 | if (contentId.isBlank()) { 27 | return null; 28 | } 29 | 30 | var cacheKey = contentId.toLowerCase(); 31 | 32 | var content = cache.get(cacheKey); 33 | if (content == null) { 34 | content = loadFromDB(contentId); 35 | } 36 | 37 | if (content == null) { 38 | return null; 39 | } 40 | 41 | if (!content.isPublished()) { 42 | return null; 43 | } 44 | 45 | return content; 46 | } 47 | 48 | var content = get("ABC") 49 | System.out.println("content = " + content) -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-5/CallChain.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-5. Loading content with an Optional call chain 6 | // 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.function.Predicate; 12 | 13 | public class CallChain { 14 | 15 | record Content(String identifier, 16 | boolean isPublished) { 17 | } 18 | 19 | static final Map cache = new HashMap<>(); 20 | 21 | static Optional loadFromDB(String identifier) { 22 | return Optional.of(new Content(identifier, true)); 23 | } 24 | 25 | static Optional get(String contentId) { 26 | return Optional.ofNullable(contentId) 27 | .filter(Predicate.not(String::isBlank)) 28 | .map(String::toLowerCase) 29 | .map(cache::get) 30 | .or(() -> loadFromDB(contentId)) 31 | .filter(Content::isPublished); 32 | } 33 | 34 | public static void main(String[] args) { 35 | 36 | var content = get("ABC"); 37 | System.out.println("content = " + content); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-5/call-chain.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-5. Loading content with an Optional call chain 6 | // 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Optional; 11 | import java.util.function.Predicate; 12 | 13 | record Content(String identifier, 14 | boolean isPublished) { } 15 | 16 | Optional loadFromDB(String identifier) { 17 | return Optional.of(new Content(identifier, true)); 18 | } 19 | 20 | Map cache = new HashMap<>(); 21 | 22 | Optional get(String contentId) { 23 | return Optional.ofNullable(contentId) 24 | .filter(Predicate.not(String::isBlank)) 25 | .map(String::toLowerCase) 26 | .map(cache::get) 27 | .or(() -> loadFromDB(contentId)) 28 | .filter(Content::isPublished); 29 | } 30 | 31 | var content = get("ABC") 32 | System.out.println("content = " + content) 33 | 34 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-6/CheckOptional.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-6. Checking for Optional values 6 | // 7 | 8 | import java.util.Optional; 9 | 10 | public class CheckOptional { 11 | 12 | public static void main(String[] args) { 13 | 14 | Optional maybeValue = Optional.ofNullable(null); 15 | 16 | // VERBOSE VERSION 17 | if (maybeValue.isPresent()) { 18 | var value = maybeValue.orElseThrow(); 19 | System.out.println(value); 20 | } else { 21 | System.out.println("No value found!"); 22 | } 23 | 24 | // CONCISE VERSION 25 | maybeValue.ifPresentOrElse(System.out::println, 26 | () -> System.out.println("No value found!")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-6/check-optional.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-6. Checking for Optional values 6 | // 7 | 8 | import java.util.Optional; 9 | 10 | Optional maybeValue = Optional.ofNullable(null) 11 | 12 | // VERBOSE VERSION 13 | if (maybeValue.isPresent()) { 14 | var value = maybeValue.orElseThrow(); 15 | System.out.println(value); 16 | } else { 17 | System.out.println("No value found!"); 18 | } 19 | 20 | // CONCISE VERSION 21 | maybeValue.ifPresentOrElse(System.out::println, 22 | () -> System.out.println("No value found!")); 23 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-7/filter-map-optional.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-7. Intermediate operations to find an active admin 6 | // 7 | 8 | import java.util.List 9 | import java.util.Optional 10 | import java.util.function.Predicate 11 | 12 | record Permissions(List permissions, Group group) { 13 | 14 | public boolean isEmpty() { 15 | return permissions.isEmpty(); 16 | } 17 | } 18 | 19 | record Group(Optional admin) { } 20 | 21 | record User(boolean isActive) { } 22 | 23 | User admin = new User(true) 24 | 25 | Group group = new Group(Optional.of(admin)) 26 | 27 | Permissions permissions = new Permissions(List.of("A", "B", "C"), group); 28 | 29 | { 30 | boolean isActiveAdmin = 31 | Optional.ofNullable(permissions) 32 | .filter(Predicate.not(Permissions::isEmpty)) 33 | .map(Permissions::group) 34 | .flatMap(Group::admin) 35 | .map(User::isActive) 36 | .orElse(Boolean.FALSE); 37 | System.out.println("isActiveAdmin = " + isActiveAdmin); 38 | } 39 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-8/filter-map-without-optionals.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-8. Finding an active admin without Optionals 6 | // 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.function.Predicate; 11 | 12 | record Permissions(List permissions, Group group) { 13 | 14 | public boolean isEmpty() { 15 | return permissions.isEmpty(); 16 | } 17 | } 18 | 19 | record Group(Optional admin) { } 20 | 21 | record User(boolean isActive) { } 22 | 23 | User admin = new User(true) 24 | 25 | Group group = new Group(Optional.of(admin)) 26 | 27 | Permissions permissions = new Permissions(List.of("A", "B", "C"), group) 28 | 29 | boolean isActiveAdmin = false; 30 | 31 | if (permissions != null && !permissions.isEmpty()) { 32 | 33 | if (permissions.group() != null) { 34 | var group = permissions.group(); 35 | var maybeAdmin = group.admin(); 36 | 37 | if (maybeAdmin.isPresent()) { 38 | var admin = maybeAdmin.orElseThrow(); 39 | isActiveAdmin = admin.isActive(); 40 | } 41 | } 42 | } 43 | 44 | System.out.println("isActiveAdmin = " + isActiveAdmin); 45 | -------------------------------------------------------------------------------- /part-2/09-handling-null-with-optionals/example-9-9/stream-filter-map.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 9 - Handling null with Optionals 4 | // 5 | // Example 9-9. Optionals as Stream elements 6 | // 7 | 8 | import java.util.List 9 | import java.util.Optional 10 | import java.util.function.Predicate 11 | 12 | record Permissions(List permissions, 13 | Group group) { 14 | 15 | boolean isEmpty() { 16 | return permissions.isEmpty(); 17 | } 18 | } 19 | 20 | record Group(Optional admin) { } 21 | 22 | record User(boolean isActive) { } 23 | 24 | User admin = new User(true) 25 | 26 | Group group = new Group(Optional.of(admin)) 27 | 28 | List permissions = List.of(new Permissions(List.of("A", "B", "C"), group)) 29 | 30 | { 31 | List activeUsers = 32 | permissions.stream() 33 | .filter(Predicate.not(Permissions::isEmpty)) 34 | .map(Permissions::group) 35 | .map(Group::admin) 36 | .filter(Optional::isPresent) 37 | .map(Optional::orElseThrow) 38 | .filter(User::isActive) 39 | .toList(); 40 | 41 | System.out.println("acitveUsers = " + activeUsers); 42 | } 43 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 10: Functional Exception Handling' 2 | 3 | ext.examples = [ 4 | 'example-10-1': ['SafeMethodExtraction': 'Extract throwing code into a safe method'], 5 | 'example-10-2': ['UncheckingExceptions': 'Unchecking java.util.Function'], 6 | 'sneaky-throws': ['SneakyThrows': 'Throwing an Exception \'sneakily\''], 7 | 'example-10-6': ['ResultAsReturnType': 'Using Result as a return type'], 8 | 'example-10-7': ['ResultTransformers': 'Adding Transformers to Result'], 9 | 'result-reactions-fallback': ['ResultReactionsFallback': 'React to result'], 10 | 'example-10-9': ['TryMinimal': 'Minimal Try accepting a lambda and Exception handler'], 11 | 'example-10-10': ['TryHandling': 'Handling success and failure in Try'], 12 | 'example-10-11': ['TryApply': 'Applying a value to Try'], 13 | 'example-10-12': ['TryFunction': 'Implementing Function>'], 14 | ] 15 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/errors-as-values/errors-as-values.go: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | package main 7 | 8 | import "fmt" 9 | 10 | func safeReadString(path string) (string, error) { 11 | return path, nil 12 | } 13 | 14 | func main() { 15 | content, err := safeReadString("location/content.md") 16 | if err != nil { 17 | // error handling code 18 | } 19 | 20 | fmt.Println(content) 21 | } 22 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/example-10-1/SafeMethodExtraction.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | // Example 10-1. Extract throwing code into a safe method 6 | // 7 | 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.Paths; 12 | import java.util.stream.Stream; 13 | import java.util.Objects; 14 | 15 | public class SafeMethodExtraction { 16 | 17 | static String safeReadString(Path path) { 18 | try { 19 | return Files.readString(path); 20 | } catch (IOException e) { 21 | return null; 22 | } 23 | } 24 | 25 | public static void main(String... args) { 26 | var result = Stream.of(Paths.get("example-10-1/SafeMethodExtraction.java"), 27 | Paths.get("example-10-1/safe-method-extraction.jsh")) 28 | .map(SafeMethodExtraction::safeReadString) 29 | .filter(Objects::nonNull) 30 | .count(); 31 | 32 | System.out.println("files count = " + result); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/example-10-1/safe-method-extraction.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | // Example 10-1. Extract throwing code into a safe method 6 | // 7 | 8 | import java.io.IOException 9 | import java.nio.file.Files 10 | import java.nio.file.Path 11 | import java.nio.file.Paths 12 | import java.util.stream.Stream 13 | import java.util.Objects 14 | 15 | // Wrapped in class so we can use a method reference more easily 16 | class SafeMethodExtraction { 17 | static String safeReadString(Path path) { 18 | try { 19 | return Files.readString(path); 20 | } catch (IOException e) { 21 | return null; 22 | } 23 | } 24 | } 25 | 26 | { 27 | // ADAPT AS NECESSARY 28 | var result = Stream.of(Paths.get("SafeMethodExtraction.java"), 29 | Paths.get("safe-method-extraction.jsh")) 30 | .map(SafeMethodExtraction::safeReadString) 31 | .filter(Objects::nonNull) 32 | .count(); 33 | 34 | System.out.println("files count = " + result); 35 | } 36 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/example-10-4/not-throwing-exceptions.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | // Example 10-4. Using Optional instead of throwing an IOException 6 | // 7 | 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.Optional; 11 | 12 | Optional safeReadString(Path path) { 13 | try { 14 | var content = Files.readString(path); 15 | return Optional.of(content); 16 | } catch (IOException e) { 17 | return Optional.empty(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/example-10-5/Result.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | // Example 10-5. Traditional Result Object 6 | // 7 | 8 | public record Result (V value, 9 | E throwable, 10 | boolean success) { 11 | 12 | public static Result success(V value) { 13 | return new Result<>(value, null, true); 14 | } 15 | 16 | public static Result failure(E throwable) { 17 | return new Result<>(null, throwable, false); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/example-10-8/TrySuccessFailure.scala: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | // Example 10-8. Scala’s Try/Success/Failure pattern 6 | // 7 | 8 | import java.nio.file.Path 9 | import scala.util.{Failure, Success, Try} 10 | 11 | object TrySuccssFailure extends App { 12 | 13 | def readString(path: Path): Try[String] = Try { 14 | // code that will throw an Exception 15 | return Try("") 16 | } 17 | 18 | val path = Path.of(""); 19 | 20 | readString(path) match { 21 | case Success(value) => println(value.toUpperCase()) 22 | case Failure(e) => println("Couldn't read file: " + e.getMessage) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/files-read-string/FilesReadString.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.stream.Stream; 9 | 10 | public class FilesReadString { 11 | 12 | public static void main(String... args) { 13 | 14 | // THIS CODE WON'T COMPILE 15 | // Error: 16 | // Unhandled exception type IOException 17 | 18 | Stream.of(Paths.get("FilesReadstring.java"), 19 | Paths.get("FilesReadStringTryCatch.java")) 20 | .map(Files::readString) 21 | .toList(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/files-read-string/FilesReadStringTryCatch.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Paths; 9 | import java.util.stream.Stream; 10 | 11 | public class FilesReadStringTryCatch { 12 | 13 | public static void main(String... args) { 14 | 15 | // THIS CODE WON'T COMPILE 16 | // Error: 17 | // Unhandled exception type IOException 18 | 19 | Stream.of(Paths.get("FilesReadstring.java"), 20 | Paths.get("FilesReadStringTryCatch.java")) 21 | .map(path -> { 22 | try { 23 | return Files.readString(path); 24 | } catch (IOException e) { 25 | return null; 26 | } 27 | }) 28 | .toList(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/files-read-string/files-read-string-try-catch.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.stream.Stream; 9 | 10 | Stream.of(Paths.get("files-read-string.java"), 11 | Paths.get("files-read-string-try-catch.java")) 12 | .map(path -> { 13 | try { 14 | return Files.readString(path); 15 | } catch (IOException e) { 16 | return null; 17 | } 18 | }) 19 | .toList() 20 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/files-read-string/files-read-string.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | import java.nio.file.Files; 7 | import java.nio.file.Paths; 8 | import java.util.stream.Stream; 9 | 10 | // THIS CODE WON'T COMPILE 11 | // Error: 12 | // incompatible thrown types java.io.IOException in functional expression 13 | 14 | Stream.of(Paths.get("files-read-string.java"), 15 | Paths.get("files-read-string-try-catch.java")) 16 | .map(Files::readString) 17 | .toList() 18 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/sneaky-throws/SneakyThrows.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | /// 5 | 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.stream.Stream; 11 | 12 | public class SneakyThrows { 13 | 14 | static void sneakyThrow(Throwable e) throws E { 15 | throw (E) e; 16 | } 17 | 18 | static String sneakyRead(Path path) { 19 | try { 20 | return Files.readString(path); 21 | } 22 | catch (IOException e) { 23 | sneakyThrow(e); 24 | } 25 | 26 | // This code is never reached 27 | return null; 28 | } 29 | 30 | public static void main(String... args) { 31 | var result = Stream.of(Paths.get("invalid file")) 32 | .map(SneakyThrows::sneakyRead) 33 | .toList(); 34 | System.out.println("files count = " + result); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /part-2/10-functional-exception-handling/try-catch/try-catch.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 10 - Functional Exception Handling 4 | // 5 | 6 | import java.io.BufferedReader; 7 | import java.io.FileReader; 8 | import java.io.IOException; 9 | import java.nio.file.Paths; 10 | import java.math.BigDecimal; 11 | 12 | // --------------------------------------------------------------------- 13 | // java.lang.ArithmeticException 14 | 15 | BigDecimal doCalculation(BigDecimal input) { 16 | return BigDecimal.ONE.divide(input); 17 | } 18 | 19 | try { 20 | doCalculation(BigDecimal.ZERO); 21 | } catch (ArithmeticException | IllegalArgumentException e) { 22 | System.err.println("Calculation failed: " + e); 23 | } 24 | 25 | // --------------------------------------------------------------------- 26 | 27 | // java.io.FileNotFoundException 28 | 29 | var path = Paths.get("invalid path"); 30 | 31 | try (var fileReader = new FileReader(path.toFile()); 32 | var bufferedReader = new BufferedReader(fileReader)) { 33 | 34 | var firstLine = bufferedReader.readLine(); 35 | System.err.println(firstLine); 36 | 37 | } catch (IOException e) { 38 | System.err.println("Couldn't read first line of " + path + ": " + e); 39 | } 40 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 11: Lazy Evaluation' 2 | 3 | ext.examples = [ 4 | 'example-11-1': ['LazyVersusStrict': 'Lazy Evaluation with Java and Suppliers'], 5 | 'example-11-2': ['ShortCircuit': 'Usage of logical short-circuit operators'], 6 | 'example-11-3': ['UpdateUserEager': ' Updating a User with eager method arguments'], 7 | 'example-11-4': ['UpdateUserLambda': 'Updating a User with a lambda'], 8 | 'example-11-5': ['ThunkSimple': 'A simple Thunk'], 9 | 'lazy-maps': [ 10 | 'Lazy': 'Lazy User', 11 | 'NonLazy': 'Non-Lazy User' 12 | ], 13 | ] 14 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-1/LazyVersusStrict.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-1. Lazy Evaluation with Java and Suppliers 6 | // 7 | 8 | import java.util.function.IntSupplier; 9 | 10 | public class LazyVersusStrict { 11 | 12 | static int strict(int x, int y) { 13 | return x + x; 14 | } 15 | 16 | static int lazy(IntSupplier x, IntSupplier y) { 17 | return x.getAsInt() + x.getAsInt(); 18 | } 19 | public static void main(String... args) { 20 | 21 | 22 | // THE ARGUMENTS ARE LAMBDAS AND ONLYS EVALUATED IF EVALUATED THEMSELVES 23 | 24 | var lazyResult = lazy(() -> 5, () -> 1 / 0); 25 | System.out.println("lazy: " + lazyResult); 26 | 27 | // ALL ARGUMENTS ARE EVALUATED DURING THE CALL, WHICH WILL THROW 28 | // AN ArithmeticException 29 | 30 | var strictResult = strict(5, 1 / 0); 31 | System.out.println("strict: " + strictResult); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-1/lazy-versus-strict.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-1. Lazy Evaluation with Java and Suppliers 6 | // 7 | 8 | import java.util.function.IntSupplier 9 | 10 | int strict(int x, int y) { 11 | return x + x; 12 | } 13 | 14 | int lazy(IntSupplier x, IntSupplier y) { 15 | return x.getAsInt() + x.getAsInt(); 16 | } 17 | 18 | // THE ARGUMENTS ARE LAMBDAS AND ONLYS EVALUATED IF EVALUATED THEMSELVES 19 | 20 | var lazyResult = lazy(() -> 5, () -> 1 / 0) 21 | System.out.println("lazy = " + lazyResult) 22 | 23 | // ALL ARGUMENTS ARE EVALUATED DURING THE CALL, 24 | // WHICH WILL THROW AN ArithmeticException 25 | 26 | var strictResult = strict(5, 1 / 0) 27 | System.out.println("strict = " + strictResult) 28 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-2/ShortCircuit.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-2. Usage of logical short-circuit operators 6 | // 7 | 8 | public class ShortCircuit { 9 | 10 | static boolean left() { 11 | return true; 12 | } 13 | 14 | static boolean right() { 15 | return true; 16 | } 17 | 18 | public static void main(String[] args) { 19 | 20 | // WON'T COMPILE: unused result 21 | // left() || right(); 22 | 23 | // COMPILES: used as if condition 24 | 25 | if (left() || right()) { 26 | // ... 27 | } 28 | 29 | // COMPILES: used as variable assignment 30 | 31 | var result = left() || right(); 32 | System.out.println("result: " + result); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-2/short-circuit.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-2. Usage of logical short-circuit operators 6 | // 7 | 8 | boolean left() { 9 | return true; 10 | } 11 | 12 | boolean right() { 13 | return true; 14 | } 15 | 16 | // WON'T COMPILE: unused result 17 | // left() || right(); 18 | 19 | 20 | // COMPILES: used as if condition 21 | 22 | if (left() || right()) { 23 | // ... 24 | } 25 | 26 | // COMPILES: used as variable assignment 27 | 28 | var result = left() || right() 29 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-3/UpdateUserEager.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-3. Updating a User with eager method arguments 6 | // 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class UpdateUserEager { 12 | 13 | record Role(String name) { } 14 | 15 | record User(List roles) { } 16 | 17 | static User updateUser(User user, List availableRoles) { 18 | return new User(availableRoles); 19 | } 20 | 21 | static class DAO { 22 | 23 | public List loadAllAvailableRoles() { 24 | return List.of(new Role("admin"), 25 | new Role("guest")); 26 | } 27 | } 28 | 29 | public static void main(String[] args) { 30 | 31 | var dao = new DAO(); 32 | 33 | // HOW TO USE 34 | 35 | var user = new User(Collections.emptyList()); 36 | System.out.println("user = " + user); 37 | 38 | var availableRoles = dao.loadAllAvailableRoles(); 39 | 40 | var updatedUser = updateUser(user, availableRoles); 41 | System.out.println("updated user = " + updatedUser); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-3/update-user-eager.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-3. Updating a User with eager method arguments 6 | // 7 | 8 | import java.util.Collections 9 | import java.util.List 10 | 11 | record Role(String name) { } 12 | 13 | record User(List roles) { } 14 | 15 | User updateUser(User user, 16 | List availableRoles) { 17 | return new User(availableRoles); 18 | } 19 | 20 | class DAO { 21 | public List loadAllAvailableRoles() { 22 | return List.of(new Role("admin"), 23 | new Role("guest")); 24 | } 25 | } 26 | 27 | var dao = new DAO() 28 | 29 | // HOW TO USE 30 | 31 | var user = new User(Collections.emptyList()) 32 | System.out.println("user = " + user) 33 | 34 | var availableRoles = dao.loadAllAvailableRoles(); 35 | 36 | var updatedUser = updateUser(user, availableRoles) 37 | System.out.println("updated user = " + updatedUser) 38 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-4/UpdateUserLambda.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-4. Updating a User with a lambda 6 | // 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.function.Supplier; 11 | 12 | public class UpdateUserLambda { 13 | 14 | record Role(String name) { } 15 | 16 | record User(List roles) { } 17 | 18 | static User updateUser(User user, 19 | Supplier> availableRolesFn) { 20 | return new User(availableRolesFn.get()); 21 | } 22 | 23 | static class DAO { 24 | 25 | public List loadAllAvailableRoles() { 26 | return List.of(new Role("admin"), 27 | new Role("guest")); 28 | } 29 | } 30 | 31 | public static void main(String[] args) { 32 | 33 | var dao = new DAO(); 34 | 35 | // HOW TO USE 36 | 37 | var user = new User(Collections.emptyList()); 38 | System.out.println("user = " + user); 39 | 40 | var updatedUser = updateUser(user, dao::loadAllAvailableRoles); 41 | System.out.println("updated user = " + updatedUser); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/example-11-4/update-user-lambda.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | // Example 11-4. Updating a User with a lambda 6 | // 7 | 8 | import java.util.Collections 9 | import java.util.List 10 | import java.util.function.Supplier 11 | 12 | 13 | record Role(String name) { } 14 | 15 | record User(List roles) { } 16 | 17 | class DAO { 18 | public List loadAllAvailableRoles() { 19 | return List.of(new Role("admin"), 20 | new Role("guest")); 21 | } 22 | } 23 | 24 | User updateUser(User user, 25 | Supplier> availableRolesFn) { 26 | return new User(availableRolesFn.get()); 27 | } 28 | 29 | 30 | var dao = new DAO() 31 | 32 | // HOW TO USE 33 | 34 | var user = new User(Collections.emptyList()) 35 | System.out.println("user = " + user) 36 | 37 | var updatedUser = updateUser(user, dao::loadAllAvailableRoles) 38 | System.out.println("updated user = " + updatedUser) 39 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/lazy-maps/Lazy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class Lazy { 10 | 11 | record User(String email, String name) { 12 | } 13 | 14 | static User loadUser(String email) { 15 | return new User("jane@doe.com", "Jane Doe"); 16 | } 17 | 18 | public static void main(String... args) { 19 | 20 | Map users = new HashMap<>(); 21 | users.put("john@doe.com", new User("john@doe.com", "John Doe")); 22 | 23 | String email = "jane@doe.com"; 24 | User user = users.computeIfAbsent(email, Lazy::loadUser); 25 | 26 | System.out.println("user = " + user); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/lazy-maps/NonLazy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class NonLazy { 10 | 11 | record User(String email, String name) { 12 | } 13 | 14 | static User loadUser(String email) { 15 | return new User("jane@doe.com", "Jane Doe"); 16 | } 17 | 18 | public static void main(String... args) { 19 | 20 | Map users = new HashMap<>(); 21 | users.put("john@doe.com", new User("john@doe.com", "John Doe")); 22 | 23 | var email = "jane@doe.com"; 24 | 25 | var user = users.get(email); 26 | if (user == null) { 27 | user = loadUser(email); 28 | users.put(email, user); 29 | } 30 | 31 | System.out.println("user = " + user); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/lazy-maps/lazy.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | 6 | import java.util.HashMap 7 | import java.util.Map 8 | 9 | // SETUP 10 | 11 | record User(String email, String name) { } 12 | 13 | Map users = new HashMap<>() 14 | users.put("john@doe.com", new User("john@doe.com", "John Doe")) 15 | 16 | 17 | User loadUser(String email) { 18 | return new User("jane@doe.com", "Jane Doe"); 19 | } 20 | 21 | 22 | // RELEVANT CODE 23 | 24 | String email = "jane@doe.com" 25 | User user = users.computeIfAbsent(email, key -> loadUser(key)) 26 | 27 | System.out.println("lazy user = " + user) 28 | -------------------------------------------------------------------------------- /part-2/11-lazy-evaluation/lazy-maps/non-lazy.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 11 - Lazy Evaluation 4 | // 5 | 6 | import java.util.HashMap 7 | import java.util.Map 8 | 9 | // SETUP 10 | 11 | record User(String email, String name) { } 12 | 13 | Map users = new HashMap<>(); 14 | users.put("john@doe.com", new User("john@doe.com", "John Doe")); 15 | 16 | 17 | User loadUser(String email) { 18 | return new User("jane@doe.com", "Jane Doe"); 19 | } 20 | 21 | // RELEVANT CODE 22 | 23 | String email = "jane@doe.com" 24 | 25 | User user = users.get(email) 26 | 27 | if (user == null) { 28 | user = loadUser(email); 29 | users.put(email, user); 30 | } 31 | 32 | System.out.println("user = " + user) 33 | -------------------------------------------------------------------------------- /part-2/12-recursion/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 12: Recursion' 2 | 3 | ext.examples = [ 4 | 'example-12-1': ['HeadRecursion': 'Calculating factorials with head recursion'], 5 | 'example-12-2': ['TailRecursion': 'Calculating factorials with tail recursion'], 6 | 'example-12-3': ['TreeNodeStructure': 'Tree node structure'], 7 | 'example-12-4': ['TreeTraversalIterative': 'Iterative tree traversal'], 8 | 'example-12-5': ['TreeTraversalRecursive': 'Recursive tree traversal'], 9 | 'example-12-6': ['TreeTraversalRecursiveNode': 'Extend Node with traversal method'], 10 | ] 11 | -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-1/HeadRecursion.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-1. Calculating factorials with head recursion 6 | // 7 | 8 | public class HeadRecursion { 9 | 10 | static long factorialHead(long n) { 11 | if (n == 1L) { 12 | return 1L; 13 | } 14 | 15 | var nextN = n - 1L; 16 | 17 | return n * factorialHead(nextN); 18 | } 19 | 20 | public static void main(String... args) { 21 | 22 | var result = factorialHead(4L); 23 | System.out.println("result = " + result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-1/head-recursion.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-1. Calculating factorials with head recursion 6 | // 7 | 8 | long factorialHead(long n) { 9 | if (n == 1L) { 10 | return 1L; 11 | } 12 | 13 | var nextN = n - 1L; 14 | 15 | return n * factorialHead(nextN); 16 | } 17 | 18 | var result = factorialHead(4L); 19 | System.out.println("result = " + result); -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-2/TailRecursion.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-2. Calculating factorials with tail recursion 6 | // 7 | 8 | public class TailRecursion { 9 | 10 | static long factorialTail(long n, 11 | long accumulator) { 12 | 13 | if (n == 1L) { 14 | return accumulator; 15 | } 16 | 17 | var nextN = n - 1L; 18 | var nextAccumulator = n * accumulator; 19 | 20 | return factorialTail(nextN, nextAccumulator); 21 | } 22 | 23 | public static void main(String... args) { 24 | 25 | var result = factorialTail(4L, 1L); 26 | System.out.println("result = " + result); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-2/tail-recursion.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-2. Calculating factorials with tail recursion 6 | // 7 | 8 | long factorialTail(long n, 9 | long accumulator) { 10 | 11 | if (n == 1L) { 12 | return accumulator; 13 | } 14 | 15 | var nextN = n - 1L; 16 | var nextAccumulator = n * accumulator; 17 | 18 | return factorialTail(nextN, nextAccumulator); 19 | } 20 | 21 | var result = factorialTail(4L, 1L) 22 | System.out.println("result = " + result) 23 | -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-3/TreeNodeStructure.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-3. Tree node structure 6 | // 7 | 8 | public class TreeNodeStructure { 9 | 10 | record Node(T value, Node left, Node right) { 11 | 12 | static Node of(T value, Node left, Node right) { 13 | return new Node<>(value, left, right); 14 | } 15 | 16 | static Node of(T value) { 17 | return new Node<>(value, null, null); 18 | } 19 | 20 | static Node left(T value, Node left) { 21 | return new Node<>(value, left, null); 22 | } 23 | 24 | static Node right(T value, Node right) { 25 | return new Node<>(value, null, right); 26 | } 27 | } 28 | 29 | public static void main(String... args) { 30 | 31 | var root = Node.of("1", 32 | Node.of("2", 33 | Node.of("4", 34 | Node.of("7"), 35 | Node.of("8")), 36 | Node.of("5")), 37 | Node.right("3", 38 | Node.left("6", 39 | Node.of("9")))); 40 | 41 | System.out.println("tree = " + root); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /part-2/12-recursion/example-12-3/tree-node-structure.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 12 - Recursion 4 | // 5 | // Example 12-3. Tree node structure 6 | // 7 | 8 | record Node(T value, Node left, Node right) { 9 | 10 | static Node of(T value, Node left, Node right) { 11 | return new Node<>(value, left, right); 12 | } 13 | 14 | static Node of(T value) { 15 | return new Node<>(value, null, null); 16 | } 17 | 18 | static Node left(T value, Node left) { 19 | return new Node<>(value, left, null); 20 | } 21 | 22 | static Node right(T value, Node right) { 23 | return new Node<>(value, null, right); 24 | } 25 | } 26 | 27 | { 28 | var root = Node.of("1", 29 | Node.of("2", 30 | Node.of("4", 31 | Node.of("7"), 32 | Node.of("8")), 33 | Node.of("5")), 34 | Node.right("3", 35 | Node.left("6", 36 | Node.of("9")))); 37 | 38 | System.out.println("tree = " + root); 39 | } 40 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 13: Asynchronous Tasks' 2 | 3 | ext.examples = [ 4 | 'example-13-1': ['FutureFlowOfExecution': 'Future flow of execution'], 5 | 'example-13-2': ['CreationConvenience': 'CompletableFuture creation with convenience methods'], 6 | 'example-13-4': ['NestedStages': 'Unwrapping nested stages'], 7 | 'example-13-5': ['EitherRejected': 'Either operations and rejected stages'], 8 | ] 9 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-1/future-flow-of-execution.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-1. Future flow of execution 6 | // 7 | 8 | import java.util.concurrent.Callable 9 | import java.util.concurrent.ExecutionException 10 | import java.util.concurrent.Executors 11 | import java.util.concurrent.TimeUnit 12 | 13 | var executor = Executors.newFixedThreadPool(10) 14 | 15 | Callable expensiveTask = () -> { 16 | 17 | System.out.println("(task) start"); 18 | 19 | TimeUnit.SECONDS.sleep(2); 20 | 21 | System.out.println("(task) done"); 22 | 23 | return 42; 24 | }; 25 | 26 | System.out.println("(main) before submitting the task") 27 | 28 | var future = executor.submit(expensiveTask) 29 | 30 | System.out.println("(main) after submitting the task") 31 | 32 | var theAnswer = future.get() 33 | System.out.println("theAnswer = " + theAnswer) 34 | 35 | System.out.println("(main) after the blocking call future.get()") 36 | 37 | executor.shutdown() 38 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-2/CreationConvenience.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-2. CompletableFuture creation with convenience methods 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ForkJoinPool; 10 | import java.util.concurrent.Future; 11 | 12 | public class CreationConvenience { 13 | 14 | public static void main(String... args) { 15 | 16 | // Future 17 | 18 | var executorService = ForkJoinPool.commonPool(); 19 | 20 | Future futureRunnable = 21 | executorService.submit(() -> System.out.println("not returning a value")); 22 | 23 | Future futureCallable = 24 | executorService.submit(() -> "Hello, Async World!"); 25 | 26 | // CompleteableFuture 27 | 28 | CompletableFuture completableFutureRunnable = 29 | CompletableFuture.runAsync(() -> System.out.println("not returning a value")); 30 | 31 | CompletableFuture completableFutureSupplier = 32 | CompletableFuture.supplyAsync(() -> "Hello, Async World!"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-2/creation-convenience.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-2. CompletableFuture creation with convenience methods 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ForkJoinPool; 10 | import java.util.concurrent.Future; 11 | 12 | // Future 13 | 14 | var executorService = ForkJoinPool.commonPool() 15 | 16 | Future futureRunnable = executorService.submit(() -> System.out.println("not returning a value")) 17 | 18 | Future futureCallable = executorService.submit(() -> "Hello, Async World!") 19 | 20 | // CompleteableFuture 21 | 22 | CompletableFuture completableFutureRunnable = CompletableFuture.runAsync(() -> System.out.println("not returning a value")); 23 | 24 | CompletableFuture completableFutureSupplier = CompletableFuture.supplyAsync(() -> "Hello, Async World!"); 25 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-4/NestedStages.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-4. Unwrapping nested stages 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | import java.util.concurrent.ExecutionException; 10 | import java.util.function.BiFunction; 11 | import java.util.function.Function; 12 | 13 | public class NestedStages { 14 | 15 | public static void main(String[] args) throws InterruptedException, ExecutionException { 16 | 17 | BiFunction> task = 18 | (lhs, rhs) -> CompletableFuture.supplyAsync(() -> lhs + rhs); 19 | 20 | CompletableFuture future1 = CompletableFuture.supplyAsync(() -> 42); 21 | CompletableFuture future2 = CompletableFuture.supplyAsync(() -> 23); 22 | 23 | CompletableFuture combined = 24 | future1.thenCombine(future2, task) 25 | .thenCompose(Function.identity()); 26 | 27 | Integer result = combined.get(); 28 | System.out.println("result = " + result); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-4/nested-stages.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-4. Unwrapping nested stages 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture 9 | import java.util.concurrent.ExecutionException 10 | import java.util.function.BiFunction 11 | import java.util.function.Function 12 | 13 | CompletableFuture future1 = CompletableFuture.supplyAsync(() -> 42) 14 | CompletableFuture future2 = CompletableFuture.supplyAsync(() -> 23) 15 | 16 | BiFunction> task = (lhs, rhs) -> CompletableFuture.supplyAsync(() -> lhs + rhs) 17 | 18 | { 19 | CompletableFuture combined = 20 | future1.thenCombine(future2, task) 21 | .thenCompose(Function.identity()); 22 | 23 | var result = combined.get(); 24 | System.out.println("result = " + result); 25 | } -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-5/EitherRejected.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-5. Either operations and rejected stages 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture; 9 | 10 | public class EitherRejected { 11 | 12 | public static void main(String... args) { 13 | 14 | CompletableFuture notFailed = 15 | CompletableFuture.supplyAsync(() -> "Success!"); 16 | 17 | CompletableFuture failed = 18 | CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); }); 19 | 20 | // NO OUTPUT BECAUSE THE PREVIOUS STAGE FAILED 21 | var rejected = failed.acceptEither(notFailed, System.out::println); 22 | 23 | // OUTPUT BECAUSE THE PREVIOUS STAGE COMPLETED NORMALLY 24 | var resolved = notFailed.acceptEither(failed, System.out::println); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-5/either-rejected.jsh: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-5. Either operations and rejected stages 6 | // 7 | 8 | import java.util.concurrent.CompletableFuture 9 | 10 | CompletableFuture notFailed = CompletableFuture.supplyAsync(() -> "Success!"); 11 | 12 | CompletableFuture failed = CompletableFuture.supplyAsync(() -> { throw new RuntimeException(); }); 13 | 14 | 15 | // NO OUTPUT BECAUSE THE PREVIOUS STAGE FAILED 16 | var rejected = failed.acceptEither(notFailed, System.out::println) 17 | 18 | // OUTPUT BECAUSE THE PREVIOUS STAGE COMPLETED NORMALLY 19 | var resolved = notFailed.acceptEither(failed, System.out::println) 20 | -------------------------------------------------------------------------------- /part-2/13-asynchronous-tasks/example-13-9/ImageProcessor.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 13 - Asynchronous Tasks 4 | // 5 | // Example 13-9. ImageProcessor with unsettled CompletableFuture 6 | // THIS EXAMPLE IS INCOMPLETE AND FOR ILLUSTRATION PURPOSES ONLY 7 | // 8 | 9 | import java.util.concurrent.CompletableFuture; 10 | 11 | public class ImageProcessor { 12 | 13 | public record Task(CompletableFuture start, 14 | CompletableFuture end) { 15 | // NO BODY 16 | } 17 | 18 | public Task createTask(int maxHeight, 19 | int maxWidth, 20 | boolean keepAspectRatio, 21 | boolean trimWhitespace) { 22 | 23 | var start = new CompletableFuture(); 24 | 25 | var end = unsettled.thenApply(...) 26 | .exceptionally(...) 27 | .thenApply(...) 28 | .handle(...); 29 | 30 | return new Task(start, end); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 14: (Functional) Design Patterns 2 | 3 | 4 | ## Factory Pattern 5 | 6 | [Object-oriented](factory-pattern-oo) 7 | [Functional](factory-pattern-fp) 8 | 9 | ## Decorator Pattern 10 | 11 | [Object-oriented](decorator-pattern-oo) 12 | [Functional](decorator-pattern-fp) 13 | 14 | ## Strategy Pattern 15 | 16 | [Object-oriented](strategy-pattern-oo) 17 | [Functional](strategy-pattern-fp) 18 | 19 | ## Builder Pattern 20 | 21 | [Object-oriented](builder-pattern-oo) 22 | [Functional](builder-pattern-fp) 23 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/build.gradle: -------------------------------------------------------------------------------- 1 | ext.exampleGroup = 'Part 2 - Chapter 14: Design Patterns' 2 | 3 | ext.examples = [ 4 | 'factory-pattern-oo': ['Main': 'Factory Pattern (Object-oriented)'], 5 | 'factory-pattern-fp': ['Main': 'Factory Pattern (Functional)'], 6 | 'decorator-pattern-oo': ['Main': 'Decorator Pattern (Object-oriented)'], 7 | 'decorator-pattern-fp': ['Main': 'Decorator Pattern (Functional)'], 8 | 'strategy-pattern-oo': ['Main': 'Strategy Pattern (Object-oriented)'], 9 | 'strategy-pattern-fp': ['Main': 'Strategy Pattern (Functional)'], 10 | 'builder-pattern-oo': ['Main': 'Builder Pattern (Object-oriented)'], 11 | 'builder-pattern-fp': ['Main': 'Builder Pattern (Functional)'], 12 | ] 13 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/builder-pattern-fp/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String... args) { 9 | 10 | // LAZY BUILDER 11 | 12 | var builder = User.builder() 13 | .name(() -> "Ben Weidig") 14 | .email("ben@example.com"); 15 | 16 | var user = builder.addPermission("create") 17 | .addPermission("edit") 18 | .build(); 19 | 20 | System.out.println(user); 21 | 22 | 23 | // WITH BUILDER 24 | 25 | var user2 = UserWith.builder() 26 | .with(with -> { 27 | with.email = "ben@example.com"; 28 | with.name = "Ben Weidig"; 29 | }) 30 | .withPermissions(permissions -> { 31 | permissions.add("create"); 32 | permissions.add("view"); 33 | }) 34 | .build(); 35 | 36 | System.out.println(user2); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/builder-pattern-oo/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String... args) { 9 | 10 | var builder = User.builder() 11 | .email("john@doe") 12 | .name("John Doe"); 13 | 14 | // DO SOMETHING ELSE, PASS BUILDER ALONG 15 | 16 | var user = builder.addPermission("create") 17 | .addPermission("edit") 18 | .build(); 19 | 20 | System.out.println(user); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/AddMilkDecorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class AddMilkDecorator extends Decorator { 10 | 11 | private final MilkCarton milkCarton; 12 | 13 | public AddMilkDecorator(CoffeeMaker target, MilkCarton milkCarton) { 14 | super(target); 15 | 16 | this.milkCarton = milkCarton; 17 | } 18 | 19 | @Override 20 | public List getIngredients() { 21 | var newIngredients = new ArrayList<>(super.getIngredients()); 22 | newIngredients.add("Milk"); 23 | return newIngredients; 24 | } 25 | 26 | @Override 27 | public Coffee prepare() { 28 | var coffee = super.prepare(); 29 | coffee = this.milkCarton.pourInto(coffee); 30 | return coffee; 31 | } 32 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/AddSugarDecorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class AddSugarDecorator extends Decorator { 10 | 11 | public AddSugarDecorator(CoffeeMaker target) { 12 | super(target); 13 | } 14 | 15 | @Override 16 | public List getIngredients() { 17 | var newIngredients = new ArrayList<>(super.getIngredients()); 18 | newIngredients.add("Sugar"); 19 | return newIngredients; 20 | } 21 | 22 | @Override 23 | public Coffee prepare() { 24 | var coffee = super.prepare(); 25 | // add sugar 26 | return coffee; 27 | } 28 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/Barista.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.Arrays; 7 | import java.util.function.Function; 8 | 9 | public final class Barista { 10 | 11 | public static CoffeeMaker decorate(CoffeeMaker coffeeMaker, 12 | Function decorator) { 13 | 14 | return decorator.apply(coffeeMaker); 15 | } 16 | 17 | public static CoffeeMaker decorate(CoffeeMaker coffeeMaker, 18 | Function... decorators) { 19 | 20 | Function reducedDecorations = 21 | Arrays.stream(decorators) 22 | .reduce(Function.identity(), 23 | Function::andThen); 24 | 25 | return reducedDecorations.apply(coffeeMaker); 26 | } 27 | 28 | private Barista() { 29 | // Suppress default constructor. 30 | // Ensures non-instantiability and non-extendability. 31 | } 32 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/BlackCoffee.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public record BlackCoffee() implements Coffee { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/BlackCoffeeMaker.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public class BlackCoffeeMaker implements CoffeeMaker { 9 | 10 | @Override 11 | public List getIngredients() { 12 | return List.of("Robusta Beans", "Water"); 13 | } 14 | 15 | @Override 16 | public Coffee prepare() { 17 | return new BlackCoffee(); 18 | } 19 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/Coffee.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface Coffee { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/CoffeeMaker.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public interface CoffeeMaker { 9 | 10 | List getIngredients(); 11 | 12 | Coffee prepare(); 13 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/Decorations.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.function.Function; 7 | 8 | public final class Decorations { 9 | 10 | public static Function addMilk(MilkCarton milkCarton) { 11 | return coffeeMaker -> new AddMilkDecorator(coffeeMaker, milkCarton); 12 | } 13 | 14 | public static Function addSugar() { 15 | return AddSugarDecorator::new; 16 | } 17 | 18 | // ... 19 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/Decorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public abstract class Decorator implements CoffeeMaker { 9 | 10 | private final CoffeeMaker target; 11 | 12 | public Decorator(CoffeeMaker target) { 13 | this.target = target; 14 | } 15 | 16 | @Override 17 | public List getIngredients() { 18 | return this.target.getIngredients(); 19 | } 20 | 21 | @Override 22 | public Coffee prepare() { 23 | return this.target.prepare(); 24 | } 25 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-fp/MilkCarton.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class MilkCarton { 7 | 8 | public Coffee pourInto(Coffee coffee) { 9 | // do stuff 10 | return coffee; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/AddMilkDecorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class AddMilkDecorator extends Decorator { 10 | 11 | private final MilkCarton milkCarton; 12 | 13 | public AddMilkDecorator(CoffeeMaker target, MilkCarton milkCarton) { 14 | super(target); 15 | 16 | this.milkCarton = milkCarton; 17 | } 18 | 19 | @Override 20 | public List getIngredients() { 21 | var newIngredients = new ArrayList<>(super.getIngredients()); 22 | newIngredients.add("Milk"); 23 | return newIngredients; 24 | } 25 | 26 | @Override 27 | public Coffee prepare() { 28 | var coffee = super.prepare(); 29 | coffee = this.milkCarton.pourInto(coffee); 30 | return coffee; 31 | } 32 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/AddSugarDecorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class AddSugarDecorator extends Decorator { 10 | 11 | public AddSugarDecorator(CoffeeMaker target) { 12 | super(target); 13 | } 14 | 15 | @Override 16 | public List getIngredients() { 17 | var newIngredients = new ArrayList<>(super.getIngredients()); 18 | newIngredients.add("Sugar"); 19 | return newIngredients; 20 | } 21 | 22 | @Override 23 | public Coffee prepare() { 24 | var coffee = super.prepare(); 25 | // add sugar 26 | return coffee; 27 | } 28 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/BlackCoffee.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public record BlackCoffee() implements Coffee { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/BlackCoffeeMaker.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public class BlackCoffeeMaker implements CoffeeMaker { 9 | 10 | @Override 11 | public List getIngredients() { 12 | return List.of("Robusta Beans", "Water"); 13 | } 14 | 15 | @Override 16 | public Coffee prepare() { 17 | return new BlackCoffee(); 18 | } 19 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/Coffee.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface Coffee { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/CoffeeMaker.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public interface CoffeeMaker { 9 | 10 | List getIngredients(); 11 | 12 | Coffee prepare(); 13 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/Decorator.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.List; 7 | 8 | public abstract class Decorator implements CoffeeMaker { 9 | 10 | private final CoffeeMaker target; 11 | 12 | public Decorator(CoffeeMaker target) { 13 | this.target = target; 14 | } 15 | 16 | @Override 17 | public List getIngredients() { 18 | return this.target.getIngredients(); 19 | } 20 | 21 | @Override 22 | public Coffee prepare() { 23 | return this.target.prepare(); 24 | } 25 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String... args) { 9 | 10 | // Café con Leche 11 | 12 | CoffeeMaker coffeeMaker = new BlackCoffeeMaker(); 13 | CoffeeMaker decoratedCoffeeMaker = new AddMilkDecorator(coffeeMaker, new MilkCarton()); 14 | 15 | System.out.println("Ingredients for Café con Leche: " + decoratedCoffeeMaker.getIngredients()); 16 | 17 | // Add Sugar 18 | 19 | CoffeeMaker lastDecoratedCoffeeMaker = new AddSugarDecorator(decoratedCoffeeMaker); 20 | 21 | System.out.println("Ingredients after adding sugar: " + lastDecoratedCoffeeMaker.getIngredients()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/decorator-pattern-oo/MilkCarton.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class MilkCarton { 7 | 8 | public Coffee pourInto(Coffee coffee) { 9 | // do stuff 10 | return coffee; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Circle.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Circle extends Shape { 7 | 8 | public Circle(Color color) { 9 | super(0, color, ShapeType.CIRCLE); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Color.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public enum Color { 7 | RED, GREEN, BLUE; 8 | } 9 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String[] args) { 9 | var triangle = ShapeType.TRIANGLE.newInstance(Color.RED); 10 | System.out.println(triangle); 11 | } 12 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Pentagon.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Pentagon extends Shape { 7 | 8 | public Pentagon(Color color) { 9 | super(5, color, ShapeType.PENTAGON); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Shape.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public abstract class Shape { 7 | 8 | public Shape(int corners, Color color, ShapeType type) { 9 | this.corners = corners; 10 | this.color = color; 11 | this.type = type; 12 | } 13 | 14 | private final int corners; 15 | private final Color color; 16 | private final ShapeType type; 17 | 18 | public int corners() { 19 | return this.corners; 20 | } 21 | 22 | Color color() { 23 | return this.color; 24 | } 25 | 26 | ShapeType type() { 27 | return this.type; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return String.format("%s[corners=%d, color=%s]", type(), corners(), color()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/ShapeType.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.Objects; 7 | import java.util.function.Function; 8 | 9 | public enum ShapeType { 10 | CIRCLE(Circle::new), 11 | TRIANGLE(Triangle::new), 12 | SQUARE(Square::new), 13 | PENTAGON(Pentagon::new); 14 | 15 | public final Function factory; 16 | 17 | ShapeType(Function factory) { 18 | this.factory = factory; 19 | } 20 | 21 | public Shape newInstance(Color color) { 22 | Objects.requireNonNull(color); 23 | return this.factory.apply(color); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Square.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Square extends Shape { 7 | 8 | public Square(Color color) { 9 | super(4, color, ShapeType.SQUARE); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-fp/Triangle.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Triangle extends Shape { 7 | 8 | public Triangle(Color color) { 9 | super(3, color, ShapeType.TRIANGLE); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Circle.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Circle extends Shape { 7 | 8 | public Circle(Color color) { 9 | super(0, color, ShapeType.CIRCLE); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Color.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public enum Color { 7 | RED, GREEN, BLUE; 8 | } 9 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | var triangle = ShapeFactory.newShape(ShapeType.TRIANGLE, Color.RED); 9 | 10 | System.out.println(triangle); 11 | } 12 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Pentagon.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Pentagon extends Shape { 7 | 8 | public Pentagon(Color color) { 9 | super(5, color, ShapeType.PENTAGON); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Shape.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public abstract class Shape { 7 | 8 | public Shape(int corners, Color color, ShapeType type) { 9 | this.corners = corners; 10 | this.color = color; 11 | this.type = type; 12 | } 13 | 14 | private final int corners; 15 | private final Color color; 16 | private final ShapeType type; 17 | 18 | public int corners() { 19 | return this.corners; 20 | } 21 | 22 | Color color() { 23 | return this.color; 24 | } 25 | 26 | ShapeType type() { 27 | return this.type; 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return String.format("%s[corners=%d, color=%s]", type(), corners(), color()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/ShapeFactory.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | import java.util.Objects; 7 | 8 | public class ShapeFactory { 9 | public static Shape newShape(ShapeType type, Color color) { 10 | Objects.requireNonNull(color); 11 | 12 | return switch (type) { 13 | case CIRCLE -> new Circle(color); 14 | case TRIANGLE -> new Triangle(color); 15 | case SQUARE -> new Square(color); 16 | case PENTAGON -> new Pentagon(color); 17 | default -> throw new IllegalArgumentException("Unknown type: " + type); 18 | }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/ShapeType.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public enum ShapeType { 7 | CIRCLE, 8 | TRIANGLE, 9 | SQUARE, 10 | PENTAGON; 11 | } 12 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Square.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Square extends Shape { 7 | 8 | public Square(Color color) { 9 | super(4, color, ShapeType.SQUARE); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/factory-pattern-oo/Triangle.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Triangle extends Shape { 7 | 8 | public Triangle(Color color) { 9 | super(3, color, ShapeType.TRIANGLE); 10 | } 11 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/ExpeditedShipping.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class ExpeditedShipping implements ShippingStrategy { 7 | private final boolean signatureRequired; 8 | 9 | public ExpeditedShipping(boolean signatureRequired) { 10 | 11 | this.signatureRequired = signatureRequired; 12 | } 13 | 14 | @Override 15 | public void ship(Parcel parcel) { 16 | System.out.println(String.format("Shipping Parcel with '%s' (signature=%s)", getClass().getSimpleName(), this.signatureRequired)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String... args) { 9 | 10 | var service = new ShippingService() {}; 11 | var parcel = new Parcel(); 12 | 13 | service.ship(parcel, ShippingStrategies.standard()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/Parcel.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public record Parcel() { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/ShippingService.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface ShippingService { 7 | 8 | default void ship(Parcel parcel, 9 | ShippingStrategy strategy) { 10 | strategy.ship(parcel); 11 | } 12 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/ShippingStrategies.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public final class ShippingStrategies { 7 | 8 | public static ShippingStrategy expedited(boolean requiresSignature) { 9 | return parcel -> new ExpeditedShipping(requiresSignature); 10 | } 11 | 12 | public static ShippingStrategy standard() { 13 | return new StandardShipping(); 14 | } 15 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/ShippingStrategy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface ShippingStrategy { 7 | 8 | void ship(Parcel parcel); 9 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-fp/StandardShipping.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class StandardShipping implements ShippingStrategy { 7 | 8 | @Override 9 | public void ship(Parcel parcel) { 10 | System.out.println("Shipping Parcel with '" + getClass().getSimpleName() + "'"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/ExpeditedShipping.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class ExpeditedShipping implements ShippingStrategy { 7 | private final boolean signatureRequired; 8 | 9 | public ExpeditedShipping(boolean signatureRequired) { 10 | 11 | this.signatureRequired = signatureRequired; 12 | } 13 | 14 | @Override 15 | public void ship(Parcel parcel) { 16 | System.out.println(String.format("Shipping Parcel with '%s' (signature=%s)", 17 | getClass().getSimpleName(), 18 | this.signatureRequired)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/Main.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class Main { 7 | 8 | public static void main(String... args) { 9 | 10 | var service = new ShippingService() {}; 11 | var parcel = new Parcel(); 12 | var strategy = new ExpeditedShipping(true); 13 | 14 | service.ship(parcel, strategy); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/Parcel.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public record Parcel() { 7 | } 8 | -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/ShippingService.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface ShippingService { 7 | 8 | default void ship(Parcel parcel, 9 | ShippingStrategy strategy) { 10 | strategy.ship(parcel); 11 | } 12 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/ShippingStrategy.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public interface ShippingStrategy { 7 | 8 | void ship(Parcel parcel); 9 | } -------------------------------------------------------------------------------- /part-2/14-design-patterns/strategy-pattern-oo/StandardShipping.java: -------------------------------------------------------------------------------- 1 | // 2 | // A FUNCTIONAL APPROACH TO JAVA 3 | // Chapter 14 - Design Patterns 4 | // 5 | 6 | public class StandardShipping implements ShippingStrategy { 7 | 8 | @Override 9 | public void ship(Parcel parcel) { 10 | System.out.println("Shipping Parcel with '" + getClass().getSimpleName() + "'"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | def includeProject(part, folder) { 2 | def projectName = ":${folder}" 3 | include projectName 4 | project(projectName).projectDir = file("part-${part}/${folder}") 5 | } 6 | 7 | includeProject(1, '02-functional-java') 8 | includeProject(1, '03-functional-jdk') 9 | 10 | includeProject(2, '04-immutability') 11 | includeProject(2, '05-working-with-records') 12 | includeProject(2, '06-data-processing-with-streams') 13 | includeProject(2, '07-working-with-streams') 14 | includeProject(2, '08-parallel-data-processing-with-streams') 15 | includeProject(2, '09-handling-null-with-optionals') 16 | includeProject(2, '10-functional-exception-handling') 17 | includeProject(2, '11-lazy-evaluation') 18 | includeProject(2, '12-recursion') 19 | includeProject(2, '13-asynchronous-tasks') 20 | includeProject(2, '14-design-patterns') 21 | --------------------------------------------------------------------------------