Professional Documents
Culture Documents
Week 7 Maybe Context
Week 7 Maybe Context
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.
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:
Thus, to remedy this issue, we will need to chain methods to the Optional context.
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
Lambda expression
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
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.
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
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> :
Lambda Expressions
Take the example of the sort() method again which takes in a Comparator object:
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
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:
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()
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.
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()
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:
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):
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
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.
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() .
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).
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>
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> .
Thus, using map() will result in a nested optional, and we would use flatMap() to return a
single-dimensional Optional object.