Exercise 4: Box
- Deadline: 12 March, 2024, Tuesday, 23:59, SGT
- Difficulty Level: 3
Prerequisite:
- Caught up to Unit 26 of Lecture Notes
- Familiar with CS2030S Java style guide
A Box
In this exercise, we are going to build our own generic wrapper class, a Box<T>
. This is a wrapper class that can be used to store an item of any reference type. For this lab, our Box<T>
is not going to be a very useful abstraction. Not to worry. we will slowly add more functionalities to it later in this module.
In the following, we will slowly build up the Box<T>
class along with some additional interfaces. We suggest that you develop your class step-by-step in the order below.
The Basics
Build a generic class Box<T>
that
-
contains a
private final
field of typeT
to store the content of the box. -
overrides the
equals
method fromObject
to compare if two boxes are the same. Two boxes are the same if the content of the box equals each other, as decided by their respectiveequals
method. -
overrides the
toString
method so it returns the string representation of its content, between[
and]
. -
provides a class method called
of
that returns a box with a given object. Ifnull
is passed intoof
, then anull
should be returned.
The method of
is called a factory method. A factory method is a method provided by a class for the creation of an instance of the class. Using a public constructor to create an instance necessitates calling new
and allocating a new object on the heap every time. A factory method, on the other hand, allows the flexibility of reusing the same instance. The of
method does not reuse instances. You will write another one that reuses available instances in the next section.
With the availability of the of
factory method, Box<T>
should keep the constructor private.
The sequence below shows how we can use a Box
using the methods you developed above.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
You can test your Box<T>
more comprehensively by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test1.java
and all tests should print ok
.
An Empty Box
The of
method returns a null
if it is given a null
. An alternative (some might say, cleaner) design is to make our factory method return an empty box instead if we try to create a box of null
.
Add a class method in Box
called empty()
that creates and returns an empty box, i.e., a box with a null
item stored in it.
Since empty boxes are likely common, we want to cache and reuse the empty box, that is, create one as a private final class field called EMPTY_BOX
, and whenever we need to return an empty box, EMPTY_BOX
is returned.
What should the type of EMPTY_BOX
be? The type should be general enough to hold a box of any type (Box<Shop>
, Box<Circle>
, etc). EMPTY_BOX
should, therefore, be assigned the most general generic Box<T>
type. Hint: It is not Box<Object>
.
Your method empty()
should do nothing more than to type-cast EMPTY_BOX
to the correct type (i.e., to Box<T>
) before returning, to ensure type consistency.
If you find yourself in a situation where the compiler generates an unchecked type warning, but you are sure that your code is type-safe, you can use @SuppressWarnings("unchecked")
(responsibly) to suppress the warning.
Add a boolean method isPresent
that returns true
if the box contains something; false
if the box is empty.
Finally, add a class factory method called ofNullable
, which behaves just like of
if the input is non-null, and returns an empty box if the input is null
.
Here is how the Box
class can be used with the added methods above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
You can test the additions to Box<T>
above more comprehensively by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test2.java
and all tests should print ok
.
Checking the Content of the Box
So far, we can only keep things inside our Box
, which is not very exciting. In the rest of the lab, we will expand Box
to support operations on the content inside.
Let's start by writing a generic interface called BooleanCondition<T>
with a single abstract boolean method test
. The method test
should take a single argument of type T
.
Now, one can create a variety of classes by implementing this interface. By implementing the method test
differently, we can create different conditions and check if the item contained in the box satisfies a given condition or not.
Create a method filter
in Box
that takes in a BooleanCondition
as a parameter. The method filter
should return an empty box if the item in the box failed the test (i.e., the call to test
returns false
). Otherwise, filter
leaves the box untouched and returns the box as it is. Calling filter
on an empty box just returns an empty box.
Here is an example of how BooleanCondition<T>
can be used with Box<T>
. Note that we make use of the class Number
, a superclass of Integer
, below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
You can test the additions to Box<T>
above more comprehensively by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test3.java
and all tests should print ok
.
Implement Your Own Conditions
The test cases above show you how you could create a class that implements a BooleanCondition
. Now you should implement your own.
Create a class called DivisibleBy
that implements BooleanCondition
on Integer
that checks if a given integer is divisible by another integer. The test
method should return true
if it is divisible; return false
otherwise.
Create another class called LongerThan
that implements BooleanCondition
on String
that checks if a given string is longer than a given limit. The test
method should return true
if it is longer; return false
otherwise.
Here is how it should work:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
On the other hand, calling
1 |
|
should result in a compilation error.
You can test your additions to Box<T>
more comprehensively by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test4.java
and all tests should print ok
.
Transforming a Box
Now, we are going to write an interface (along with its implementations) and a method in Box that allows a box to be transformed into another box, possibly containing a different type.
First, create an interface called Transformer<T, U>
with an abstract method called transform
that takes in an argument of generic type T
and returns a value of generic type U
.
Write a method called map
in the class Box
that takes in a Transformer
, and use the given Transformer
to transform the box (and the value inside) into another box of type Box<U>
. Calling map
on an empty box should just return an empty box.
In addition, implement your own Transformer
in a non-generic class called LastDigitsOfHashCode
to transform the content of the box into a box of integer, the value of which is the last \(k\) digits of the value returned by calling hashCode()
on the content of the original box (ignoring the positive/negative sign and leading zeros). The value \(k\) is passed in through the object of LastDigitsOfHashCode
. The method hashCode()
is defined in the class Object
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
You can test your additions to Box<T>
more comprehensively
by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test5.java
and all tests should print ok
.
Box in a Box
The Transformer
interface allows us to transform the content of the box from one type into any other type, including a box! You have seen examples above where we have a box inside a box: Box.of(Box.of(0))
.
Now, implement your own Transformer
in a class called BoxIt<T>
to transform an item into a box containing the item. The corresponding type T
is transformed into Box<T>
. This transformer, when invoked with map
, results in a new box within the box.
1 2 3 4 5 6 7 |
|
You can test your Box<T>
by running:
1 2 |
|
There shouldn't be any compilation warning or error when you compile Test6.java
and all tests should print ok
.
Files
A set of empty files has been given to you. You should only edit these files. You must not add any additional files.
The files Test1.java
, Test2.java
, etc., as well as CS2030STest.java
, are provided for testing. You can edit them to add your own test cases, but they will not be submitted.
Following CS2030S Style Guide
You should make sure that your code follows the given Java style guide
To check for style,
1 |
|
(You may copy ex3_style.xml
from Exercise 3 if needed)
@SuppressWarnings
should be used in at most two places mentioned above.