Skip to content

Latest commit

 

History

History
290 lines (227 loc) · 10.4 KB

01_optionals.adoc

File metadata and controls

290 lines (227 loc) · 10.4 KB

Extensions to Optional

The static factory Methods for creating Optional instances are not really meant to be used as statically imported methods. They have no meaningful names to be used this way. They also differ from the commonly used names some, none and maybe which are used in many other languages.
The class OptionalIntExtensions provides static factory methods with these common names which are implemented to be inlined to the factory methods used by the JDK.

Examples:

import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<String> no = none
val Optional<String> yes = some("yesss!")
val Optional<String> dunno = maybe(possiblyNull())
// ...
private def String possiblyNull() {
	if(System.currentTimeMillis % 2 == 0) {
		"I'm in ur optional"
	} else {
		null
	}
}

The optional class does not provide a filter method, that filters the optional based on the class the wrapped object is instance of, as known e.g. from Xtend’s filter methods on Iterable. The OptionalIntExtensions adds such a method, providing an instance check of the wrapped value.

Example:

import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<Object> optObj = some("Hi there!")
val Optional<String> optStr = optObj.filter(String)
optStr.ifPresent [
	println(it.toUpperCase)
]

When testing an Optional for a value or otherwise perform a different operation the optional has to be used twice e.g.

import java.util.Optional
// ...
val noVal = Optional.empty
if(noVal.isPresent) {
	val value = noVal.get
	println("Here is your value: "+ value)
} else {
	println("Awww, no value")
}

This can be error prone, since the optional (in the example noVal) has to be used twice and a different optional may be used accidently. To not run into this issue this library provides the whenPresent method which allows defining an else branch on the returned object.

Example:

import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val noVal = Optional.empty
noVal.whenPresent [
	println("Here is your value: "+ it)
].elseDo [
	println("Awww, no value")
]

Alternatively, the ifPresentOrElse extension method can be used, but this does not have a clear visual separation which case is the if and which the else callback.

To avoid allocating objects over and over for the lambda passed to the elseDo method, there are overloaded versions of the method passing on additional parameters to the lambda. This can avoid "capturing" lambdas which would create a new object on every elseDo call.

Example:

import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val captureMe = "no value"
val noVal = Optional.empty
noVal.whenPresent [
	println("Here is your value: "+ it)
].elseDo(captureMe) [
	println("Awww, " + it)
]

To bridge between APIs providing an Optional value and ones that expect multiple values, the extension methods asIterable, toList and toSet are provided to create immutable implementations of common JVM collection APIs.

The Optional class has a map method that can map the value present in the optional to a value of another type. Unfortunately there is no method to map to a primitive type returning a primitive optional, such as OptionalInt. The extension methods mapInt, mapLong, and mapDouble allow mapping to primitive options without having to box the resulting value. To map to a boolean value the method test can be used, which returns an OptionalBoolean. This extension method is mostly intended to check a property on a value wrapped in an Optional.

Example:

import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<String> yes = some("yesss!")
val OptionalInt lenOpt = yes.mapInt[length]
val len = lenOpt.orElse(0)
println("Length is " + len)

The call chain map[…​].orElseGet[..] is pretty common. As a shortcut the method mapOrGet is provided.

Some methods on Optional introduced in Java 9 are available as retrofitted extension methods. When compiling a class using the extension method targeting Java 9, the native Optional method has precedence and will be used. No changes in the source code has to be done to switch to the native Java 9 implementation. The following instance methods of Optional are backported for Java 8:

As a shortcut for the or extension method, the || operator is provided. The ?: operator is a shortcut for the orElse method on Optional.

Tip

Related JavaDocs:

Extensions to Primitive Optionals

Extensions to the primitive versions of Optional are provided by the following classes:

de.fhg.fokus.xtensions.optional.OptionalIntExtensions
de.fhg.fokus.xtensions.optional.OptionalLongExtensions
de.fhg.fokus.xtensions.optional.OptionalDoubleExtensions

Same as for Optional, there is a some alias for the OptionalInt.of, OptionalLong.of, and OptionalDouble.of methods (see Extensions to Optional).
The methods noInt, noLong, and noDouble provide empty primitive Optionals.

The Open JDK / Oracle JDK currently does not cache OptionalInt and OptionalLong instances in the static factory method OptionalInt.of(int) and OptionalLong.of(long) as it is currently done for Integer creation in Integer.valueOf(int). To provide such a caching static factory methods, the OptionalIntExtensions.someOf(int) and OptionalLongExtensions.someOf(long) method were introduced.

Example:

import static de.fhg.fokus.xtensions.optional.OptionalIntExtensions.*
// ...
if(someOf(42) === someOf(42)) {
	println("someOf caches instances")
}

Stunningly, the primitive versions of Optional do not provide map and filter methods. These are provided as extension methods by this library.

OptionalBoolean

The class de.fhg.fokus.xtensions.optional.OptionalBoolean represents a boolean value that may be absent. As with other optional types, references to objects of this type should never be null! It is mostly intended to be used as a return value from methods to allow for fluent call chains.

Creation

The static factory method empty creates an OptionalBoolean not holding a value. Creating an OptionalBoolean from a Boolean that may be null, the ofNullable factory method can be used; an alias meant to be used as an extension method is the asOptional factory method. To create an OptionalBoolean from a boolean value, definitely holding a value, the static of factory method can be used. The static methods ofTrue and ofFalse provide OptionalBoolean wrapping true or false.

Testing for Content

To check if an OptionalBoolean is either empty, wrap true or wrap false, the class provides several methods:

  • boolean isEmpty()

  • boolean isPresent()

  • boolean isTrue()

  • boolean isTrueOrEmpty()

  • boolean isFalse()

  • boolean isFalseOrEmpty()

Note that isTrue and isFalse return false if the OptionalBoolean is empty.

Analogous to the methods mentioned above there are methods that test for a value and execute a given callback if the condition applies. These methods have the if prefix, e.g. void ifTrue(()⇒void then). Additional the void ifPresentOrElse(BooleanConsumer action, Runnable emptyAction) method takes two callbacks, one for the case of an optional with value, and one that is invoked if the optional is empty.

import de.fhg.fokus.xtensions.optional.OptionalBoolean
// ...
val random = new Random()
val randomBool = if (random.nextBoolean) {
		OptionalBoolean.empty
	} else {
		OptionalBoolean.of(random.nextBoolean)
	}

// will not print on empty optional
randomBool.ifTrue [
	println("Yippie! A random true!")
]

Extracting Values

There are several methods that allow to extract a wrapped value, and handle the case of an empty optional in different ways.

  • orElse(boolean fallback) returns a given default value on empty,

  • orElseGet(BooleanSupplier fallback) computes a default value (via fallback) on empty

  • <X extends Throwable> boolean orElseThrow(Supplier<? extends X> exceptionSupplier) throws X throws an exception provided by exceptionSupplier on empty

Note that the OptionalBoolean does not have a simple get method which throws an NoSuchElementException as other optionals have. However the method getNullable returns a Boolean which is null if the optional is empty.

Example:

import de.fhg.fokus.xtensions.optional.OptionalBoolean
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val names = #["Mike", "Joe", "Christian"]
val longNameWithC = names.stream
	.filter[length > 4]
	.findFirst
	.test[startsWith("C")]
	.orElse(false)

if(longNameWithC) {
	println("The long name starts with 'C'")
}

Note that the test method is an extension method on Optional provided by OptionalExtensions returning an OptionalBoolean.