We will use the ClockTime
example. All the source code for
this part of the lecture can be found here.
We would like to validate the values passed to ClockTime
's
constructor. We expect the value given for hours to be between 0 and 23.
Similarly we expect the value given for minutes to be between 0 and 59.
We would like to check for this conditions inside the constructor method
and raise an exception when these conditions are not met.
So we would like to have something like the following skeleton
... public ClockTime(int hours, int minutes){ if // hours are valid? this.hours=hours; else // exception, the value given for hours is not valid if // minutes are valid? this.minutes=minutes; else // exception, the value given for minutes is not valid } ...
Let's start with the parts we already know how to do; we will write 2 methods one for checking hours and one for checking minutes.
// to verify that hours is between 0 and 23 private boolean validHours(int hours){ return hours >= 0 && hours < 24; } // to verify that minutes is between 0 and 59 private boolean validMinutes(int minutes){ return minutes >= 0 && minutes < 60; }
The preceding method definitions are instance
methods. Methods that can be called on an instance (object)
of this class. But we want to use these methods inside the
constructor. We do not have an instance yet. Notice that for the
preceding two methods we do not need this
at all. We
can make these methods static methods since they do not depend
on instance information (i.e. this
).
// to verify that hours is between 0 and 23 private static boolean validHours(int hours){ return hours >= 0 && hours < 24; } // to verify that minutes is between 0 and 59 private static boolean validMinutes(int minutes){ return minutes >= 0 && minutes < 60; }
The keyword static
after the modifier
public
indicates that this method is a static
method. We can now use this method inside our constructor to check
the values given for hours and minutes.
... public ClockTime(int hours, int minutes){ if (this.validHours(hours)) this.hours=hours; else // exception, the value given for hours is not valid if (this.validMinutes(minutes)) this.minutes=minutes; else // exception, the value given for minutes is not valid } ...
If the values given to hours and minutes are within the appropriate ranges then we go ahead and assign these values to the class' fields. But what do we do when the values given are not within the appropriate ranges? Java provides a mechanism for exceptions. Exceptions in Java are objects as well. They have however a special meaning and we can perform special operations on exceptions. One such special operation is the ability to throw an exception. Throwing an exception in Java is a way of indicating that something unexpected happened and we are going to exit from our current computation.
public ClockTime(int hours, int minutes){ if (this.validHours(hours)) this.hours=hours; else throw new RuntimeException("Invalid hours: " + hours); if (this.validMinutes(minutes)) this.minutes=minutes; else throw new RuntimeException("Invalid minutes " + minutes); }
A RuntimeException
in Java stops the
program's execution and reports the message inside the
RuntimeException
instance. In our example, when the
value given for hours
is not within 0 and 23 then
we create a new RuntimeException
instance with the
message "Invalid hours " + hours
. This creates a
string with the erroneous value for hours. The throw
keyword causes the execution of the program to stop and passes the
instance of RuntimeException
that we just created to
the Java runtime.
Here is what you get after running ClockTimeTest
. One
of our tests causes an exception to be thrown.
Exception in thread "main" java.lang.RuntimeException: Invalid minutes 77 at ClockTime.(ClockTime.java:12) at ClockTimeTest.runTests(ClockTimeTest.java:23) at ClockTimeTest.main(ClockTimeTest.java:29)
All the code for this part of the lecture can be found here.
In Java we can specify which other classes (and objects) can have
access to methods and/or fields of our class. We will look at
two Java modifiers; public
and private
.
Recall the class Rat
from assignment 4. A rat has a
location and a lifespan. After each round a rat's lifespan goes down
by one. A rat can increase its lifespan by consuming food. There
should be no other way of changing a rat's lifespan. The rat class
should have sole control of how to manipulate the rat's lifespan.
/* +------------------+ | Rat | +------------------+ | int lifespan | | Posn loc | +------------------+ | Rat starve() | | Rat eat() | | bool isDead() | +------------------+ */ // to represent a Rat public class Rat{ private int lifespan; private Posn loc; public Rat(Posn loc){ this(loc,10); } private Rat(Posn loc, int lifespan){ this.loc = loc; this.lifespan = lifespan; } // produce a hungrier rat. public Rat starve(){ return new Rat(this.loc,this.lifespan-1); } // produce a less hungrier rat. public Rat eat(int foodsize){ return new Rat(this.loc, this.lifespan + foodsize); } // the rat is dead when lifespan is 0 public boolean isDead(){ return lifespan == 0; } }
Notice that we have two constructors for Rat
. One is
public
and the other is private
. The
public and private modifiers can be used for a field or method. A public
field/method is visible to other classes. A private field/method
is only accessible to the class defining the field or method.
The Rat class has two constructors. One is public and one is private. Notice how the public constructor consumes only one argument; the Rat's location. The private constructor consumes a location and a lifespan. Other classes can only use the public constructor. Thus they can only create instances of rats that start with a lifespan of 10 units. The only class that can create rats with arbitrary lifespan is the class rat. This stops other classes from being able to create rats with arbitrary lifespan and gives us one point of control for the creation of rats.
All the code for this part of the lecture can be found here.
Lets try this out for our Temperature
class.
public class Temperature { private double deg; // Create a Temperature given degrees in Fahrenheit public Temperature(int degF){ this.deg = (degF - 32.0)/1.8; } public Temperature(int deg, char c) { this.deg = deg; } //produce temperature in Fahrenheit public double getF(){ return (this.deg * 1.8) + 32.0; } //produce temperature in Celcius public double getC() { return this.deg; } public int compareTo(Temperature t){ return (int)this.deg - (int)t.deg; } public String toString(){ return new String("Temperature: " + this.deg + "\n"); } }
Note: You have already used static methods (or
otherwise known as class
methods). Recall Math.sqrt(x)
where x
is an int
. The method sqrt
is a class
method.