This is a library of utilities to encourage and support functional programming in Java, With inspiration from functional programming articles and languages like Haskell and Scala, new monads and enhancements to existing ones have been introduced. This page provides a brief description of the objects in the library, but it is encouraged to review the javadoc documentation for additional information and examples.
The library is now available for download from Maven Central Repository. In the POM file add the maven dependency configuration below:
<!-- https://mvnrepository.com/artifact/org.javalaboratories/java-extensions -->
<dependency>
<groupId>org.javalaboratories</groupId>
<artifactId>java-extensions</artifactId>
<version>2.0.1.0-RELEASE</version>
</dependency>
Alternatively, for Gradle users, amend the build.gradle file with the following:
// https://mvnrepository.com/artifact/org.javalaboratories/java-extensions
compile group: 'org.javalaboratories', name: 'java-extensions', version: '2.0.1.0-RELEASE'
Use the CryptographyFactory class to gain access to both symmetric and asymmetric cryptography objects. It provides
a simple abstraction over the Java Cryptography Archetecture (JCA). It provides not just decryption and encryption
functionality but also includes signing and verification. In the case of RSA encryption that is not capable of
encrypting large data, a hybrid approach is introduced to enable RSA encryption/decryption of large amounts of data.
Explore the package but here's an example of usage of the library:
// Symmetric cryptography
AesCryptography cryptography = CryptographyFactory.getSymmetricCryptography();
ByteCryptographyResult<SymmetricKey> result = cryptography.encrypt(SymmetricKey.from(PASSWORD), TEXT);
String encrypted = result.getBytesAsBase64();
ByteCryptographyResult<SymmetricKey> stringResult = cryptography.decrypt(SymmetricKey.from(PASSWORD),encrypted);
assertEquals(TEXT, stringResult.getString().orElseThrow());
// Asymmetric cryptography
RsaHybridCryptography cryptography = CryptographyFactory.getAsymmetricHybridCryptography();
ByteCryptographyResult<PublicKey> result = cryptography.encrypt(publicKey,TEXT);
ByteCryptographyResult<PrivateKey> stringResult = cryptography.decrypt(privateKey, result.getBytesAsBase64());
assertNotNull(result.getKey());
assertEquals(TEXT, stringResult.getString().orElseThrow());
// Signing / Verfication
RsaMessageSigner signer = CryptographyFactory.getMessageSigner(prvateKey);
Message message = signer.encrypt(publicKey,TEXT);
// Note that verification does not require a public key, this is "transmitted" in the ciphertext
// and decoded at the recipient's end and then used to verify the message.
RsaMessageVerifier verifier = CryptographyFactory.getMessageVerifier();
String s = verifier.decryptAsString(privateKey,message.getSignedAsBase64());
assertEquals(TEXT, s);
The PublicKey and PrivateKey require the use of the KeyFactory or openssl. Review the factory and other related
classes for more information in the cryptography package.
Either class is a container, similar to the Maybe and Optional classes, that represents one of two possible values
(a disjoint union). Application and/or sub-routines often have one of two possible outcomes, a successful completion or
a failure, and so it is common to encapsulate these outcomes within an Either class. Convention dictates there is a
Left and Right sides; "left" considered to be the "unhappy" outcome, and the "right" as the "happy" outcome or path.
So rather than a method throwing an exception, it can return an Either implementation that could be either a Left
or a Right object, and thus allowing the client to perform various operations and decide on the best course of action.
In the example, the parser.readFromFile method returns an Either object, but notice the concise client code and
readability and how it neatly manages both "unhappy" and "happy" outcomes.
// Client code using parser object.
String string = parser.readFromFile(file)
.flatMap(parser::parse)
.map(jsonObject::marshal)
.fold(Exception::getMessage,s -> s);
...
...
// Parser class (partial implementation)
public class Parser {
public Either<Exception,String> readFromFile(File file) {
try {
...
return Either.right(fileContent)
} catch (FileNotFoundException e) {
return Either.left(e);
}
}
Provided implementations of the Either are right-biased, which means operations like map,flatMap and others have
no effect on the Left implementation, such operations return the "left" value unchanged.
Some objects are expensive to create due to perhaps database access and/or complex calculations. Rather than creating
these objects before they are actually needed, Eval can leverage a lazy strategy, offering the access to the underlying
value only at the point of use. Essentially, the library introduces three main strategies:
- Always - Evaluation always retrieves the
valueat the point of use -- no caching is involved. - Eager - Evaluation occurs immediately and the
valueis therefore readily available. - Later - Evaluation retrieves the
valueat the point of use and caches it for efficient retrieval. LikeMaybe,Eitherand other objects provided in this library,EvalimplementsflatMap,mapand other useful operations. Scala's Cat library and lazy design pattern provided the inspiration for this object.
Eval<Integer> eval = Eval.later(() -> {
logger.info ("Running expensive calculation...",value);
return 1 + 2 + 3;
})
// eval = Later[unset]
eval.get();
// Running expensive calculation...
// eval = Later[7]
eval.get();
// eval = Later[7]
In the above case, eval object caches the results of the calculation, hence no repetition of the "Running expensive
calculation" message. Review javadoc for additional details on supported operations.
EventBroadcaster class has the ability to notify its subscribera of events they are interested in. It is a partial
implementation of the Observer Design Pattern. To complete the design pattern, implement the EventSubscriber
interface and subclass AbstractEvent class for defining custom events or use the out-of-the-box Event objects in the
CommonEvents class. It is recommended to encapsulate the EventBroadcaster object within a class considered to be
observable.
public class DownloadEvent extends AbstractEvent {
public static final DOWNLOAD_EVENT = new DownloadEvent();
public DownloadEvent() { super(); }
}
public class News implements EventSource {
private EventPublisher<String> publisher;
public News() {
publisher = new EventBroadcaster<>(this);
}
public void addListener(EventSubscriber subscriber, Event... captureEvents) {
publisher.subscribe(subscriber,captureEvents);
}
public void download() {
...
...
publisher.publish(DOWNLOAD_EVENT,"Complete");
...
}
}
...
...
public class NewsListener implements EventSubscriber<String> {
public notify(Event event, String value) {
logger.info ("Received download event: {}",value);
}
}
...
...
public class NewsPublisherExample {
public static void main(String args[]) {
News news = new News();
NewsListener listener1 = new NewsListener();
NewsListener<String> listener2 = (event,state) -> logger.info("Received download event: {}",state);
news.addListener(listener1,DOWNLOAD_EVENT);
news.addListener(listener2,DOWNLOAD_EVENT);
news.download();
}
}
The EventBroadcaster class is thread-safe, but for additional information on this and associated classes, please refer
to the javadoc details.
These classes are used to detect possible thread-safe issues in target objects by subjecting them to method calls
from multiple threads. Currently, they do no assert the state of the target object, but generate log information
for analysis. Each Floodgate is configured with a specific number of thread workers with each worker calling the
target object's method repeatedly for a configured number of times. For example the Floodgate in the example code below
configures 5 (default) thread workers to repeatedly call the add(10) method 5 (default) times, and so the expected
total of the additions is 250, as opposed to 240, clearly indicating lost updates.
Floodgate<Integer> floodgate = new Floodgate<>(UnsafeStatistics.class, () -> statistics.add(10));
floodgate.open();
List<Integer> results = floodgate.flood();
logger.info("UnsafeStatics statistics={}", unsafe);
>> output: statistics=UnsafeStatistics(total=240, requests=24, average=10.0
Floodgate is really designed to flood one method/resource, but it is possible to target multiple methods of an object
under test with this class, but consider the use of Torrent instead for this purpose. Torrent manages and controls
multiple Floodgates, ensuring a fairer distribution of thread workers in the core thread pool as well as triggering the
flood of all floodgates simultaneously. These features increase the likelihood of detecting thread-safe issues in the
target object. Review the javadoc for more information.
Torrent torrent = Torrent.builder(UnsafeStatistics.class)
.withFloodgate("print", () -> unsafe.print())
.withFloodgate("add", () -> unsafe.add(10))
.build();
torrent.open();
torrent.flood();
Handlers class provides a broad set of wrapper methods to handle checked exceptions within lambda expressions. Lambdas
are generally short and concise, but checked exceptions can sometimes cause the lambda expression to look unwieldy.
This class has many useful methods that compliment common functional interfaces. Each method wraps the function object
into a function that transforms the checked exception to a RuntimeException object.
For example, here is an example of a method performing file input/output:
public void writeFile(String file) throws IOException {
...
}
// Common technique is to handle the checked exception within the lambda expression :-
Consumer<String> consumer = s -> {
try {
writeFile(s)
} catch (IOException e) {
...
}
}
// But using the Handlers class, the expression becomes :-
Consumer<String> consumer = Handlers.consumer(s -> writeFile(s));
In cases where it may be necessary to capture and manipulate values from or in lambdas, often containers are used, such as
Atomic wrappers. This library now has new and improved Holders that are themselves applicatives, monads and functors, which
means map, flatMap and others are available for operations on the contained value. The most basic operations include
the get and set methods to read or mutate the contained value. Although they are mutable, holder objects are thread-safe,
in that the reference to the contained value cannot be changed by more than one thread. Ensure the contained value is
itself thread-safe to guarantee complete thread safety. Factory methods are provided to create both mutable immutable
holders. Below, are examples of usage:
// This example demenstrates side-effects where the Holder object
// captures the subtotal of the even numbers. Although it is mutated
// it is thread-safe.
Holder<Double> total = Holder.of(0.0);
IntStream
.range(0,1000)
.parallel()
.filter(n -> n % 2 == 0)
.forEach(n -> total.setGet(v -> v + n));;
assertEquals(249500.0, total.get());
...
...
// In this example, the Holder object is created and mutated within the
// context of the reducer.
List<Integer> numbers = Arrays.asList(5,6,7,8,9,10,1,2,3,4);
String result = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.reduce(Holder.of(0.0),(h,v) -> h.map(n -> n + v),(a,b) -> a.map(n -> n + b.fold(0.0,v -> v)))
.map(n -> n / 2)
.fold("",n -> STR."Sum of even numbers (2,4,6,8,10) / 2 = \{n}");
assertEquals("Mean of even numbers (2,4,6,8,10) / 2 = 15.0",result);
In addition to the above, Holder objects come supplied with helper classes to perform operations such as sum, max and
min within streams. Explore the Holders package for more information:
List<Integer> numbers = Arrays.asList(5,6,7,8,9,10,1,2,3,4);
String result = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.map(Double::valueOf)
.collect(DoubleHolders.summing())
.map(n -> n / 2)
.fold("",n -> STR."Sum of even numbers (2,4,6,8,10) / 2 = \{n}");
assertEquals("Sum of even numbers (2,4,6,8,10) / 2 = 15.0",result);
logger.info(result);
The library introduces Maybe class, which is a "drop-in" replacement for Optional. It has features that are only
available in the Optional class in Java-9/11/13 but it also includes new features. For example, the following is
possible:
Maybe<Person> person = people.findById(10983);
person.forEach(System.out::println);
...
person.ifPresentOrElse(System.out::println, () -> System.out.println("Person not found"))
...
List<Person> list = person.toList();
Similarly, there are NullableInt,NullableLong and NullableDouble for int,long and double types respectively.
Release v1.0.5 includes many new features found in Scala and Haskell such as filterNot, flatten and fold--
review javadoc for further details.
The Promise object is a lightweight abstraction of the CompletableFuture object, the inspiration of which came from
the JavaScript's Promise object behaviour. This implementation provides an easily understood API for asynchronous
submission of tasks encapsulated as Action objects with comprehensive exception management. The example below
demonstrates the ability to perform I/O and transformation of data asynchronously, which is then output to the console
in the main thread:
Promise<String> promise = Promises
.newPromise(PrimaryAction.of(() -> doLongRunningTask("Reading integer value from database")))
.then(TransmuteAction.of(value -> "Value read from the database: "+value));
String result = promise.getResult()
.IfPresent(result -> System.out::println(result));
There's a lot more to discover about Promise objects -- review the source's Javadoc for details.
Reducers are collectors but with a difference. Most of them return Stream objects, and so it is possible to continue
functional programming within a stream context. Many of the Collectors methods have been implemented in Reducers class,
again as a possible "drop-in" replacement for Collectors class. Reducers also support a comprehensive set of statistical
calculations such as mean, median, mode, standard deviation and much more. Expect to see an expansion of statistical
functions in this area over the coming days.
List<String> strings = Arrays.asList("9","7","5","76","2","40","101");
strings.stream()
.map(Integer::parseInt)
.peek(n -> System.out.print(n+" "))
.collect(Reducers.summingInt(Integer::valueOf))
.findFirst()
.ifPresent(n -> System.out.println("= "+n));
Outputs: 9 7 5 76 2 40 101 = 240
StopWatch provides a convenient means for timings of methods. There are no explicit methods in the class to start and
stop the timings, because these are naturally determined through the process of invoking the function that is currently
being timed. In other words, executing the function will start the StopWatch and when the function comes to a
natural/unnatural conclusion, the StopWatch is automatically stopped. Number of instances of StopWatch is unlimited,
and so useful statistics are available of all the timed functions via the class' methods or via the StopWatch.getTime
methods. Every StopWatch instance has a unique name, which is useful when reviewing the timings. Use the
StopWatch.time(Runnable) method to start the timings.
StopWatch stopWatch = StopWatch.watch("methodOne");
StopWatch stopWatch2 = StopWatch.watch("methodTwo");
// This is a common usecase of the StopWatch
stopWatch.time(() -> doSomethingMethod(1000));
// Want review all the timings? This is easy!
StopWatch.forEach((a,b) -> logger.info("{} \t-> {}",a,b));
// Output :-
10:47:20.394 [main] INFO StopWatch - methodOne -> 00:00:01.000
10:47:20.399 [main] INFO StopWatch - methodTwo -> 00:00:00.000
A tuple can be considered as a container of ordered elements of different types. Each element may not relate to each
other but collectively they have meaning. They are particularly useful for methods in that they enable them to
return multiple values as a single tuple object, as well as passing several values to a method in a single argument(s).
The tuple has many abilities including join, truncateAt, mapAt, match,toList,toMap and much more. Another
particularly useful feature of tuples is that they are immutable, making them thread-safe. Moreover, they all implement
the Iterable, Serializable, and Comparable interfaces, allowing their contents can be traversed easily, sortable in
collections and persistable. Here are some examples of usage:
Tuple3<String,Integer,Integer> tupleEarth = of("Earth",7926,92955807);
// tupleEarth: ("Earth",7926,92955807), diameter in miles, distance from Sun in miles
tupleEarth.value2();
// tupleEarth.value2(): 7926
Tuple3<String,Integer,Integer> kmEarth = tupleEarth.mapAt2(t -> Math.round((t / (float) 0.621371)));
// tupleEarth: ("Earth",12756,92955807), diameter in km
Tuple5<String,Integer,Integer,String,Integer> tupleEarthMoon = tupleEarth.join(of("Moon",2159));
// earthMoon: ("Earth",7926,92955807,"Moon",2159), joined moon, diameter of 2159
Tuple2<Tuple3<String,Integer,Integer>,Tuple2<String,Integer>> tuplePlanetaryBodies = tupleEarthMoon.spliceAt4();
// planetaryBodies: (("Earth",7926,92955807),("Moon",2159))
tupleEarth = tuplePlanetaryBodies.value1();
// tupleEarth: ("Earth",7926,92955807)
Tuple3<String,Integer,Integer> tupleMoon = tuplePlanetaryBodies.value2().join(92900000);
// tupleMoon: ("Moon",2159,92900000), added moon distance from Sun
Tuple7<String,String,String,String,String,String,String> tupleCoordinates = tupleEarth
.truncateAt2()
.addAt1("Milky Way")
.join(of("Europe","England","Blackfriars","London","EC2 1QW"));
// tupleCoordinates: ("Milky Way","Earth","Europe","England","Blackfriars","London","EC2 1QW")
List<?> list = tupleCoordinates.toList();
// list: ["Milky Way","Earth","Europe","England","Blackfriars","London","EC2 1QW"]
tupleEarth.match(allOf("^Earth$"),(a,b,c) -> logger.info("Earth's distance from Sun {}",c));
// Outputs: "Earth's distance from Sun 92955807"
Try is another class that represents a computation/operation that may either result in an exception or a success.
It is similar to the Either class type, but it dynamically decides the success/failure state. The implementation of the
Try class is inspired by Scala's Try class, and is considered to be a monad as well as a functor, which means the
context of the container is transformable via the flatMap and map methods.
Below are some use cases demonstrating the elegant recovery strategies and other features:
// Recovering from arithmetic exceptions: result1="Result1=1000"
String result1 = Try.of(() -> 100 / 0)
.recover(t -> t instanceof ArithmeticException ? 100 : 100)
.map(n -> n * 10)
.filter(n -> n > 500)
.fold("",n -> "Result1="+n);
// Using orElse to recover: result2="Result2=2500"
String result2 = Try.of(() -> 100 / 0)
.orElse(100)
.map(n -> n * 25)
.filter(n -> n > 500)
.fold("",n -> "Result2="+n);
// IOExceptions are handled gracefully too: result3=0
int result3 = Try.of(() -> new String(Files.readAllBytes(Paths.get("does-not-exist.txt"))))
.orElse("")
.map(String::length)
.fold(-1,Function.identity());
There are many more operations available, the API is documented, so go ahead and explore them. There is a potential case to abandon the use of the try-catch block in favour of a more functional programming approach.
Development is ongoing. I have many ideas in the pipeline, and of course will consider your ideas and recommendations. If you encounter any bugs, please raise an issue(s).
Licensed under the Apache License, Version 2.0 (the "License")
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.