3. [TD]: Classes
Keywords: class, interface, inheritance, exception, polymorphism
Recommended reading:
- sections 2.1, 2.2, 2.4, and 2.7 of Chapter 2 of [ref1]: Classes and Interfaces
- sections 3.3 (String class), 3.5 (ArrayList class), 3.6 (Arrays class)
In Part 1 of the ELECTIONS exercise, no classes were used. We built a solution as we would have built it in the C language. We will now introduce the concept of Java classes.
3.1. Support
![]() | ![]() |
The folder [support / chap-03] contains the Eclipse project for this chapter.
We will now work with JDK 1.8 because some of the upcoming projects require this JDK. To determine which JDK is being used, proceed as follows:
![]() |
![]() |
- in [4], the JRE (Java Runtime Environment) being used. This JRE is actually a JDK (Java Development Kit) here, specifically [jdk1.8.0_60]. If it is not a JDK or if you have a version lower than 1.8, proceed as follows [5-21];
![]() |
- in [8], the JRE currently used by default by Eclipse;
- in [11], the various JDKs and JREs currently recognized by Eclipse;
![]() |
- in [15], choose a JDK rather than a JRE. This document uses Maven projects that require a JDK;
![]() |
![]() |
- in [21], we have a JDK version >=1.8;
- in [22-23], access the facets (different views of the same Eclipse project) of the project;
![]() |
- in [24], verify that you are using a Java version >=1.8;
3.2. The class [ ListeElectorale]
In C, we would probably have used a structure to represent a list of election candidates. It might have looked something like this:
The concept of a structure does not exist in the Java language. It must be replaced by that of a class. We therefore decide to create a class to store information about a list of candidates. This class would have the following skeleton:
package istia.st.elections;
public class VoterList {
/**
* list ID
*/
private int id;
/**
* list name
*/
private String name;
/**
* number of votes for the list
*/
private int votes;
/**
* number of seats on the list
*/
private int seats;
/**
* indicates whether the list is eliminated or not
*/
private boolean eliminated;
/**
* default constructor
*/
public VoterList() {
}
/**
*
* @param name String: the name of the list
* @param votes int: the number of votes
* @param seats int: the number of seats
* @param eliminated boolean: whether it has been eliminated or not
*/
public ElectoralList(int id, String name, int votes, int seats, boolean eliminated) {
...
}
/**
*
* @return int: the list's identifier
*/
public int getId() {
...
}
/**
* Initializes the list ID
* @param id int: list ID
* @throws ElectionsException if id<1
*/
public void setId(int id) {
...
}
/**
*
* @return String: the name of the list
*/
public String getName() {
...
}
/**
* initializes the name of the list
* @param name String: name of the list
* @throws ElectionsException if the name is empty or blank
*/
public void setName(String name) {
...
}
/**
*
* @return int: the number of votes for the list
*/
public int getVotes() {
...
}
/**
* initializes the number of votes in the list
* @param voices int: the number of voices in the list
*/
public void setVoices(int voices) {
...
}
/**
*
* @return int: the number of seats on the list
*/
public int getSeats() {
...
}
/**
* Sets the number of seats in the list
* @param seats int: the number of seats in the list
*/
public void setSeats(int seats) {
...
}
/**
*
* @return boolean: value of the removed field
*/
public boolean isDeleted() {
...
}
/**
*
* @param seats int
*/
public void setEliminated(boolean eliminated) {
...
}
/**
*
* @return String: voter registration ID
*/
public String toString() {
...
}
}
- line 8: a unique identifier for a list. Not essential here, but reserved for future use.
- line 13: the name of the list.
- line 17: the number of votes for the list
- line 21: the number of seats for the list
- line 25: a boolean indicating whether the list is eliminated (percentage of votes obtained below the electoral threshold) or not.
Each private field named [xyz] can be initialized using a method named [setXyz]. The [getXyz] method retrieves the value of the private field [xyz]. In the specific case where [xyz] is a Boolean field, the [getXyz] method can be replaced by the [isXyz] method. The specific naming of these methods follows a coding standard called the JavaBean standard. Thus, we define the following public methods:
- getId (line 48), setId (line 57)
- getName (line 65), setName (line 74)
- getVoice (line 82), setVoice (line 90)
- getSeats (line 98), setSeats (line 106)
- isEliminated (line 114), setEliminated (line 122)
- lines 30-31: define a constructor without parameters. This allows you to create a [VoterList] object without initializing it. It can then be initialized using the set methods.
- Lines 40–42: define a constructor that creates a [VoterList] object while initializing its five private fields.
- Lines 130–132: define the [toString] method, which returns a string containing the values of the object’s five fields.
A test program for the [VoterList] class could look like this:
package istia.st.elections.tests;
import istia.st.elections.VoterList;
public class MainTest1VoterList {
public static void main(String[] args) {
// Create an electoral list
VoterList voterList1 = new VoterList(1, "A", 32000,
0, false);
// display voter list
System.out.println("voterList1=" + voterList1);
// Change the number of seats
voterList1.setSeats(2);
// display list 1
System.out.println("voterList1=" + voterList1);
// a new electoral list
VoterList voterList2 = voterList1;
// Display the identity of list 2
System.out.println("voterList2=" + voterList2);
// change the number of seats
voterList2.setSeats(3);
// Display the contents of both lists
System.out.println("voterList2=" + voterList2);
System.out.println("voterList1=" + voterList1);
}
}
The Eclipse environment for this test could be as follows:
![]() |
- [1]: the project is named [elections-02A]
- [2]: The application will be placed in a package, here [istia.st.elections]
- [3]: [VoterList.java] is the source code for the [VoterList] class
- [4]: The test classes will be placed in a package, here [istia.st.elections.tests]
- [5]: the test class [MainTest1VoterList]
The screen display obtained after running the above program is as follows:

Task: Using the information above, complete the code for the VoterList class.
3.3. Creating an Exception Class [ElectionsException]
Among the various exception classes in the Java language, there is one called [RuntimeException]. This class derives from the [Exception] class, the root of all exception classes. The distinctive feature of [RuntimeException] instances or derived instances is that you are not required to declare or handle them. They are called uncaught exceptions.
Let’s look at a first example. The [BufferedReader] class is a class whose instances allow you to read lines of text from a data stream. It has a [readLine] method with the following signature:
We can see that the method can throw an [IOException]. The class hierarchy for this class is as follows:
The [IOException] class derives from the [Exception] class (line 3). The compiler requires us to handle and declare exceptions of type [java.lang.Exception] or derived types (except for the [RuntimeException] branch, which we will discuss later). Thus, to read a line of text typed on the keyboard, we must write something like:
Let's take another example. To convert a string to an integer, we can use the static method [Integer.parseInt], whose signature is as follows:
The argument [s] is the string to be converted to an integer. We can see that the method can throw a [NumberFormatException]. The class hierarchy for this class is as follows:
The [NumberFormatException] class extends the [RuntimeException] class (line 4). The compiler does not require us to handle or declare exceptions of type [java.lang.RuntimeException] or its subclasses. Thus, we can write something like:
We are not required to include a [try-catch] block to handle any exception generated by [Integer.parseInt] (line 9).
There are advantages and disadvantages to creating and using exception classes derived from [RuntimeException]:
- on the plus side: the code is more concise
- On the downside: we might end up resorting to C-style methods where each function returns an error code—a practice few people use, precisely to keep the code lightweight. When such an unhandled error occurs, the program crashes, usually in an inelegant way.
We have decided to create a special class that groups all exceptions that may occur in our ELECTIONS application. It will be called [ElectionsException] and will be derived from the [RuntimeException] class. Its code is as follows:
package istia.st.elections;
public class ElectionsException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ElectionsException() {
super();
}
public ElectionsException(String message) {
super(message);
}
public ElectionsException(Throwable cause) {
super(cause);
}
public ElectionsException(String message, Throwable cause) {
super(message, cause);
}
}
- Line 1: We place the class in the [istia.st.elections] package;
- line 3: the class extends [RuntimeException]. It is therefore unchecked;
- line 4: a serialization identifier that we can ignore for now;
- In our application, we will use two types of constructors:
- the classic one in lines 15–17, as shown below:
In this case, the method that calls a method throwing such an exception can handle it as follows:
// exception test
try {
voterList2.setSeats(-3);
} catch (ElectionsException ex) {
System.err.println("The following exception occurred: ["
+ ex.toString() + "]");
}
- (continued)
- or the one in lines 14–20, which is designed to propagate an exception that has already occurred by wrapping it in an [ElectionsException]:
try {
...;
} catch (SQLException ex) {
// encapsulate the exception
throw new ElectionsException("error closing the database connection", ex);
}
This second method has the advantage of preserving the information contained in the first exception. In this case, the method that calls a method throwing such an exception can handle it as follows:
try {
...;
} catch (ElectionsException ex) {
System.out.println(ex.getMessage() + ", Cause: " + ex.getCause().getMessage());
System.exit(1);
}
Task: Modify the code of the ListeElectorale class so that the set methods throw an [ElectionsException] if the requested initialization is incorrect, such as initializing the name with an empty string.
The Eclipse test project for this new version could be as follows:
![]() |
- [1]: the project is named [elections-02B]
- [2]: The application is placed in a package, here [istia.st.elections]
- [3]: the classes [VoterList] and [ElectionsException]
- [4]: the test classes are located in a package, here [istia.st.elections.tests]
- [5]: The test class [MainTest1VoterList]
The test class [MainTest1VoterList] discussed earlier has been slightly modified to test exception cases:
package istia.st.elections.tests;
import istia.st.elections.ElectionsException;
import istia.st.elections.VoterList;
public class MainTest1VoterList {
public static void main(String[] args) {
// Create an electoral roll
VoterList voterList1 = new VoterList(1, "A", 32000,
0, false);
// display voter list
System.out.println("voterList1=" + voterList1);
// Change the number of seats
voterList1.setSeats(2);
// display list 1
System.out.println("voterList1=" + voterList1);
// a new electoral list
VoterList voterList2 = voterList1;
// Display the identity of list 2
System.out.println("voterList2=" + voterList2);
// change the number of seats
voterList2.setSeats(3);
// Display the contents of both lists
System.out.println("voterList2=" + voterList2);
System.out.println("voterList1=" + voterList1);
// exception test
try {
voterList2.setSeats(-3);
} catch (ElectionsException ex) {
System.err.println("The following exception occurred: ["
+ ex.toString() + "]");
}
}
}
- line 28: we try to initialize the number of seats with an invalid value
- line 30: if an exception occurs, it is displayed
Running the test yields the following results:

Note that the [VoterList] class did indeed throw an exception when we attempted to initialize the number of seats with an invalid value (line 28 of the code).
3.4. A unit test class
The previous type of test relies on visual verification. We check that what is displayed on the screen matches what is expected. This method is not recommended in a professional setting. Tests should always be automated as much as possible and aim to require no human intervention. Humans are indeed prone to fatigue, and their ability to verify tests diminishes over the course of the day.
An application evolves over time. With each update, we must verify that the application does not "regress," i.e., that it continues to pass the functional tests that were performed during its initial development. These tests are called "non-regression" tests. A moderately large application may require hundreds of tests. In fact, every method in every class of the application is tested. These are called unit tests. They can tie up a lot of developers if they haven’t been automated.
Tools have been developed to automate testing. One of them is called [JUnit]. It is a library of classes designed to manage tests. We will use this tool to test the [VoterList] class.
A JUnit test program (version 4.x) has the following form:
package istia.st.elections.tests;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class JUnitTest {
@Before
public void before() throws Exception {
System.out.println("tearUp");
}
@After
public void after() throws Exception {
System.out.println("tearDown");
}
@Test
public void t1() {
System.out.println("test1");
Assert.assertEquals(1, 1);
}
@Test
public void t2() {
System.out.println("test2");
Assert.assertEquals(1, 2);
}
}
- line 1: the class has been placed in the [istia.st.elections.tests] package;
- line 11: the method annotated with the [@Before] annotation is executed before each unit test;
- line 16: the method annotated with the [@After] annotation is executed after each unit test;
- line 21: a method annotated with the [@Test] annotation is a method tested by the unit test. Methods annotated with [@Test] will be executed one after another, unless otherwise specified by the tester, who can select the methods to be tested themselves. Before each execution of a [@Test] method, the [@Before] method is executed. After each execution of a [@Test] method, the [@After] method is executed;
- lines 22–25: define a [t1] test method;
- Line 18: One of the [Assert.assert*] methods used to check assertions. The following [assert] methods are available:
- assertEquals(expression1, expression2): checks that the values of the two expressions are equal. Many types of expressions are accepted (int, String, float, double, boolean, char, short). If the two expressions are not equal, then an [AssertionFailedError] exception is thrown,
- assertEquals(real1, real2, delta): checks that two real numbers are equal to within delta, i.e., abs(real1-real2) <= delta. For example, one could write assertEquals(real1, real2, 1E-6) to verify that two values are equal to within 10⁻⁶,
- assertEquals(message, expression1, expression2) and assertEquals(message, real1, real2, delta) are variants that allow you to specify the error message to be associated with the [AssertionFailedError] exception thrown when the [assertEquals] method fails,
- assertNotNull(Object) and assertNotNull(message, Object): checks that Object is not equal to null,
- assertNull(Object) and assertNull(message, Object): checks that Object is equal to null,
- assertSame(Object1, Object2) and assertSame(message, Object1, Object2): checks that the references Object1 and Object2 point to the same object,
- assertNotSame(Object1, Object2) and assertNotSame(message, Object1, Object2): checks that the references Object1 and Object2 do not point to the same object;
- line 24: this assertion must pass;
- line 30: this assertion must fail;
In the Eclipse environment, a JUnit test class can be created as follows:
![]() |
- [1]: right-click on the package where you want to add the test class, then select [JUnit / New / JUnit Test Case]
![]() |
- [1]: Select a JUnit version;
- [2]: Select the folder where the test class should be created;
- [3]: Select the package in which the test class should be created;
- [4]: Enter the name of the test class;
- [5]: Select the methods to include in the class that will be generated;
- [6]: The JUnitEssai class has been generated
The previous wizard generates a nearly empty class:
package istia.st.elections.tests;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
public class JUnitTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
}
Let's complete and modify the previous code as follows:
package istia.st.elections.tests;
import org.junit.Assert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class JUnitTest2 {
@Before
public void before() throws Exception {
System.out.println("tearUp");
}
@After
public void after() throws Exception {
System.out.println("tearDown");
}
@Test
public void t1() {
System.out.println("test1");
Assert.assertEquals(1, 1);
}
@Test
public void t2() {
System.out.println("test2");
Assert.assertEquals(1, 2);
}
}
In Eclipse, right-click on the test class, then select [Run as / JUnit test] to run it:

The results obtained when running this test are as follows:

Above, the [test2] method failed. Whenever a test fails, an error message is associated with it. For [test2], it is the one shown above. The message indicates the line number where the error occurred (line 30). On line 30, the call that failed was:
Assert.assertEquals(1, 2);
The first parameter is called the expected value, the second the actual value. The error message for [test2] above indicates that the expected value was 2 but the actual value was 3.
Finally, the messages written to the console by the various test methods were as follows:

These messages show that the [@Before] and [@After] methods were indeed called, respectively, before and after each test method.
Test classes are not necessarily written by the developers themselves. They may be written by the people who wrote the application specifications. Some development methods known as TDD (Test-Driven Development) advocate writing test classes even before writing the classes to be tested. This sometimes helps clarify specifications that might otherwise be interpreted in multiple ways.
Let’s create a JUnit 4 test, named [JUnitTest1VoterList], for the [VoterList] class. In Eclipse, we’ll proceed as described earlier:
![]() | ![]() |
We complete the code generated by the wizard as follows:
package istia.st.elections.tests;
import org.junit.Assert;
import istia.st.elections.ElectionsException;
import istia.st.elections.VoterList;
import org.junit.Test;
public class JUnitTest1VoterList {
@Test
public void t1() {
// Create voter list
VoterList voterList = new VoterList(1, "a", 32000, 0, false);
// checks
Assert.assertEquals("a", list.getName());
Assert.assertEquals(32000, list.getVotes());
Assert.assertEquals(false, list.isEliminated());
Assert.assertEquals(0, list.getSeats());
// ID validity check
boolean error = false;
try {
list.setId(-4);
} catch (ElectionsException e) {
error = true;
}
Assert.assertEquals(true, error);
// Check name validity
error = false;
try {
list.setName("");
} catch (ElectionsException e) {
error = true;
}
Assert.assertEquals(true, error);
// Check vote validity
error = false;
try {
list.setVotes(-4);
} catch (ElectionsException e) {
error = true;
}
Assert.assertEquals(true, error);
// Check seat validity
error = false;
try {
list.setSeats(-4);
} catch (ElectionsException e) {
error = true;
}
Assert.assertEquals(true, error);
}
}
Running the test yields the following result:

The tests passed. We will now consider the [VoterList] class to be operational.
3.5. MainElections: version 2
Recommended reading:
- Sections 2.1, 2.2, 2.4, and 2.7 of Chapter 2 in [1]: Classes and Interfaces
- sections 3.3 (String class), 3.5 (ArrayList class), 3.6 (Arrays class)
We want to rewrite the [Elections] application by adding the following new constraints:
- We will use the [VoterList] class to represent a list of candidates
- the application will prompt the user for the following information:
- the number of seats to be filled
- the names and votes for the lists. We do not know in advance how many lists there are. The last list will be indicated by a name equal to the string "*".
- Since we do not know in advance the number of lists, they will first be stored in an [ArrayList] object. Then, once all lists have been entered, they will be transferred to an array of lists.
- The results will be displayed in descending order of the number of seats obtained.
To sort an array T, we can use various static methods of the [Arrays] class:
- Arrays.sort(T): sorts the array T in a natural order if it has one (ascending for numbers, dates, alphabetical for strings, etc.)
- Arrays.sort(T,comparator): to sort arrays T that do not have a natural order. This is the case here with the array of lists, which must be sorted according to a specific field of the list: the number of seats obtained.
In the Arrays.sort(T,comparator) method, the comparator parameter is an object that implements the following Comparator interface:

- The compare method allows comparing two elements of the array T
- The equals method determines whether two objects are equal
Both methods compare Object types obj1 and obj2. Determining whether obj1<obj2, obj1=obj2, or obj1>obj2 depends on the ordering relationship we wish to establish between the two objects. It is up to the developer implementing this interface to specify how we know that:
- obj1 is smaller than obj2
- obj1 is greater than obj2
- obj1 is equal to obj2
The Object class, from which all Java classes derive, already has an [equals] method. To sort an array T of objects of type O, the [equals] method of class O is not useful. We can therefore leave the default implementation provided by the Object class. Only the [compare] method must then be implemented. This method is called repeatedly by the [Arrays.sort] method. Each time, [Arrays.sort] passes obj1 and obj2—two elements of the array T to be sorted—as parameters to the compare method. In our case, these elements will be of type [VoterList]. Note the polymorphism at work here. The [compare] method is defined to accept parameters of type [Object]. This means it can accept parameters of type [Object] or any derived type (polymorphism). Since [Object] is the parent class of all Java classes, the actual parameters can be of type [VoterList].
For sorting in ascending order, the [compare] method must return:
- -1 if obj1 is smaller than obj2
- +1 if obj1 is greater than obj2
- 0 if obj1 is equal to obj2
For sorting in descending order, the values +1 and -1 are reversed. The terms "is smaller than," "is larger than," and "is equal to" express an order relation. For objects of type [VoterList], the relation list1 "is smaller than" list2 holds if list1 has fewer votes than list2.
In the same source file as the [MainElections] class, we can add a second class:
- Line 2: The class is not declared as public. In a Java source file, there can be multiple classes, but only one can have the public attribute—the one with the same name as the source file.
In the previous compare method, the parameters are of type Object, which requires lines 7 and 8 to cast the method parameters from type Object to type VoterList. The signature of the compare method is imposed by the Comparator interface, which was written to compare arbitrary objects. Since the JDK 1.5 , there is a generic Comparator interface: Comparator<T>, where T is any Java type. The compare method of the Comparator<T> interface compares objects of type T rather than of type Object, which avoids the previous type casts. The comparison class for objects of type VoterList might look like this:
// class for comparing electoral lists
class CompareVoterLists implements Comparator<VoterList> {
// comparison of two electoral lists based on the number of seats
public int compare(VoterList voterList1,
VoterList voterList2) {
...
}
}
- line 2: the class implements the Comparator<VoterList> interface
- lines 5-6: the parameters of the compare method are of type VoterList. Type casting is no longer necessary.
JDK 1.5 introduced the concept of generic classes/interfaces for various classes/interfaces in JDK 1.4 that initially handled only objects of type Object. This is the case for lists, dictionaries, ...
We mentioned earlier that, because we didn’t know the number of lists, we couldn’t store them in an array. They can be stored in an ArrayList object, which implements the concept of an “object list.” This class stores objects of type Object. Since JDK 1.5, there have been typed object lists. Thus, we will use an ArrayList<VoterList> object to store the lists before transferring them to an array. If the array is named tLists, it will be sorted using the following statement:
// Sort the lists
Arrays.sort(tLists, new CompareVoterLists());
where CompareVoterLists is the class implementing the Comparator<VoterList> interface.
Task: Rewrite the [Elections] application taking these new specifications into account.
The Eclipse project could look like this:
![]() |
An example of the execution of [1] is as follows:
















