We will use this example to review what classes are and how instances of those classes are constructed and manipulated.
In order to have a bankAccount class, we 1st going to develop a
class named Balance. This class will help us control access to
the amount of balance that we will be part of our bankAccount class later.
Here is what it would look like:
public class Balance {
private double amt;
public Balance() {amt=0;}
public void add(double a) {amt+=a;}
public void subtract(double a) throws negativeBalanceException {
if (a>amt)
throw new negativeBalanceException ();
amt -=a;
}
public double balance() {return amt;}
The qualifier public means that class Balance, as well as, its methods:add, subtract, and balance are accessible outside of this class (we'll see what this means later). the word private however implies that amt is not accessible outside of this class.
The subtract method throws an exception if asked to remove more that
what is actually the amount stored. Exceptions like negativeBalanceException
are thrown when abnormal situations occur. In this case, the statement
amt -= a;
won't execute if a is too large. More on
exceptions later.
Now, lets look at the bankAccount class:
public class bankAccount {
private int accountNumber;
private String name;
private Balance b;
public bankAccount(int an, String n) {
accountNumber=an;
name=n;
b= new Balance();
}
public void deposit (double am) {
b.add(am);
}
public void withdrawal (double am) throws negativeBalanceException {
b.subtract(am);
}
public double balance() {return b.balance();}
}
The bankAccount class has three fields/variables: accountNumber, name, and b. The constructor has two parameters, an and n. The fields accountNumber and name are set to an and n, respectively. The balance filed b, is constructed as well. The deposit method simply calls the method for b. The withdrawal method also simply calls the subtract method for b. Notice that the negativeBalanceException is not dealt with in the withdrawal method; this simply means that this exception will be propagated to the caller. The balance method is designed to return b's balance.
The program below demonstrates how we create a bankAccount object and its usage in an application; it also demonstrates how exceptions are handled.
This program simply creates a bankAccount object and deposits 100.0 in it,
then it withdraws 10.0 and finally displays the balance.
The try/catch is needed to deal with the exception that could be thrown.
With $100.0 in the bank account, we don't expect an exception in
when take out $10; however, we must invoke the withdrawal in a
try/catch as Java requires you to do so. More on exception handling
later.
import java.io.*;
import java.lang.*;
//Testing bankAccount
public class testBankAccount {
public static void main(String[] argn) {
String n = new String ("John Doe");
bankAccount b = new bankAccount(12,n);
b.deposite(100.0);
try {
b.withdrawal(10.0);
}
catch(negativeBalanceException e) {
system.out.println("Negative Balance Error");
}
system.out.println("The Account balance is: " + b.balance());
}
}
Assignment #1 is designed to help in reviewing designing and using classes, but also gives you the opportunity to get acquinted with applets. You begin by designing a Light class and testing it with an applet that I wrote. You will then go on to a trafficLight class that has three instances of Light; and acts like a traffic light. An Applet tests this class.
I don't except you to develop mathematically complete pre and post
conditions for your methods. It is, however, important to learn about pre/post
conditions. A pre-condition is a clear statement of a method's
assumptions at the start. A post condition is a clear statement and
promise about what a method will do by the time it reaches its end. Suppose that we have a container class that keeps an array of integer
values. Lets call this array l. We want a method that
sorts l,
either in ascending or descending order. So, the sort method has an
integer parameter that is suppose to be either 1 or 0. If its 1, we sort in
ascending order, otherwise, we sort the values in descending order. Assume
that last_cell is the index for the last value in the array. Here is a
reasonable pre/post condition for sort:
The precondition says that a_or_d must be either 1 or 0 for the
method to work. The postcondition says that if a_or_d is 1,
the value in the array must be less that or equal to the next; otherwise,
a_or_d is expect to be zero and each value in the array to be greater
than or equal to the next.
Even a reasonable pre/post condition like the above requires some
human interpretation. We know the intent is to sort the values already in
the array. If you were going only based on what the post condition here
says, you could write a loop and instead of sorting the content of the
array, set all of its cells to zero. The post-condition would be
satisfied in this case, but of course, in no way did we intend for the sort
method to do that.
It is possible to be more complete in stating a postcondition, but, it
could get complicated to the point where it stops communicating the
purpose of a method to either the programmer who is to write the method
or the one who wants to use it.
It is time to review class design and encapsulation in Java. A
simple tank class is designed to help in discussing state and
methods of objects. Tank objects allow us to represent tanks that
contain some type of fluid in application programs. In the version that we are
discussing here, they are only responsible for controlling access to tank
content.
How is this content manipulated by the application? Tanks are simple
objects, we like to add fluid to them, remove fluid from them. Occasionally,
we need to know what the current content of a tank is, and if they are full
or empty. This is where the methods come into play. We concern ourselves with
determining functionality for objects and giving a tank the methods that
are needed for it to be useful. It takes some time to learn to
effectively design objects, it takes significantly longer to learn to
design them so that they remain useful beyond one application! You will
learn about
reuse in this course and the natural framework that Java provides for it.
Lets discuss a few issues that may not be obvious. Why would we not
permit an application program to just manipulate content of a tank with simple
assignment statements? The integrity of a tank is an important consideration.
Having tank objects control modification of their contents by only
allowing it to be done via an add or remove method provide a
safeguard that is important. Our experiences
in programming large applications, before object orientation, tells us that
much of our time was spent in tracking down errors that manifested
themselves at a point different from where they were caused. The phrase that
best describe this approach is programming with side-effects. What does
this mean? you write an application that requires one or more tanks and
whenever you need, increment or decrement the contents. You don't want to
end up with a negative content or a content that is over capacity and
good programmers would always check for such anomalies, or do they!
When the side-effect of calling a variety of subprograms are changes in
content of a tank, and one or more of those subprograms do not guard against
such things, we have problems. The process that you need to go through to
figure out where this violation occurred is typically a very time consuming
task. In some context, these types of error are very hard to find.
This is not to say we don't have logic errors when our approach is Object
Oriented, but we have a framework that allows us to isolate such problems
more rapidly that is both natural and easy. In our tank example, a tank
object becomes responsible for protecting its own content, the application
gets to only manipulate the content through the add and remove methods
provided for tanks. Now, Java also gives us a really nice way of handling
exceptions like overflow or underflow attempts by an application. We can
design add and remove methods to throw exceptions when
such attempts are made. The application must catch these exceptions any time
they call upon a tank to manipulate its content. The end result is that you
find the problem at its source and not at some later point when the program
breaks. You also never have to count on the application programmer to be
cautious, as they won't even get to compile the program without dealing with
exceptions.
All objects of class Tank are of fixed capacity. In Fact, we only
have a "class variable" for capacity that is a constant.
A tank can be constructed with no content (i.e. content_ is set to 0)
The Tank class has three methods: add,
remove, and content. The first two methods
change a tank's content, they are mutators. The method content(),
just returns the content of a tank and we consider it an accessor.
add, in normal cases, executes If you consider the add and remove methods carefully, you will notice that
the throw statements only occur in them when we detect
exceptions. Exceptions are generally abnormal cases. In your
documentation (pre/post conditions), you make it clear that you don't want to
negative values for your parameter, if the user of this class gives
you one anyway, you throw the exception. We will later see how we
catch exceptions in our testing of class Tank. An exception
doesn't have to terminate the user's program, although in all the test
programs for the Tank class it does.
Three exceptions may be thrown in this class:
TankOverFlowException, IllegalArgumentException, and
TankUnderFlowException. A tank may overflow when the add method is invoked and the amount provided would send the
content over capacity. Underflow simply means that the amount supplied for
the remove method would cause tank to go below empty.
IllegalArumentException is thrown when the parameter
amount in either add or remove is negative. Check out this statement carefully:
Exceptions provide us with a nice example of inheritance in Object
Oriented design. In java there are existing exception classes that are very
specific, such as, FileNotFoundException used in
testNormal2.java which needs to open a file
and read from it, IllegalArgumentException is used in the
Tank class itself for handling bad parameters. However, we don't have an
existing exception class that would deal with our overflow problem.
So, we built one. Notice that TankOverFlowException extends
Exception, that means it inherits everything in
Exception, and is going to add on some new things.
TankOverFlowException has two fields of its own,
overflowtank that represents the Tank object that caused
the exception andoverflow that holds the amount of overflow.
The constructor in TankOverFlowException calls
Lets take a second and look at
Here is the code from
testNormal1.java that shows how we try
something that may cause an exception and how we catch the exception if it
was thrown.
the add method may cause an overflow exception. So, in
this test program, we need the try/catch combination
when adding to the tank. Any Time, you invoke a method that may
throw an exception, you must do so in the context of a
try/catch or the compiler will complain.
The order in which you
catch exceptions is sometimes important. The following two catch clauses in
testNormal2.java had to go in the order given: Why should FileNotFoundException go before IOException?
because, the first is a subclass of the later. If a FileNotFoundException
is thrown when we perform Lets discuss the logic and the IO operation of this program. The
exception handling aspects have already been covered earlier. We will consider
the code piece by piece:
You should notice The statement Here are a couple of methods we could have added to the tank class.
The first one, roomLeft, returns a double representing
howmuch room is left in a tank. The second one, is an example
of a static method with two tank parameters; it uses existing methods
to move the content of one tank into the other. What happens if
the existing content of t1 is too much to be added to t2?
Lab #2
is designed to help better understand the difference between entities
that belong to each instance of a class and those that are shared by them.
By removing "static final" from the declaration of capacity_, each object
of class Tank gets its own capacity and tanks no longer have to max their
capacity at 5000. With the changes made here, capacity_ can be set at
construction. capacity_ will also need to be treated like content_ by
being qualified as a protected field; it will also need a method thats
returns its value. But, unlike content, we don't want any methods that
change capacity once the object is constructed.
samePercentage is a static method. There are plenty of examples
of such methods in java. Since this method is static, it does not
operate on fields of a particular instance of our Tank class.
samePercentage can operate on static fields, but we don't have any
of those any more; it can also be sent tank objects as arguments and
operate on their fields by qualifying them. For example, even though,
it can't say content_, it can say t1.content_ when t1 is
a parameter to it.
void sort (int a_or_d)
/** Precondition:
(a_or_d == 0) || (a_or_d == 1)
Postcondition:
((a_or_d == 1) && (l[0] <= l[1] <= ... <= l[last_cell])) ||
((a_or_d == 0) && (l[0] >= l[1] >= ... >= l[last_cell]))
**/
Tank example.
Details of Tank class
capacity
public static final double capacity=5000;
declares capacity.
public means that capacity can be accessed anywhere in Tank or
any subclass of tank. It could also be accessed in any application that
imports the Tank class by simply referring to it as Tank.capacity.
Making variables public should be done with care. In this case,
you can see that capacity is also final which declares it as
a constant that can't change. static is what makes capacity
a "class variable". Static variables don't get constructed for each object of
a class, they are shared by all objects of the particular class.
content_
protected double content_;
declares content_.
Each object of class Tank gets its own content_. Note that the
word static does not appear in the declaration. content_
is protected, this makes it accessible in Tank or any subclass of Tank,
but not in the applications that import it. Subclassing will be discussed
later in this lecture.
Tank Construction
methods
content_ +=amount;
which simply adds amount to content_.
remove, in normal cases, executes
content_ -=amount;
which simply subtracts amount from
content_.
Exceptions
throw new TankOverFlowException(this, overflow);
.
The word
new means that we are constructing an object of some class.
An object of TankOverFlowException is being created and thrown.
The throw statement requires an object of an exception class.
TankOverFlowException Class
public class TankOverFlowException extends Exception {
public Tank overflowTank;
public double overflow;
public TankOverFlowException(Tank t, double amount) {
super(); overflowTank = t; overflow = amount;
}
}
super();
which is how we invoke the constructor for the
super class Exception. Our constructor then sets its fields
to the incoming parameters.
throw new TankOverFlowException(this, overflow);
from the
Tank constructor shown earlier. When we throw an exception, we create an
object of the particular exception class. In this case, we have specified two
arguments for construction of such an object. The first one,
based on the definition of the constructor, must be an object of class Tank.
Note that our first argument is "this". So, what is this?
this provides a handle (reference) to the object whose
method is called; in this case, it provides the constructor of the
TankOverFlowException with the reference to the object where the overflow
occurred in. This is how exception object that is thrown here knows what
tank overflowed.
Catching TankOverFlowException
try {
Tank t1 = new Tank();
...
t.add(10);
}
...
catch (TankOverFlowException e) {
System.err.println("Tank overflow by "+e.overflow);
}
catch (FileNotFoundException e) {
System.err.println("File does not exist");
return;
}
catch (IOException e) {
System.err.println("Unsuccessful read");
return;
}
InputStream ist = new FileInputStream(fname);
, we want it caught as such and not just as an IOException!
If you change the order of the catch clauses, you'll notice that the compiler
will, very nicely, let you know that your
catch (FileNotFoundException e) {...}
is not reachable!
What we are assuming here in the way we have handled these two exceptions is
that catching FileNotFoundException helps us recognize that we may
have a bad file name and catching IOException would determine a problem
when we are actually reading data.
IO in Java
Lets discuss the testNormal2.java in more detail.
This program is representative of test programs that one would write to test
a program. It takes input values from a data file, such as,
test.dat which enables us to have more
flexibility in what we test a class for as we can change the data without
modifying the program to test multiple cases. Basically, it is designed to
prompt for a file name, and read double values from the file that are either
added or removed from a tank object. If the value read from the data file
is positive, it is added to the tank, otherwise, it is removed from the tank.
After each add or remove, a message is output that indicates how much was
added or removed and what the content of the tank was after the operation.
When there are no more values in the file, the program stops. If exception
occurs, such as, tank overflow, the program would output a message and stop.
String text;
declares a string variable that we will use
for reading double values from our data file.
Tank t = new Tank();
creates a tank referenced by t.
BufferedReader inp = new BufferedReader(new InputStreamReader(System.in));
creates an object that we can read strings from.
System.in is an object of class InputStream. This object only allows for input
that of single characters. Class
DataInputStream enables you to read
strings as well as other things.
System.out.println("Enter File Name:");
prompts the user for
a file name using the standard output object.
String fname = inp.readLine();
is used to read the input file
name from the keyboard (user).
The following sequence of code, simply, opens the input file,
and read the first line of it into text.
InputStream ist = new FileInputStream(fname);
BufferedReader istream = new BufferedReader(new InputStreamReader(ist));
text = istream.readLine();
text = istream.readLine();
also appears
before the end of the while loop. End of file is determined when
istream.readLine() returns null, which is why the while loop
is designed to terminate if text is null.
double am = (Double.valueOf(text)).doubleValue();
invokes a
class method in class Double that allows for parsing a string and returning
a Double object, the doubleValue() method then gets us a double value that we then store in am.
The next few lines simply check the
value read, and if positive, add it to tank, otherwise, remove it from
tank. Note that -amt is sent t.remove.
if (am > 0) {
t.add(am);
System.out.println("tank content after adding "+ am + " = "+
t.content());
}
else {
t.remove(-am);
System.out.println("tank content after removing "+ -am + " = "+
t.content());
}
A couple of additional Methods for Tank
public double roomLeft () {
/** return how much more room is left in the tank **/
return capacit-content_; //before lab#2
}
public static void moveAll (Tank t1, Tank t2) throws TankOverFlowException {
/** move all of the content of t1 into t2. **/
try {
t2.add(t1.content());
t1.remove(t1.content());
}
catch (TankUnderflowException e) {} //Can't happen here, so do nothing
catch (IllegalArgumentException e) {} //Can't happen here, so do nothing
}
Lab #2