You are on page 1of 12

Week 7: The maybe context

null and NullPointerException


To understand what a null object is, we will look through the example of the createUnitCircle()
method. Basically, what this method does is that it first checks the condition of the distance between
2 points, if the distance between them is between 0 units and 2 units, it will proceed to create a unit
circle.

However, the problem comes if the distance between the 2 points is more than 2 units in the
else clause, then it comes the question of what to create and return. We have to still return a

Circle object to satisfy the return type of the method, but should not even be returning a

Circle object as a unit circle could not be even created in the first place. Thus, we would try to

return the null object which has no reference to any object or value.

Including the keyword null to return a null object with no reference to any object will still enable
the code to compile. The program would work fine with normal inputs (distance between 0 and 2).
But if the distance between the 2 points passed as arguments to createUnitCircle() is more than 2,
we will return null instead of a unit circle. Sometimes we do not know whether a null object will be
returned, and we can unintentionally call methods on the null object returned. Should this happen,
a NullPointerException will be thrown and it forces the program to stop executing.

Java Optional (Maybe context)


Thus, we want to have an elegant way to handle null references and avoid NullPointerException s.
We can thus use the Optional context provided by java.

Week 7: The maybe context 1


Of course, Optional is also a class, for more information about Optional , refer to the link:

Java Development Kit Version 17 API Specification


declaration: module: java.base, package: java.util, class: Optional
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html

Since Optional is a context, it will serve as a wrapper class and will wrap around primitives or
references. Thus, under the documentation, it will have a generic type <T> . There are 3 main ways
to instantiate an Optional object:

jshell> Optional.<Integer>empty()
==> Optional.empty
jshell> Optional.<String>of("one")
==> Optional[one]
jshell> Optional.<Circle>.ofNullable(null)
==> Optional.empty

We can then replace the both the Circle and null object created with Optional objects:

Note that we will need to do an import of the java Optional object at the start of the method.

Now the return type will be Optional<Circle> , the program will still compile. Now, if the distance is
more than 2, an empty Optional<Circle> object will be returned, instead of a null object:

Week 7: The maybe context 2


However, we still cannot call an additional method, say contains() on the new Optional<Circle>
object as there is no contains() method defined under the Optional<Circle> class:

Thus, to remedy this issue, we will need to chain methods to the Optional context.

Chaining methods to a context


Since we cannot call contains() on an Optional<Circle> object, the general idea would be to pass
the contains() method into the Optional<Circle> . Thus, to do this, Optional<Circle> will need a
method to get the functionality of contains() . This method should then access the Circle object
wrapped inside the Optional<Circle> , apply the functionality to the Circle object produce an output,
and then wrap the output back into an Optional<T> .

This method to get the functionality of another method will be the map() method, which is a generic
method with the return type Optional<U> and a type declaration <U> .

We notice that the map() method actually takes in a functionality. There are several ways to
pass a functionality into the map() method:

Cross-Barrier manipulation

Anonymous inner class

Lambda expression

Week 7: The maybe context 3


As a method (functionality) can now be passed around, it behaves just like any other value or
object. It can be assigned to a variable, passed into another method as argument or returned from
another method (higher-order function). Thus this method will be regarded as a first-class citizen.

Cross-Barrier manipulation
This is the first way to pass a functionality into the map() method. This is where the client defines a
method via the creation of a Function object that implements from the Function interface. The
Function interface will have 2 generic types:

The first being the generic data type of the argument (input type) of the method with the desired
functionality (in this case contains() )

The second being the generic return type (output type) of the method with the desired
functionality (in this case contains() )

This concept is similar to the sort() method, where the functionality passed as an argument into
the sort() method in the form of a Comparator object that provides an implementation of the abstract
compare() method in the Comparator interface. Passing a functionality into a context for the context to

handle the method is called cross-barrier manipulation.

Similarly, we will need to create our own implementation of the abstract method in the Function

interface:

// Function interface will have the exact same generic input and output type as contains()
class F implements Function<Circle, Boolean> {
@Override
// The apply() method in contains would basically just apply the contains() method to the Circle object
public Boolean apply(Circle c) {
return c.contains(new Point(0.5, 0.5));
}
}

The abstract method specified in the Function interface will be apply() . The apply() method:

Has an input type of T , which will correspond to the input type of contains() method in this
case. This will in turn correspond to the first generic type of the Function interface.

Has a return type of R , which will correspond to the return type of contains() method, and
in turn correspond to the second generic type of the Function interface.

Week 7: The maybe context 4


After creating the Function class which implements the Function interface, we can then pass the
Function object as an argument into the map() method:

Now, if the distance is more than 2, calling the map() method that maps the functionality of
contains() to the Optional<Circle> will result in a few possible outputs:

If the distance between 2 points is more than 2, then a unit circle would not be created, but
the contains() method could still be mapped to the empty Optional object, giving an
Optional.empty object.

If the distance between 2 points is valid (between 0 and 2), a unit circle with type
Optional<Circle> would be created, and the contains() method will be mapped to the

Optional<Circle> object to give an Optional<Boolean> output.

Anonymous inner class


An anonymous inner class is very much similar to cross-barrier manipulation except that the
functionality is being stored as an anonymous class inside a variable rather than inside object
implementing an interface.

An anonymous inner class for the sort() method will be as follows:

Comparator<Integer> IntComp = new Comparator<Integer>() {


@Override
public int compare(Integer i, Integer j) {
return i - j;
}
}

Week 7: The maybe context 5


It stores the functionality in an IntComp variable without creating a concrete class. Thus, this
variable can be treated as a first-class citizen and passed readily into the sort()
IntComp

method. Note that since IntComp is a variable and not an object, there is no need to instantiate it
and we can avoid the new keyword when passing in the functionality.

When can now use anonymous inner class and lambda expressions to pass the functionality into
Optional<Circle> :

Assigning a functionality into the variable f

Passing the functionality stored in variable f into the map() function.

Lambda Expressions
Take the example of the sort() method again which takes in a Comparator object:

class IntComp implements Comparator<Integer> {


@Override
public int compare(Integer i, Integer j) {
return i - j;
}
}

We realise that the class name IntComp and the interface Comparator<Integer> is not very useful
as we would be eventually passing it as an argument into the sort() method, so it is inferred to
implement from a Comparator interface. So actually both need not be specified.

With the class name and interface omitted, we then move on to the method name compare() .
Here, we realise that since the Comparator interface is a functional interface or Single Abstract
Method interface (SAM) containing only 1 abstract method, the method name is no longer
important as there will be no ambiguity as to whether which method will be implemented. Thus,
method name also need not be specified.

Thus, this leaves us to only the arguments and the return statement. We can further cut the
method down. Since the generic type of the ImList or whichever iterable to be sorted will
already be specified upon initialisation, the data type of the arguments would be inferred as

Week 7: The maybe context 6


being the same as that of the generic type of the iterable (in this case it is <Integer> ). We can
then remove the data type of the arguments that is specified. Lastly, since the method body only
has a single return statement, we can omit the return keyword.

Finally, after trimming down the class, we can pass the functionality in the form of a lambda
expression directly as an argument into the sort() method:

class IntComp implements Comparator<Integer> {


@Override
public int compare(Integer i, Integer j) {
return j - i; // Sort in descending order
}
}

jshell> ImList<Integer> lst = new ImList<Integer>.of(1,2,3)


jshell> lst
==> [1,2,3]
jshell> lst.sort(new IntComp()) // Passing in a new Comparator object
==> [3,2,1]
jshell> lst.sort((i, j) -> j - i) // Passing in a lambda expression
==> [3,2,1]

Bringing this back to the example of createUnitCircle() :

In a lambda function, we will just need to provide the arguments and method body. In this case
the method body will just comprise of the return statement. The arguments will just be the
Circle object c and the method body will be the contains() method called on c , which will

return a boolean object which will eventually be wrapped into an Optional<Boolean> by the map()

method in the Optional context.

Computation context
A computation context:

Wraps around a value of either a primitive or reference type, and abstract away computations
handled by the context away from the client. Since a context wraps around a value, contexts will
always be of generic type <T> .

Has a higher order method to pass a functionality into the context. Usually, this method will take
the functionality in the form of either a concrete class (cross-border manipulation) or lambda
expression etc as argument and invoke it on the object wrapped by the context.

Week 7: The maybe context 7


Optional is a context which particularly handles invalid or missing values.

Other methods in Optional


These are some other higher order methods in the Optional context apart from map() .

Filter( ) method

Takes in a SAM interface Predicate as argument, which has a single abstract method test() .

Note that the return type of test() is boolean , this means that any implementation of the test()

method MUST contain a functionality (method) that returns a boolean .

Basically, the filter() method will check if an optional object satisfies the condition stated in the
test() method implemented by the Predicate object. If the optional object satisfies the condition in

the Predicate , then filter() would return the optional object itself. Otherwise, if:

The optional object does not satisfy the condition

The optional object is empty

Then, filter() will filter out the object from the optional object and return an empty optional, which
is Optional.empty .

This is an example of how to use filter() for the example of createUnitCircle() . ( test() method
need not be written out explicitly):

Week 7: The maybe context 8


In this case, the functionality, or more specifically the condition, is to check whether the
Optional<Circle> object passed into the filter() method contains the Point (0.5, 0.5) or not.
This condition is stored inside the variable pred with a generic type Predicate<Circle> . (Note that
for a Predicate object, you only need to specify the input type, and not the return type, as the
return type will ALWAYS be boolean .)

Since the Optional<Circle> object created in the 2nd statement does not contain the Point (0.5,
0.5), the filter() method will filter out the Circle object from Optional<Circle> and return an
Optional.empty .

The Last statement does will not create a Circle object to be wrapped in an Optional , does it
will create an Optional.empty . Thus, the filter() method will similarly return an Optional.empty .

or( ) method

Takes in a Supplier of an Optional of an object, Supplier<Optional<T>> . Supplier is a SAM interface


which has a single abstract method get() .

Basically, the or() method will check:

If the optional object is not empty (has another object wrapped in it), then or() will return the
optional object itself.

Otherwise, if the optional object is empty ( Optional.empty ), then or() will return the optional
object produced by the get() method implemented by the Supplier object.

Week 7: The maybe context 9


This is an example of how to use or() for the example of createUnitCircle() :

In this case, the variable stores a Supplier<Optional<Circle>> which contains an


supp

Optional<Circle> object with centre (0,0) and a radius of 1. This Optional<Circle> object can then

be accessed through the lambda expression, which has the same functionality of the get()
method.

Since the 2nd statement returns an Optional.empty , the or() method will then access and return
the Optional<Circle> object stored inside the Supplier. ( get() method for a Supplier will only be
invoked WHEN NEEDED.)

The last statement returns an Optional<Circle> object, thus or() will simply just return the
Optional<Circle> object itself.

ifPresent( ) method

Takes in a SAM interface Consumer as argument, which has a single abstract method accept() .

The ifPresent() method will check:

If the optional object is not empty (has another object wrapped in it), then ifPresent() will invoke
the functionality contained in the accept() method implemented by the Consumer object. Since
the return type of the accept() method is void , there will be nothing returned, and the
functionality would just be a side-effect (eg. printing something on the console).

Otherwise, if the optional object is empty ( Optional.empty ), then ifPresent() will do nothing (not
return anything and not print anything).

Week 7: The maybe context 10


This is an example of how to use ifPresent() for the example of createUnitCircle() :

In this case, the consumer variable stores the functionality of printing additional symbols in front
and behind of the String output of the Optional<Circle> object. Note that since the return type is
not Optional<Circle> but void , the Circle object inside the Optional<Circle> would not be
wrapped back into an Optional<Circle> after functionality is invoked, thus the output would not
have “optional[ ]”.

Since the 2nd statement returns an Optional.empty , the ifPresent() method will not perform any
function.

The last statement returns an Optional<Circle> object, thus ifPresent() will simply just return
print out the String output of the Circle object from Optional<Circle> together with additional
symbols at the front and back.

map() vs flatMap()
Recall that the map() function takes in an Optional<T> , extracts out the T object and performs a
functionality on T , and returns an object of type R for example. Then, map() will take the object of
type R and wrap it back into an Optional<R> .

However, if the functionality invoked on the object T extracted from Optional<T> actually returns a
Optional<R> instead of R (returns a context instead of a type), then map() will take this Optional<R>

and wrap it back to become an Optional<Optional<R>> . This results in a nested optional.

To prevent this form of nesting from occurring, we will use flatMap() instead of map() , as flatMap()
will flatten out any nesting in the context and return just the single-dimensional wrapped object
Optional<R> .

This is an example of how to use the flatMap() in createUnitCircle() :

Week 7: The maybe context 11


The contains method now returns an Optional<Boolean> instead of a boolean .

Thus, using map() will result in a nested optional, and we would use flatMap() to return a
single-dimensional Optional object.

Week 7: The maybe context 12

You might also like