COMP1406 Ch3 DefiningObjectBehavior
COMP1406 Ch3 DefiningObjectBehavior
Consider, for example, a Person which is defined as shown below with 6 attributes:
Person p1;
p1 = new Person();
p1.firstName = "Bobby";
p1.lastName = "Socks";
p1.age = 24;
p1.gender = 'M';
p1.retired = false;
p1.address = new Address();
- 40 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Certainly, constructors allow us to greatly simplify our code when we need to create objects in
our program.
The above constructor forces the user of our objects to supply parameters for ALL of the
instance variables when they use (i.e., call) our constructor ... which is approach number (1)
above. But we can be more accommodating, because in JAVA we are allowed to create more
than one constructor as long as the constructors each have a unique list of parameter types.
What if, for example, we did not know the person’s age, nor their address.
For this situation, we can actually define a second constructor that leaves out these two
parameters:
- 41 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice that there are two less parameters now (i.e., no age and no address). However, you
will notice that we still set the age and address to some default values of our choosing. What
is a good default age and address ? Well, we used 0 and null. Since we do not have an
Address object to store in the address instance variable, we leave it undefined by setting it to
null. Alternatively, we could have created a “dummy” Address object with some kind of
values that would be recognizable as invalid such as:
It is entirely up to you to decide what the default values should be. Make sure not to pick
something that may be mistaken for valid data. For example, some bad default values for
firstName and lastName would be “John” and “Doe” because there may indeed be a real
person called “John Doe”.
Here is one more constructor that takes no parameters. It has a special name and is known
as either a zero-parameter constructor, the zero-argument constructor or the default
constructor. This time there are no parameters at all, so we need to pick reasonable default
values for every one of the attributes:
public Person() {
firstName = "UNKNOWN";
lastName = "UNKNOWN";
gender = '?';
retired = false;
age = 0;
address = null;
}
You can actually create many constructors in the same class. You just need to write them all
one after each other in your class definition and the user can decide which one to use at any
time. Here is our resulting Person class definition showing the four constructors …
- 42 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
p1 = new Person();
p2 = new Person("Sue", "Purmann", 58, 'F');
p3 = new Person("Holly", "Day", 'F', true);
p4 = new Person("Hank", "Urchif", 19, 'M', false, new Address(...));
Note that it is always a good idea to ensure that you have a zero-parameter constructor. As it
turns out, if you do not write any constructors, JAVA provides a zero-parameter constructor for
free. That is, we can always say new Car(), new Person(), new Address(), new
BankAccount() etc.. without even writing those constructors. However, once you write a
constructor that has parameters, the free zero-parameter constructor is no longer available.
So, for example, if you write constructors in your Person class that all take one or more
parameters, then you will no longer be able to use new Person(). JAVA will generate an
error saying:
cannot find symbol constructor Person()
- 43 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
In general, you should always make your own zero-parameter constructor along with any
others that you might like to use … mainly because others who use your class may expect
there to be a zero-parameter constructor available.
However, in real life, vehicles also vary in terms of their performance characteristics, their
functionality, their abilities and their features/options:
Object = +
STATE BEHAVIOR
- 44 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
What does all of this mean ? Instead of writing a single program with a list of functions/
procedures, we will now be associating some of the functions/procedures with various objects.
So, we will actually move some of the functions/procedures into our various class definitions.
For example, consider code that causes two cars to public class Car {
int x, y;
accelerate across the screen. We could create a Car class float speed;
which maintains a car's location and speed. }
- 45 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Example:
Consider the Person class. Assume that we want to write a function that computes and
returns the discount for a person who attends the theatre on “Grandma/Granddaughter Night”.
Assume that the discount should be 50% for women who are retired or to girls who are 12 and
under. For all other people, the discount should otherwise be 0%. If we had the Person
passed in as a parameter to the function, we could write this code:
To write this as a method in JAVA, we would place this method in the Person class after the
instance variables and constructors are defined:
- 46 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice that the Person parameter is no longer there and that the word this is now being used
in place of that parameter. The word this is a special word in JAVA that can be used in
methods (and constructors) to represent the object that we are applying the behavior to. That
is, whatever object that we happen to call the method on, that object is represented by the
word this within the method's body. You can think of the word this as being a nickname for
the object being "worked on" within the method. Outside of the method, the word this is
actually undefined (and therefore unusable outside of the method) .
So, if we called the computeDiscount() method for different Person objects, this would
represent the different objects p1, p2 and p3, each time the method is called, respectively:
As it turns out, if you leave off the keyword this, JAVA will "assume" that you meant the object
that received the method call in the first place and will act accordingly. Therefore, the
following code is equivalent and often preferred since it is shorter:
- 47 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
It is important for you to understand that the gender, age and retired attributes are obtained
from the Person object on which we called the computeDiscount() method.
You may have also noticed that the method was declared as public. This allows any code
outside of the class to use the method.
Consider writing another method that determines whether one person is older than another
person. We can call the method isOlderThan(Person x) and have it return a boolean value:
- 48 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
The method accesses the age that is stored within both Person objects this and x.
How could we write a similar method called oldest() that returns the oldest of the two Person
objects, instead of just returning a boolean ?
- 49 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice how the code is similar except that it now returns the Person object instead. Now we
can simplify our program that determines the oldest of 3 people:
oldest = p1.oldest(p2.oldest(p3));
Do you understand how this code works ? Notice how the innermost oldest() method returns
the oldest of the p2 and p3. Then this oldest one is compared with p1 in the outermost
oldest() method call to find the oldest of the three.
In addition to writing such functions, we could write procedures that simply modify the object.
For example, if we wanted to implement a retire() method that causes a person to retire, it
would be straight forward as follows:
Notice that the code simply sets the retired status of the person and that the method has a
void return type, indicating that there is no "answer" returned from the method's computations.
Notice how the temporary variable is required to store the String that is being replaced.
- 50 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
At this point let’s step back and see what we public class Person {
have done. We have created 5 interesting // These are the instance variables
String firstName;
methods (i.e., behaviors) for our Person object String lastName;
(i.e., computeDiscount(), isOlderThan(), int age;
char gender;
oldest(), retire() and swapNameWith()). boolean retired;
All of these methods were written one after Address address;
another within the class, usually after the // These are the constructors
constructors. Here, to the right, is the public Person() { ... }
public Person(String fn, ...) { ... }
structure of the class now as it contains all the
methods that we wrote (the method code has // These are our methods
public int computeDiscount() { ... }
been left blank to save space). public boolean isOlderThan(Person x) { ... }
public Person oldest(Person x) { ... }
public void retire() { ... }
Now although these methods were defined in the public void swapNameWith(Person x) { ... }
class, they are not used within the class. We wrote }
- 51 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Person p;
System.out.println(p.computeDiscount());
This code will not compile. JAVA will give a compile error for the second line of code saying:
JAVA is trying to tell you that you forgot to give a value to the variable p. In this case, we
forgot to create a Person object.
Assume then that we created the Person as follows and then tried to get the streetName:
Person p;
This code will now compile. Assume that the Person class was defined as follows:
- 52 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Here the address attribute stores an Address object which is assumed to have an instance
variable called streetName.
The code will generate a java.lang.NullPointerException. That means, JAVA is telling you
that you are trying to do something with an object that was not yet defined. Whenever you get
this kind of error, look at the line of code on which the error was generated. The error is
always due to something in front of a dot . character being null instead of being an actual
object. In our case, there are two dots in the code on that line. Therefore, either p is null or
p.address is null, that is the only two possibilities. Well, we are sure that we assigned a
value to p on the line above, so then p.address must be null. Indeed that is what has
happened, as you can tell from the constructor.
1. Remove the line that attempts to access the streetName from the address, and access
it later in the program after you are sure there is an address there.
2. Check for a null before you try to print it and then don’t print if it is null … but this may
not be desirable because it is excessive error checking.
3. Think about why the address is null. Perhaps you just forgot to set it to a proper value.
You can make sure that it is not null by giving it a proper value before you attempt to
use it.
NullPointerExceptions are one of the most common errors that you will get when
programming in JAVA. Most of the time, you get the error simply because you forgot to
initialize a variable somewhere (i.e., you forgot to create a new object and store it in the
variable).
- 53 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
3.4 Overloading
When we write two methods in the same class with the same name, this is called
overloading. Overloading is only allowed if the similar-named methods have a different set
of parameters. Normally, when we write programs we do not think about writing methods with
the same name … we just do it naturally. For example, imagine implementing a variety of
eat() methods for the Person class as follows:
Notice that all the methods are called eat(), but that there is a variety of parameters, allowing
the person to eat either an Apple, an Orange or two Banana objects. Imagine the code below
somewhere in your program that calls the eat() method, passing in an object z of some type:
Person p;
p = new Person();
p.eat(z);
How does JAVA know which of the 3 eat() methods to call ? Well, JAVA will look at what kind
of object z actually is. If it is an Apple object, then it will call the 1st eat() method. If it is an
Orange object, it will call the 2nd method. What if z is a Banana ? It will NOT call the 3rd
method … because the 3rd method requires 2 bananas and we are only passing in one. A call
of p.eat(z, z) would call the 3rd method if z was a Banana. In all other cases, the JAVA
compiler will give you an error stating:
where the ... above is a list of the types of parameters that you are trying to use.
JAVA will NOT allow you to have two methods with the same name AND parameter types
because it cannot distinguish between the methods when you go to use them. So, the
following code will not compile:
Recall our method called isOlderThan() that returns a boolean indicating whether or not a
person is older than the one passed in as a parameter:
- 54 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
We could actually write another method in the Person class that took two Person objects as
parameters:
if (p1.isOlderThan(p2,p3,p4))
oldest = p1;
else if (p2.isOlderThan(p3,p4))
oldest = p2;
else if (p3.isOlderThan(p4))
oldest = p3;
else
oldest = p4;
Keep in mind, however, that the parameters need not be the same type. You can have any
types of parameters. Remember as well that the order makes a difference. So these would
represent unique methods:
public int computeHealthRisk(int age, int weight, boolean smoker) { ... }
public int computeHealthRisk(boolean smoker, int age, int weight) { ... }
public int computeHealthRisk(int weight, boolean smoker, int age) { ... }
But these two cannot be defined together in the same class because the parameter types are
in the same order:
- 55 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
In this example, p1 and p2 are variables that store instances of the Person class (i.e., specific
individual Person objects). Therefore, the computeDiscount() method is considered to be
an instance method of the Person class, since it operates on a specific instance of the
Person class.
Instance methods typically access the inner parts of the receiver object (i.e., its attributes) and
perform some calculation or change the object’s attributes in some way.
A method that does not require an instance of an object to work is called a class method:
- 56 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice how the method now accesses the person p that is passed in as the parameter, instead
of the receiver this. If we do this, the code result is now fully-dependent on the attributes of
the incoming parameter p, and hence independent of the receiver altogether. To call this
method, we would need to pass in the person as a parameter:
We would never do this, however, since within the computeDiscount() method, the parameter
p and the keyword this both point to the same Person object. So, the extra parameter is not
useful since we can simply use this to access the person. Instead, what we probably wanted
to do is to make a general function that can be written anywhere (i.e., in any class) that allows
a discount to be computed for any Person object that was passed in as a parameter.
Consider this class for example:
Now to call the method, we would need to make an instance of Toolkit as follows:
new Toolkit().computeDiscount(p1);
But this seems awkward. If we wanted to use this "tool-like" function on many people, we
could do this:
- 57 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Now we can see that toolkit is indeed a separate class from Person and that it acts as a
container that holds on to the useful computeDiscount() function. However, we can simplify
the code.
Whenever we write a method that does not modify or access the attributes of an instance of
the class that it is written in, the method is independent of that object’s state. In other words,
the code is not changing from instance to instance ... and is therefore considered static.
In our example, the code inside of the computeDiscount() method does not access or modify
and attributes of the Toolkit class ... it simply accesses the attributes of the Person passed in
as a parameter as performs a computation. Therefore this method should be made static.
How do we do this ? We simply add the static keyword in front of the method definition:
Now we do not need to make a new Toolkit object in order to call the method. Instead, we
simply use the Toolkit class name to call the method. Here is how the code changes. Notice
how much simpler it is to use the method once it has been made static. (to save space,
System.out.println has been written as S.o.p below):
- 58 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
This is the essence of a class/static method … the idea that the method does not need to be
called by using an instance of the class.
Hopefully, you will have noticed that the main difference between an instance method and a
class/static method is simply in the way in which it is called. To repeat … instance methods
are called by supplying a specific instance of the object in front of the method call (i.e., a
variable of the same type as the class in which the method is defined in), while class methods
supply a class name in front of the method call:
Often, we use class methods to write functions that may have nothing to do with objects at all.
For example, consider methods that convert a temperature value from centigrade to fahrenheit
and vice-versa:
Where do we write such methods since they only deal with primitives, not objects ? The
answer is … we can write them anywhere. We can place them at the top of the class that we
would like to use them in. Or … if these functions are to be used from multiple classes in our
application, we could make another tool-like class and put them in there:
double f = ConversionTools.centigradeToFahrenheit(18.762);
As you browse through the JAVA class libraries, you will notice that there are some useful
static methods, however ... most methods that you will write for your own objects will be
instance methods.
- 59 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
• starting/ stopping
• steering
• changing gears
• braking, etc..
Cars are clearly designed to be easy to drive, requiring a simple and easy-to-understand
interface. Similarly, it is important that we make our code easy to use and easy to
understand. Otherwise, making changes to the code, debugging it and extending it with new
features can quickly become very difficult and time consuming.
In order to keep our code simple, we need to make the interface (or
"outside view") of our objects as simple as possible. To do this, we
need to “hide the details” of our object that most people would
not need to worry about. That is, we will hide some of the
attributes (complicated parts) and some of the methods
(complicated procedures) of our object “under the hood”.
In JAVA, we protect and hide attributes and behaviors by using something called an access
modifier.
There is an advantage to using access modifiers when working with a team of software
developers on a large program. They can be set so that some developers will have the
freedom to access or modify attributes or methods of an object, while other developers will not
be allowed such freedom to view or change portions the object.
We have already been using an access modifier called public when we wrote our classes,
constructors and various methods:
The keyword public at the front of a method declaration means that the method is publicly
available to everyone, so that these methods may be called from anywhere.
For most classes, constructors and methods, we do not need to write public. If we leave off
this access modifier, then the class/constructor/method will have what is known as default
access … meaning that the methods may be called from any code that is in the same package
or folder that this method’s class is saved in. If we write all of our code in the same folder,
then default and public access means the same thing.
There are two other access modifier options available called private and protected. When
we declare a method as private, we would not be able to use this method from any class other
than the class in which it is defined. Protected methods are methods that may be called from
the method’s own class or from one of its subclasses (more on this soon). So here is a
summary of the access modifiers for methods:
In this course, most of the methods that we write will be public … which allows the most
freedom to access and modify our objects. Usually, private methods are known as helper
methods since they are often created for the purpose of helping to reduce the size of a larger
public method or when a piece of code is shared by several methods.
- 61 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
For example, consider bringing in your car for repair. The publicly-available method would be
called to repair() the car. However, many smaller sub-routines are performed as part of the
repair process (e.g., runDiagnostics(), disassembleEngine() etc...). From the point of view
of the user of the class, there is no need to understand the inner workings of the repair
process. The user of the class may simply need to know that the car can be repaired,
regardless of how it is done. Here is an example of breaking up the repair problem into helper
methods that do the sub-routines as part of the repair …
Notice that the helper methods are private since users of this class probably do not need to
call them. Here is an example showing how we might attempt to call these methods from
some other class:
Now what about protecting an object’s attributes ? Well, the public/private/protected and
default modifiers all work the same way as with behaviors. When used on instance variables,
it allows others to be able to access/modify them according to the specified restrictions.
So far, we have never specified any modifiers for our attributes, allowing them all default
access from classes within the same package or folder.
However, in real world situations, it is often best NOT to allow outside users
to modify the internal private parts of your object. The reason is that results
can often be disastrous. It is easy to relate to this because we well
understand how we hide our own private parts ☺.
As an example, consider the following code, which may appear in any class. It shows that we
can directly access the balance of a BankAccount.
- 62 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
This is clearly undesirable since there is little protection. Could you imagine if anyone could
modify the balance of your bank account directly ?
In order to prevent direct access to important information we would need to prevent the code
above from compiling/running. If we were to declare the balance instance variable as private
within the BankAccount class, then the above code would not compile, thus solving the issue.
In general, while freedom to access/modify anything from anywhere seems like a friendly thing
to do, it is certainly dangerous. Anyone could "stomp" all over our instance variables
changing them at will. A general "rule-of-thumb" that should be followed is to declare ALL of
your instance variables as private as follows:
...
}
Once we do this, then the following code will not work (when written in a class other than the
Patient class):
- 63 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
What we have essentially done is to erect a wall around the object ... like
the wall around a city. We have encapsulated it with a protective bubble.
Although we are still able to create the object, we are prevented from
accessing or modifying its internals now from outside the class. By doing
this, we have protected the object so much that we cannot get information
neither into it nor out from it. We have kind of secluded the object from the
rest of the world by doing this. However, just as a walled city has gates or doors to allow
access, we too can set up a form of gated access by means of public methods.
We will grant access to "some" of our object's attributes (i.e., instance variables) by creating
methods known as get and set methods (also known as getters and setters). The idea of
creating these gateways to our object’s data is common practice and is considered to be a
robust strategy when creating classes to be used in a large software application.
In this course, since we are only creating a few classes and since we are the only code writers,
we may not immediately see the benefits of declaring private attributes and then creating
these methods. However, in a larger/complicated system with hundreds of classes, the
benefits become quite clear:
Let us first consider get methods. They let us look at information that is within the object by
getting the object’s attribute values (i.e., get the values of the instance variables). Get
methods have:
• public access
• return type matching attribute’s type
• name matching attribute’s name
• code returning attribute’s value
// Get methods for name, age, height, gender and retired attributes
public String getName() { return this.name; }
public int getAge() { return this.age; }
public float getHeight() { return this.height; }
public char getGender() { return this.gender; }
public boolean isRetired() { return this.retired; }
}
- 64 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice that all the methods look the same in structure. They are all public, all have return
types and names that match the attribute type, all have no parameters and all are one line
long.
When we call the method to get the attribute value, the method simply returns the attribute
value to us. It’s quite simple. By convention, all get methods start with “get” followed by the
attribute name, with the exception of attributes that are of type boolean. In that case, we
usually use “is” followed by the attribute name, as it makes the method call more natural.
Now let us examine the set methods. Set methods allow us to put values into the instance
variables (i.e., to set the object's attributes). Set methods have:
Here is how we would write the standard set methods for the Patient class:
When we call the method to give the attribute a new value (i.e., we supply the new value as a
parameter to the method), the method simply takes that new attribute value and sets the
attribute to it by using the = operator.
Normally, we write all the get and set methods together, and sometimes shorten them onto
one line. Also, they are often listed in the code right after the public constructors as follows:
- 65 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
// Constructor
public Patient() {
name = "Unknown";
age = 0;
height = 0;
gender = '?';
retired = false;
}
// Get methods
public String getName() { return this.name; }
public int getAge() { return this.age; }
public float getHeight() { return this.height; }
public char getGender() { return this.gender; }
public boolean isRetired() { return this.retired; }
// Set methods
public void setName(String n) { this.name = n; }
public void setAge(int a) { this.age = a; }
public void setHeight(float h) { this.height = h; }
public void setGender(char g) { this.gender = g; }
public void setRetired(boolean r) { this.retired = r; }
}
Notice that primitive attribute values are returned as simple values but object attribute values
are returned as pointers/references to the object. The Patient object remains unchanged as a
result of a get method call.
- 66 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice that primitive attribute values are simply replaced with the new value. For object
attribute values, after the set call, the attribute will point to the new object. The previous
object that the attribute used to point to is discarded (i.e., garbage collected) if no other objects
are holding on to it. Once we create these get/set methods, we can then access and modify
the object from anywhere in our program as before:
- 67 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Here is what the output would be (however, initial values depend on the Patient constructor):
Now if we think for a moment ... what did we really do by making all the get and set methods
? Really, we wrote a lot of code (e.g., 5 get methods and 5 set methods for the Patient class)
but did not gain anything new. The code does the same thing as before. In fact, the test
code seems longer and perhaps slower (since we are calling a method to get/set the instance
variables for us instead of accessing them directly). So why did we do this ? Let us review
the advantages again:
1. First, get/set methods actually make life simpler for users of your
class because the user does not have to understand the “guts” of
the object being used. It allows them to treat the object as a “black
box”. The user does not need to know about all the instance
variables. Some are used to hold data that is temporary or
private. You should only create public get methods for the
instance variables that the user of the class would need to know
about.
- 68 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Patient@7d8a992f
Patient@164f1d0d
By default, JAVA displays all of the objects that you make in this manner, showing you the type
of object (i.e., the class name) followed by something that represents the object's location in
memory. This format for displaying objects is not very useful for debugging. If we had a
dozen or so Patient objects displayed in this manner, we would not be able to "pick out" one
that we may be looking for. It would be more advantageous if we had something a little more
descriptive ... perhaps showing the patient's name.
- 69 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Why do we care ? Well, we can actually replace the default toString() behavior by writing our
own toString() method for all of our own objects that defines exactly how to convert our object
to a String. That is, we can control the way our object “looks” when we print it on the screen
or when we display it in our user interface.
Suppose that we want our Patient object to display something like this when printed:
Patient named Hank
You should notice that the first two words of this output are "fixed" and it is only the last part
(i.e., the first name of the Patient) that varies from patient to patient. We can make this to be
the standard output format for all Patient objects simply by writing the following method in the
Patient class:
This method overrides the default toString() method, essentially replacing it. Notice that the
method is called toString() with no parameters and that it has a return type of String. This is
important in order for the method to properly override the one inherited from the Object class.
System.out.println(p1);
System.out.println(p2);
System.out.println(p3);
Patient named
Patient named Holly
Patient named Hank
- 70 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
To write an appropriate toString() method, we need to understand what is fixed in this output
and what will vary. The number 19 should vary for each patient as well as the first and last
names. Here is how we could write the code (replacing our previous toString() method):
Notice that the basic idea behind creating a toString() method is to simply keep joining
together String pieces to form the resulting String. Now here is a harder one. Let us see if
we can make it into this format:
Here we have the age and names being variable again but now we also have the added
variance of their retirement status.
However, this is not quite correct. This would be the format we would end up with:
19 year old false patient named Hank
Notice that we cannot simply display the value of the retired attribute but instead need to write
“retired” or “non-retired” for the retired status.
To do this then, we will need to use an IF statement. However, in JAVA, we cannot write an IF
statement in the middle of a return statement. So we will need to do this using more than one
line of code. We can make an answer variable to hold the result and then break down our
method into logical pieces that append to this answer:
return answer;
}
- 71 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
if (this.retired)
answer = answer + "retired";
else
answer = answer + "non-retired";
answer = answer + " patient named " + this.name;
return answer;
}
The result is what we wanted. Note however, that we can simplify this code a little further:
if (!this.retired)
answer = answer + "non-";
- 72 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Of course, we will need to make the Address object too. We can make something quite
simple like this (we will leave off city/province/postal code to keep things simple):
- 73 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Now, let us define a BankAccount with the following attributes and constructors as follows:
Likely, when someone makes a new BankAccount, they DO NOT get to choose their own
account number, as this is usually assigned by the bank itself. Let us assume that the first
created account is assigned the account number 100001, the second gets 100002, the third
100003 and so on. In this scenario, we can simply keep a counter that starts at 100001 and
increases each time a new account is created.
To do this, we can create a static/class variable in the BankAccount class to represent this
counter. We can call it LAST_ACCOUNT_NUMBER which will store the account number that
was last given out. We can give this variable an initial value of 100000 as follows …
rather set it to the next available number and then increment the counter. Here is the code
that we would need to write:
Notice that each bank account will always get a new number because all available
constructors increment the global counter before assigning the bank account number to the
new account. Also, notice that there are no set methods. That is because an account
should never be allowed to change its owner nor its accountNumber once it has been created.
Also, there should not be any permission to directly modify (i.e., set) the balance ... there
should be deposit and withdraw procedures that must be followed.
Now, what about a toString() method ? What should a bank account look like when printed ?
That is up to us. Perhaps we want it to look like this:
The above does not display the account’s owner. Here is how we would write the code:
Notice that the withdraw() method returns a boolean that will inform us as to whether or not
there was enough money in the account. Both of these methods would need to be added to
the account.
b1.deposit(125);
b2.deposit(3245.02f);
b2.withdraw(1000);
b3.withdraw(20);
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
}
}
- 76 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice that the account numbers assigned are consecutive (i.e., 100001, 100002 and 100003).
Of course, each time we run the program, the account numbers start over at 100001 again. If
we wanted to ensure that our code assigned new numbers even when we restart the program,
we would have to store the last account number counter in a file and then re-save the changed
counter each time to the file. We will discuss the reading and writing of files later in the course.
Finally, we need a way of keeping the accounts all together. We can do this by making a
Bank class which keeps an array of BankAccount objects. Since we are using arrays, we
will also want to define a fixed size for the array ... perhaps defined as a constant. Here is
what we can do:
public Bank(String n) {
name = n;
numberOfAccounts = 0;
accounts = new BankAccount[ACCOUNT_CAPACITY];
}
// These are the get methods (set methods are not allowed)
public String getName() { return name; }
public BankAccount[] getAccounts() { return accounts; }
public int getNumberOfAccounts() { return numberOfAccounts; }
Notice that the method is private. That is because we don't want others passing in accounts
that may be invalid or ones that belong to different banks. Instead, we will make a public
method as a means of creating a new account, given a Customer:
- 77 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
We will want to also probably allow depositing and withdrawals from the accounts based on an
account number:
We can then write any interesting methods that we want such as these:
- 78 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
myBank.deposit(100001, 125);
myBank.deposit(100002, 3245.02f);
myBank.withdraw(100002, 1000);
myBank.withdraw(100003, 20);
- 79 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Normally, it is not common to test your constructors nor get/set methods, but it is certainly
important to test methods that perform computations, search, sort, etc… For problems that
require numerical parameters, it is a good idea to test different values that could potentially
cause problems. For example, if we were to fully test the deposit() method for the
BankAccount class, we would want to test depositing the following amounts:
We could create a simple test program to do this, making sure that we properly display the
results to confirm that they are correct as follows …
- 80 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
Notice the careful use of System.out.println() in the program to provide a kind of “log”
showing exactly what we tested and the order that things were tested in. If you were to read
the output, you should be able to follow along as the deposit transactions were made to
confirm the correct balance each time.
From the output, you may notice something that needs changing. For example, you may
decide to prevent depositing negative amounts of money. You might do this by changing the
code to generate an exception (more on this later) or perhaps simply perform a check and
ignore deposits of negative amounts.
It really depends on the application and whether or not it is tied-in with the user interface. For
example, at a bank machine, it is impossible to deposit a negative amount of money because
the machine does not allow you to enter a negative sign. In such a situation, you may choose
simply to ignore the problem altogether, since it would never occur. However, a simple check
may be best, in case you port your code into a different program:
Then we would re-run the same test code to see whether or not it worked:
Now this was a simple test program which is often known as a “Test Unit”. In larger, more
complicated, real-world programs, in order to keep organized, it would be necessary to create
multiple simple “unit tests” that test particular aspects of the program. For example …
- 81 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
In fact, it is often the case that we would like to perform transactions and test cases on a
particular bank account. In this case, we can break down the separate test units as test
methods in a larger test program:
- 82 -
COMP1406 - Chapter 3 – Defining Object Behavior Winter 2018
deposit1(acc);
deposit2(acc);
withdraw1(acc);
withdraw2(acc);
deposit1(acc);
deposit2(acc);
withdraw1(acc);
withdraw2(acc);
deposit1(acc);
deposit2(acc);
withdraw1(acc);
withdraw2(acc);
}
}
There are actually principles and guidelines for writing test cases for large systems. However,
it is beyond the scope of this course. You will learn more about proper testing next year.
- 83 -