LongInteger: unit tests.
LongInteger()
Let us create a testing class called LongIntegerTest
and make a unit test for the constructors:
L6
: the @Test
annotation indicates that this method is used for unit testing.
L7
: methods used for unit testing must be public
.
L8
: tests the default constructor
assertEquals()
: compares two parameters where the left parameter is the expected value and the right parameter is the actual value.
L12-15
: tests the constructor with a string parameter.
L18-19
: tests the copy constructor.
When should we use
import static
instead ofimport
?
When you run this test, you see a prompt similar to the followings:
multiply()
Let us define another method for testing the multiply()
method:
compareTo()
Let us define a method for testing the compareTo()
method:
assertTrue()
: passes if the parameter returns true.
Unit testing provides an effective way of ensuring the correctness of your program. Making a unit test for every method is standard practice, although this standard is often dismissed due to time pressure.
LongInteger: implementation.
We are going to create a class called inheriting SignedNumeral
that can store an indefinite size of an integer value beyond the such as int
and long
.
What is so special about primitive data types in Java?
Java SE provides a similar class called although the implementations of LongInteger
andBigInteger
are completely independent.
Let us declare the member field digits
that is an array of bytes holding the values of this integer:
L1
: LongInteger
is passed to specify the generic type T
in SignedNumeral
.
The i
'th dimension of digits
is the i
'th least significant digit in the integer such that the integer 12345
would be stored as digits = {5, 4, 3, 2, 1}
, which makes it convenient to implement the arithmetic methods, add()
and multiply()
.
Is the array of bytes the most efficient way of storing a long integer?
Let us define the following three constructors:
L2
: the default constructor that initializes this integer with 0
by calling the constructor in L20
.
L10
: a copy constructor that initializes this integer with n
.
super()
: calls the corresponding constructor in the super class, SignedNumeral
.
L20
: a constructor that initializes this integer with n
by passing it to the set()
method.
Do the constructors in
L10
andL20
call any constructor in the super class?
Can you call non-static methods or fields in the body of a static method?
The static
keyword must not be abused to quickly fix compile errors unless it is intended.
set()
Let us define the set()
method that takes a string and sets the sign and the value of this integer:
L1-7
: javadoc comments.
L10-11
: throws the NullPointerException
.
yield
: returns the value of this switch statement for the condition (introduced in Java 14).
L21-30
: sets the value of n
to this.digits
.
L23
: for-loop can handle multiple variables such as i
and j
.
L25-28
: throws the InvalidParameterException
if v
is not a digit.
L29
: stores the value in the reverse order.
add()
Let us override the add()
method that calls two helper methods:
L3-4
: adds n
to this integer that has the same sign by calling addSameSign()
.
L5-6
: adds n
to this integer that has a different sign by calling addDifferentSign()
.
The following shows an implementation of addSameSign()
based on the simple arithmetic:
L7-9
: creates the byte array result
by copying values in this integer.
L8
: the dimension of result
can be 1 more than m
after the addition.
L12-19
: adds n
to results
(if exists) from the least significant digit.
L15-18
: pass a carry to the next digit.
L22
: trims the most significant digit if it is 0
.
What are tradeoffs to make the size of
result
to bem
instead ofm+1
and vice versa?
In practice, addSameSign()
and addDifferentSign()
should be private. We made them protected for exercise purposes.
multiply()
Let us override the multiply()
method:
L4
: sets the sign after the multiplication.
L7-15
: multiplies n
to this integer:
L7
: the max-dimension of results
is digits.length + n.digits.length
.
L12-13
: pass a carry to the next digit.
L18-20
: trims the most significant digit iteratively if it is 0
.
L20
: ++m
increments m
before the comparison.
What is the worst-case complexity of the
multiply()
method?
main()
Can we define the main method in
LongInteger
instead without creatingLongIntegerRun
?
L2
: the parameter args
is passed from the command line.
Why does the main method need to be static?
This prints something like the following:
[
: one-dimensional array.
L
: the element of this array is an object.
java.lang.String
: the type of object.
d716361
: the hash code of this array in hexadecimal.
What is the hash code of an object?
How is the
Arrays.toString()
method implemented?
Since no argument is passed to the main method at the moment, this prints an empty array:
If you set the arguments to 123 -456
using the [Run - Edit Configurations - Program arguments]
setting, it prints the following array:
Given those two arguments, we can create two integers:
This prints something like the following, which are returned by a.toString()
:
How is the
toString()
method implemented in theObject
class?
toString()
To print a more readable representation, we need to override the toString()
method in LongInteger
:
What are the advantages of using
StringBuilder
instead of concatenating values with the+
operator as follows:
Given the overridden method, the above main method now prints the following:
What are the advantages of overriding
toString()
instead of creating a new method with the same code, and calling the new method to get the string representation ofLongInteger
?
compareTo()
L2
: LongInteger
is passed to Comparable
as a generic type.
Is
extends
always used to inherit a class whereasimplements
is used to inherit an interface?
The compareAbs()
method compares the absolute values of this
and n
:
L7
: if digits
has more dimensions, its absolute value is greater.
L10-13
: compares the significant digits iteratively.
Is it safe to use the same variable
i
to iterate bothdigits
andn.digits
?
Once LongInteger
properly inherits Comparable
by overriding compareTo()
, objects instantiated by this class can be compared using many built-in methods.
<>
: the diamond operator that infers the generic type from its declaration.
What is the advantage of declaring
list
asList
instead ofArrayList
? What kind of sorting algorithm doesCollections.sort()
use?
The above code prints the following sorted lists:
What would be the case that needs to distinguish
-0
from0
?
L11-12
: a
can hold the integer 152415787517146788750190521
(), which is much larger than the maximum value of long
that is .
: creates a new array by copying n.digits
.
Arrays.copyOf()
is a referenced by the class type Arrays
, not an object. Java provides many classes with static methods that are commonly used (e.g., , ).
L4
: this method throws if n
is null.
L5
: this method throws if the format of n
is invalid.
L14-18
: checks the first character of n
and sets this.sign
using the expression.
String
member methods: , .
L24
: gets the value of n.charAt(i)
.
L27
: is a static method in String
.
When should we use statements over blocks for error handling and vice versa?
This type of method is called a setter. Java encourages making member fields private and creating getters and setters to access the fields for , which is not necessarily encouraged by other languages.
Static methods: , .
The following shows addDifferentSign()
that throws :
The implementation of addDifferentSign()
is quite similar to addSameSign()
although it involves a few more logics. We will leave this as an .
Let us create a runnable class called that contains the main method:
Every object implicitly inherits that defines a few member methods including , which gets called automatically by the println()
method to retrieve the string representation of this object. We can use the helper method that gives a more readable representation:
L5
: provides an efficient way of concatenating different data types into one string.
Java does not allow , so it is not possible to use logical operators to compare the two integers above, a
and b
:
In fact, any object that is comparable must inherit the interface as follows:
The Comparable
interface contains one abstract method called that returns a negative value if this object is smaller than n
, a positive value if this object is greater than n
, and zero if this object equals to n
. The compareTo()
method must be overridden by the LongInteger
class:
L2
: is a specific implementation of the interface .
All collections in Java inheriting uses generics.
L11
: sorts the list in ascending order using and .
L14
: sorts the list in descending order using and .
Quiz 1: Java Essentials
Create a class called LongIntegerQuiz
under the main algebraic
package that extends the LongInteger
class.
Override the addDifferentSign()
method so that the add()
method in LongIntegerQuiz
can handle integers with different signs.
Create the LongIntegerQuizTest
class under the test algebraic
package.
Test the correctness of your LongIntegerQuiz
using the unit tests.
Add more tests for a more thorough assessment if necessary.
What is the advantage of using Generics?
How do you make the class you define comparable?
What is the advantage of overriding member methods in the Object class?
What kind of methods should be defined as static?
1. Commit and push everything under the following packages to your GitHub repository:
2. Submit answers to the above quizzes to Canvas.
This chapter explains essential object-oriented programming features in Java to implement data structures and algorithms.
Please follow every example described in this section. Programming is an act of writing, not reading. By the end of this chapter, you should be able to reproduce the entire codebase yourself from scratch without consulting those examples.
Different types of objects and inheritances in Java.
A class is a template to instantiate an object.
What is the relationship between a class and an object?
Let us create a class called Numeral
that we want to be a super class of all numeral types:
L1
: package
indicates the name of the package that this class belongs to in a hierarchy.
L3
: public
is an access-level modifier.
What are the acceptable access-level modifiers to declare a top-level class?
Let us declare a method, add()
, that is an operation expected by all numeral types:
L4
: @param
adds a javadoc comment about the parameter.
The issue is that we cannot define the methods unless we know what specific numeral type this class should implement; in other words, it is too abstract to define those methods. Thus, we need to declare Numeral
as a type of abstract class.
What are the advantages of having
Numeral
as a super class of all numeral types?
There are two types of abstract classes in Java, abstract class
and interface
.
Can an object be instantiated by an abstract class or an interface?
Let us define Numeral
as an interface:
L2
: abstract method
All methods in an interface are public
that does not need to be explicitly coded.
Abstract methods in an interface are declared without their bodies.
Who defines the bodies of the abstract methods?
Let us create a new interface called SignedNumeral
that inherits Numeral
and adds two methods, flipSign()
and subtract()
:
Can an interface inherit either an abstract class or a regular class?
L1
: extends
inherits exactly one class or interface.
L9
: default
allows an interface to define a method with its body (introduced in Java 8).
Can we call
add()
that is an abstract method without a body in the default methodsubtract()
?
Although the logic of subtract()
seems to be correct, n.flipSign()
gives a compile error because n
is a type of Numeral
that does not include flipSign()
, which is defined in SignedNumeral
that is a subclass of Numeral
.
What kind of a compile error does
n.flipSign()
cause?
There are three ways of handling this error: casting, polymorphism, and generics.
The first way is to downcast the type of n
to SignedNumeral
, which forces the compiler to think that n
can invoke the flipSign()
method:
This removes the compile error; however, it will likely cause a worse kind, a runtime error.
Why is a runtime error worse than a compile error?
Downcasting, although allowed in Java, is generally not recommended unless there is no other way of accomplishing the job without using it.
How can downcasting cause a runtime error in the above case?
The second way is to change the type of n
to SignedNumeral
in the parameter setting:
This seems to solve the issue. Then, what about add()
defined in Numeral
? Should we change its parameter type to SignedNumeral
as well?
It is often the case that you do not have access to change the code in a super class unless you are the author of it. Even if you are the author, changing the code in a super class is not recommended.
Why is it not recommended to change the code in a super class?
How about we override the add()
method as follows?
L2
: @Override
is a predefined annotation type to indicate the method is overridden.
The annotation @Override
gives an error in this case because it is not considered an overriding.
What are the criteria to override a method?
When @Override
is discarded, the error goes away and everything seems to be fine:
However, this is considered an overloading, which defines two separate methods for add()
, one taking n
as Numeral
and the other taking it as SignedNumeral
. Unfortunately, this would decrease the level of abstraction that we originally desired.
What are good use cases of method overriding and overloading?
The third way is to use generics, introduced in Java 5:
L1
: T
is a generic type that is a subtype of Numeral
.
A generic type can be recursively defined as T
extends Numeral<T>
.
L2
: T
is considered a viable type in this interface such that it can be used to declare add()
.
Can we define more than one generic type per interface or class?
The generic type T
can be specified in a subclass of Numeral
:
L1
: T
is specified as SignedNumeral
.
This would implicitly assign the parameter type of add()
as follows:
The issue is that the implementation of add()
may require specific features defined in the subclass that is not available in SignedNumeral
. Consider the following subclass inheriting SignedNumeral
:
L1
: implements
inherits multiple interfaces.
L2-6
: LongInteger
is a regular class, so all abstract methods declared in the super classes must be defined in this class.
Since the n
is typed to SignedNumeral
in L6
, it cannot call any method defined in LongInteger
, which leads to the same issue addressed in the casting section.
Would the type of
n
beingSignedNumeral
an issue for thesubtract()
method as well?
Thus, SignedNumeral
needs to define its own generic type and pass it onto Numeral
:
L1
: T
is a generic type inheriting SignedNumeral
, that implies all subclasses of SignedNumeral
.
T
can be safely passed onto Numeral
because if it is a subclass of SignedNumeral
, it must be a subclass of Numeral
, which is how T
is defined in the Numeral
class.
Generics are used everywhere in Java, so it is important to understand the core concept of generics and be able to adapt it in your code to make it more modular.
Let us create an enum class called Sign
to represent the "sign" of the numeral:
All items in an enum have the scope of static
and the access-level of public
.
Items must be delimited by ,
and ends with ;
.
The items in the enum can be assigned with specific values to make them more indicative (e.g., +
, -
):
L5
: final
makes this field a constant, not a variable, such that the value cannot be updated later.
L8
: this
points to the object created by this constructor.
L11
: @return
adds a javadoc comment about the return value of this method.
Why should the member field
value
beprivate
in the above example?
Note that value
in L8
indicates the local parameter declared in the constructor whereas value
in L13
indicates the member field declared in L5
.
In SignedNumeral
, it would be convenient to have a member field that indicates the sign of the numeral:
L2
: All member fields of an interface are static
and public
.
Can you declare a member field in an interface without assigning a value?
Given the sign
field, it may seem intuitive to define flipSign()
as a default method:
L3
: condition ?
A :
B is a ternary expression that returns A if the condition is true; otherwise, it returns B.
Is there any advantage of using a ternary operator instead of using a regular if statement?
Unfortunately, this gives a compile error because sign
is a constant whose value cannot be reassigned. An interface is not meant to define so many default methods, which were not even allowed before Java 8. For such explicit implementations, it is better to declare SignedNumeral
as an abstract class instead.
Let us turn SignedNumeral
into an abstract class:
L9
: the default constructor with no parameter.
L17
: another constructor with the sign
parameter.
L10
: this()
calls the constructor in L17
.
Why calling
this(Sign.POSITIVE)
inL10
instead of statingthis.sign = Sign.POSITIVE
?
L29
: abstract
indicates that this is an abstract method.
Member fields and methods in an abstract class can be decorated by any modifiers, which need to be explicitly coded.
Is there anything that is not allowed in an abstract class but allowed in a regular class?
In summary, SignedNumeral
includes 2 abstract methods, add()
inherited from Numeral
, and multiply()
declared in this class.
Can you define an abstract class or an interface without declaring an abstract method?