Lab 5
Goals:
To learn to design constructors that provide better user interface and at the same time assure the integrity of the data that the instances of the class represent.
We will also learn how the programmer can signal an error in the program by throwing an Exception.
1 Understanding Constructors
Start a new project named Lab5-Date and import into it the file Date.java.
Standard Java asks that programmers define every public class in a file with the same name as the name of the class, followed by .java. We will now follow this convention.
Define a new file in your project (in menu New -> File) and name it ExamplesDate.java. As you can guess, this will be the place for your examples and tests. Download the latest version of the tester library and add it to your project.
Note: We expect this to be the last major change to the tester library for the forseeable future, definitely until next April.
Add at least three examples of valid dates to this class, and set up a Run configuration to run it.
1.1 Overloading Constructors: Assuring Data Integrity.
The data definitions at times do not capture the meaning of data and the restrictions on what values can be used to initialize different fields. For example, if we have a class that represents a date in the calendar using three integers for the day, month, and year, we know that our program is interested only in some years (maybe between the years 1500 and 2500), the month must be between 1 and 12, and the day must be between 1 and 31 (though there are additional restrictions on the day, depending on the month and whether we are in a leap year).
Suppose we make the following Date examples:
// Good dates |
Date d20100228 = new Date(2010, 2, 28); // Feb 28, 2010 |
Date d20091012 = new Date(2009, 10, 12); // Oct 12, 2009 |
|
// Bad date |
Date dn303323 = new Date(-30, 33, 23); |
Of course, the third example is just nonsense. While complete validation of dates (months, leap-years, etc...) is a course material itself, for the purposes of practicing constructors, we will simply make sure that the month is between 1 and 12, the day is between 1 and 31, and the year is between 1500 and 50000 we’re thinking ahead!
Did you notice the repetition in the description of validity? It suggests we start with a few helper methods (an early abstraction):
Design the method validNumber that consumes a number and the low and high bound and returns true if the number is within the bounds (inclusive on the low end, exclusive on the high end).
Design the methods validDay, validMonth, and validYear designed in a similar manner.
Do this quickly - do not spend much time on it - maybe do just the method validDay and leave the rest for later - for now just returning true regardless of the input. (Such temporary method bodies are called stubs, their goal is to make the rest of program design possible.)
Now change the Date constructor to the following:
Date(int year, int month, int day){ |
if(this.validYear(year)) |
this.year = year; |
else |
throw new IllegalArgumentException("Invalid year"); |
|
if(this.validMonth(month)) |
this.month = month; |
else |
throw new IllegalArgumentException("Invalid month"); |
|
if(this.validDay(day)) |
this.day = day; |
else |
throw new IllegalArgumentException("Invalid day"); |
} |
This is similar to the constructors for the Time class we saw in lectures. To signal an error or some other exceptional condition, we throw an instance of the RuntimeException. We elected to use the instance of the IllegalArgumentException, which is a subclass of the abstract class RuntimeException.
If the program ever executes a statement like:
throw new ...Exception("... message ...");
Java stops the program and signals the error throught the constructed instance of the Exception. For our purposes now, this is as good as terminating the program and printing the message String.
The tester library provides methods to test constructors that should throw exceptions:
boolean t.checkConstructorException(Exception e, |
String className, |
... constr args ...); |
For example, the following test case verifies that our constructor throws the correct exception with the expected message, if the supplied year is 53000:
t.checkConstructorException( |
// the expected exception |
new IllegalArgumentException("Invalid year"), |
|
// the class whose constructor we invoke |
"Date", |
|
// the argument for the constructor |
53000, 12, 30); |
Run your program with this test. Now change the test by providing an incorrect message, incorrect exception (e.g. NoSuchElementException), or by supplying arguments that do not cause an error, and see that the test(s) fail.
Java provides the class RuntimeException with a number of subclasses that can be used to signal different types of dynamic errors. Later we will learn how to handle errors and design new subclasses of the class RuntimeException to signal errors specific to our programs.
1.2 Overloading Constructors: Providing Defaults.
When entering dates for the current year it is tedious to continually enter 2012. We can provide an additional constructor that only requires the month and day, assuming the year should be 2012.
Remembering the single point of control rule, we make sure that the new overloaded constructor defers all of the work to the primary full constructor:
Date(int month, int day){ |
this(2012, month, day); |
} |
Add examples that use only the month and day to see that the constructor works properly. Include tests with invalid month or year as well.
1.3 Overloading Constructors: Expanding Options.
The user may want to enter the date in the form: Oct 20 2012. To make this possible, we can add another constructor:
Date(String month, int day, int year){ |
... |
} |
Our first task is to convert a String that represents a month into a number. We can do it in a helper method getMonthNo:
// Convert a three letter month into the numeric value |
int getMonthNo(String month){ |
if(month.equals("Jan")){ return 1; } |
else if (month.equals("Feb")){ return 2; } |
else if (month.equals("Mar")){ return 3; } |
else if (month.equals("Apr")){ return 4; } |
... |
else |
throw new IllegalArgumentException("Invalid month"); |
} |
Our constructor can then invoke this method as follows:
Date(String month, int day, int year){ |
// Invoke the prinmary constructor, with a valid month |
this(year, 1, day); |
|
// Change the month to the given one |
this.month = this.getMonthNo(month); |
} |
Complete the implementation, and check that it works correctly.