- Introduction
- Out Of Scope
- Explanation Declarative Approach
- Explanation Functional Interface
- Explanation Callback
- Explanation Consumer
- Explanation Function
- Explanation Optional
- Explanation Predicate
- Explanation Supplier
- Explanation Lambdas
- Explanation Map
- Explanation Stream
- Explanation Parallel Stream
- Explanation Combinator Pattern
- Explanation Other important Points
- Technologies Used
- Prerequisities
- Commands
- References
- Contact Information
The aim of this project is to show case the differences between Imperative and declarative approach of coding. Another focus is on different functions available in the Stream API as well as some pros and cons of some of these approaches.
Since only basic implementation of various functions from stream API is targeted for this project and the main idea is just to see them in action, unit tests are out of scope.
Our normal approach before Java 8 was to program via Imperative Programming but lambdas work via Declarative programming.
Declarative programming: Declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow. In simple words, Declarative Programming is like asking your friend to draw a landscape. You don’t care how they draw it, that’s up to them.
whereas
Imperative Programming: imperative programming is a programming paradigm that uses statements that changes a program's state. In simple words, Imperative Programming is like your friend listening to Bob Ross telling them how to paint a landscape. While good ole Bob Ross isn’t exactly commanding, he is giving them step by step directions to get the desired result.
A functional interface is an interface which has exactly one abstract method.
Please note the following from JavaDoc:
Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface's abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.
Let us see the six basic function interfaces.
Interface | Signature | Examples |
---|---|---|
UnaryOperator T | apply(T t) | String::toLowerCase, Math::tan |
BinaryOperator T | apply(T t1, T t2) | BigInteger::add, Math::pow |
Function<T, R> R | apply(T t) | Arrays::asList, Integer::toBinaryString |
Predicate<T, U> boolean | test(T t, U u) | String::isEmpty, Character::isDigit |
Supplier T | get() | LocalDate::now, Instant::now |
Consumer void | accept(T t) | System.out::println, Error::printStackTrace |
Rules for functional programming
Pure functional programming has a set of rules that one must follow:
- No state, meaning that the function must not depend on or change the state of a variable/object outside the boundary of the function.
- Pure functions, meaning that the function should have everything encapsulated within it and it should not depend on something outside the boundary of the function, for example, some form of global state.
- No side effects outside of the boundary of the function.
- Higher order functions: A function is considered a higher order functions if one of the following two conditions is true.
- The function takes one or more functions as parameters. For example, Callback
- The function returns another function as result. For example, combinator pattern.
A callback is some code that you pass to a given method, so that it can be called at a later time.
Reference: The source code for the Callback is present in Callback.java class.
Consumer: Represents an operation that accepts a single input argument and returns no result. It is similar to a void function when compared to imperative implementation.
BiConsumer<T,U>: Represents an operation that accepts two input arguments and returns no result.
Consumer is implemented in Consumer class in the project. In the consumer class, a consumer object is created which takes Customer as input and prints the information on the command line. In the main method, the consumer is called via accept() function.
Reference: The source code for the Consumer is present in _Consumer.java class.
- Add a picture linking imperative and declarative approach together.
Function<T,R>: Represents a function that accepts one argument and produces a result.
BiFunction<T,U,R>: Represents a function that accepts two arguments and produces a result.
Function is implemented in the Function class in the project. In the function class, a function object multiplyByTen is created which takes integer as input and returns a Integer as output. In the main method, the function is utilized by calling apply() function.
Similarly, a BiFunction incrementByOneAndMultiplyBiFunction is also created which takes two integers as input and returns an output.In the main method, andThen() along with apply() function.
Reference: The source code for the Function is present in _Function.java class.
- Add a picture linking imperative and declarative approach together.
A container object which may or may not contain a non-null value. It contains several methods such as isPresent() and get(). If a value is present, isPresent() will return true and get() will return the value.
Additional methods that depend on the presence or absence of a contained value are provided, such as orElse() (return a default value if value not present) and ifPresent() (execute a block of code if the value is present).
Optional is implemented in the Optional class in the project. In the main method, the code
final Optional<Object> empty = Optional.empty();
means that Optional object is empty. Due to this, isPresent() returns false whereas isEmpty() returns true.
Similarly, the code
final Optional<String> hello = Optional.of("hello");
creates an optional object of hello. Due to this, isPresent() returns true whereas isEmpty() returns false.
The code
final Optional<String> hello2 = Optional.ofNullable(null);
delivers a null value. Hence, when calling
hello2.orElse("World");
System.out.println() prints 'World' to command line.
ifPresentOrElse(): Using this method, one can define the logic if the value is present or respond accordingly if it is null. Logically speaking, it works like a ternary operator.
Reference: The source code for the Optional is present in _Optional.java class.
- Add a picture linking imperative and declarative approach together.
Predicate: Represents a predicate(boolean-valued function) of one argument. Basically, it evaluates a condition to check if it results in true or false. Similarly,
BiPredicate<T,U>: Represents a predicate (boolean-valued function) of two arguments.
Predicate is implemented in Predicate class in the project. In the predicate class, a predicate object
static Predicate<String> isPhoneNumberValidPredicate = phoneNumber ->
phoneNumber.startsWith("07") &&
phoneNumber.length() == 11;
is created which takes phone number as input and checks if the conditions are fulfilled. In the main method, the predicate is called via test() function. Please note that two or more predicates can be combined using .and() or .or() functions.
Reference: The source code for the Predicate is present in _Predicate.java class.
- Add a picture linking imperative and declarative approach together.
Supplier: Represents a supplier of results. It takes no input and gives an object/list of objects as output.
Supplier is implemented in Supplier class in the project. In the supplier class, a supplier object
static Supplier<String> getDBConnectionUrlsSupplier = ()
-> DUMMY_DATABASE_CONNECTION_URL;
is created which nothing as input and delivers a String as output. In the main method, the supplier is called via get() function.
Reference: The source code for the Supplier is present in _Supplier.java class.
- Add a picture linking imperative and declarative approach together.
- To be defined
- To be defined
- To be defined
- To be defined
- To be defined
-
Created a simple maven project.
-
My project was not compiling, so I added the properties for 1.8 version as indicated in this link: https://facingissuesonit.com/2021/05/08/maven-error-source-option-5-is-no-longer-supported-use-6-or-later/
-
A stream allows us to get in an abstract mode where we simple tell it what we want.
-
Examples of functional programming:
// Assignment context Predicate p = String::isEmpty; Here we specify the predicate as an empty string . Similar to String p="";
// Method invocation context stream.filter(e -> e.getSize() > 10)... Here e--> is the return type e.size()>10 is the logic of the method
// Cast context stream.map((ToIntFunction) e -> e.getSize())... Here, (ToIntFunction) is used to transform the return type to Integer.
- Java 11
- To be defined
- To be defined
- To be defined
- 1: Java functional package (JavaDoc)
- 2: Java Stream (JavaDoc)
- 3: Functional Programming in Java - Full Course (YouTube)
- 4: Java Optionals | Crash Course (YouTube)
- 5: Get a Taste of Lambdas and Get Addicted to Streams by Venkat Subramaniam (Youtube)
- 6: Java 8 STREAMS Tutorial (YouTube)
- 7: Declarative vs Imperative Programming
- 8: Java 8 Tutorials(From Mkyong)
- 9: What is a callback function?
- 10: Optional (JavaDoc)
How to reach me? At github specific gmail account. Additionally, you can reach me via Linkedin or at Xing