95.105 - Introduction to Programming
Fall 2001

 3  Object Oriented Programming


What's in This Set of Notes ?



 3.1 Important Terms

An object: All objects belong to some class.

Logically,

Physically in Java, When we want to use an object defined in a certain class, we must create an instance of that class

Logically, an instance:

Physically, an instance: When we want to "talk to" or "manipulate" an object, we must send it a message.   A message: How can we make an object (say, a person) lift its left arm, then its right arm and then tell us its name ?
  Here is some possible code for doing this (assuming that a Person class exists): Maybe we could use messages with parameters instead: It is up to the designer of the Person class as to what messages will and will not work when sent to a person object. That is, the designer of the Person class will dictate exactly what the person is capable of understanding and what it will do when the messages are sent to it. Obviously, there will be some things that the person object will not understand such as: A method : An instance method: (Note: most methods that you'll write are instance methods since they operate on instances of the object that you are defining the method for).

Instance methods represent the "behaviour" of a particular kind of object.   So, sending a message to an object really means calling one of its methods.  We send messages to objects (i.e., instances) in Java by using the dot operator:

anObject.methodName();
This means: "send the message called methodName to anObject".   We can also supply parameter values within the round brackets.
 

An instance variable:

In the case where an instance variable of class X stores a primitive data type, you can think of that state being stored directly as part of X itself.  In the case where the instance variable stores an Object, the object itself does not "sit" inside X, instead, a pointer is there which points to the actual object.   This allows a object to hold any other object...even another object just like it:

Note that both instances above have the same instance variables, but these variables are holding different values.  Hence, the values vary between instances of the class and therefore are called instance variables.

A class method:

A static method (i.e., class method) is often used to represent a function which performs some computation and is independent of a particular instance.   The message is not sent to any particular instance, rather it takes some values as parameters and performs a computation on these values, returning a result.

Class methods represent static (or fixed) "behaviour" which may not depend on instances.   So, sending a message to a classt really means calling one of its functions.  We send messages to classes in Java by using the dot operator, making sure the class name is on the left side:

Classname.methodName();


A class variable:

Typical uses of static variables are to define shared constants for the class.  For example: Static variables are also used to store a commonly accessed value such as the last bank account number that was given out or the last customer served from a service center.  In a sense, these are acting as global counters.   Whenever a new account is created or a new customer served, the value is incremented:


A package:

A constructor: A default constructor:
 3.2 Approach to Object-Oriented Problem Solving

When doing object oriented programming keep in mind that you
  1. first define and implement the objects in your problem domain,
  2. then you can write your application to make use of these objects
Sometimes though, you do not know all of the objects you need, so you typically iterate through the steps above.   Also, if you are merely using existing objects, or objects that someone else gives you, then you can jump ahead to the application part.

So ... you'll find that much of your time is spent in defining objects, their state and their behaviour.  Once you have defined these objects, you can start using them ... this is the application's responsibility.   In your assignments, the application is merely your testing that you'll be doing.
 

There are 3 basic steps to follow when solving problems in an object-oriented manner:

  1. Identify the Objects
  2. Identify the inter-relationships between the Objects
  3. Identify the characteristics of the Objects (state and behaviour)
Name as many candidate objects as you can from the following problem domains.  In other words, try to determine what kind of objects you need to create to make the objects.  For each of your candidate objects, list what you think their major responsibilities might be. For each of the following objects create a list of plausible operations (behaviour) and suggest a possible representation or state (attributes): As we can see, OOP encapsulates data and behaviour into objects.  Each object has the property of data hiding.  That is, the user of a class does not need to know how the object is actually represented, just how it is to be used.  This is known as encapsulation and it is a very important term in OOP.
 


 3.3 Creating Simple Methods

Messages are sent to objects.  This causes a Method to be evaluated.
When code inside a method is being executed, the term receiver is given to the object that received that message.

Some methods return an answer, and some do not.  Those that return answers are also known as functions.  Those that do not return an answer are typically called procedures.  When writing a method, the method must have a return type which is the kind of value that is returned from the method upon its completion.   The return type must ALWAYS be specified.  All procedures have a return type of void.   Hence, if no value is to be returned, then the return type should be void.

When methods are created, they are called (used) differently depending on where the method is declared.
Here is the format for calling the method (this is not actual code, just a template):

methodName(parameters)         // when the method with name methodName is declared in this class
object.methodName(parameters)  // when the method with name methodName is declared in another class
classname.methodName(parameters) // when the method with name methodName is declared static in another class
Note that in the writing of the method, we must declare the types of each parameter as well (we'll see this later).

When Java encounters a method call with some Java code, it stops what it is doing, goes and does the code within the method and then comes back (i.e., returns) to what it was doing before the method call.   If the method was a function, the returned value is then used in the computations.

For example, consider what happens when we call the Math.sin() function in this code:

System.out.println("Sine of PI is " + Math.sin(3.14159265));
Note that Java does not print anything from the println() method until it has the entire String to be printed.  Java will come across the Math.sin(3.14159265) method call and then go get the answer.  This answer (happens to be 0.0) then replaces the call and so this is what Java now sees:
System.out.println("Sine of PI is " + 0.0);
Just remember that all method calls get replaced by their return value, and this is then used in the Java expressions.
Then, the string is concatenated with 0.0 and so Java sees this:
System.out.println("Sine of PI is 0.0");
Finally, Java evaluates the println method and the output appears in the console.

Consider the following code which converts a centigrade (Celsius) temperature to fahrenheit:

public class CentFahr {
    public static void main(String args[]) {
        System.out.println("Enter a centigrade temperature:");
      int cent = KeyboardPrompter.getInteger();
      int fahr = cent * 9 / 5 + 32;
        System.out.println(cent + "C is " + fahr + "F");
    }
}


Here is the output if  28 is entered :
 
Enter a centigrade temperature:
28
28C is 82F.

Here are some things to note:

It would be nice to be able to send some message that will compute each centigrade temperature to fahrenheit one at a time.  This way, we do not need to copy the mathematical equation each time we want to do the conversion.  The expression will be contained within the method.  But who gets this message ?   Well, we want to apply this conversion from one number format to another.  Is there a class for the integers that we've been using ?  No.  In fact, the integers are basic data types and not really Java objects.

We must create our own class (we'll call it Convert) and make a message (perhaps called  fahrenheit() ) .  If we define the method as static, then it is a class method for that class and we can call it as follows:

In fact, if we call it from any other method in this class, we can just say since Java assumes by default that it is a class method for this class if we do not specify the class.

Here is the class definition and method:

Note that the method requires a single integer parameter (i.e. the centigrade value) and returns a single integer value (i.e., the converted temperature).  Now, in order to see if it works, we need to write some test code: Here is the output:
 
34C is 93F
0C is 32F
24C is 75F

Example:

Now consider a program that needs to display a name and email address for several people as follows:

Obviously, we can just write a series of System.out.println calls as we know how to do this.  However, it may be that there are many people and we can clearly see that certain things are repeated in the output (such as the lines and Name: and E-mail:).

We can write a method that will print out information for one person and then merely call it repeatedly with different names and email addresses.  We'll call the method printInfo().

Here is the method:

static void printInfo(String name, String email) {
    System.out.println("Name:     " + name);
    System.out.println("E-mail:   " + email);
    System.out.println("----------------------------------------");
}
Notice that the method does not return anything (declared as void).  Also, notice that there are two parameters that are required.  Of course, to test it, we'll need some test code:
public class Labels {
    static void printInfo(String name, String email) {
        System.out.println("Name:     " + name);
        System.out.println("E-mail:   " + email);
        System.out.println("----------------------------------------");
    }

    public static void main(String args[])  {
        printInfo("Rob Banks","rbanks@bns.nortel.ca");
        printInfo("Ben Dover","dover@abb.ca");
        printInfo("Hugh Jass","hjass@npp.springfield.ca");
        printInfo("Phil Meeyup","pm@itt.nrc.ca");
    }
}


 3.4 Creating a Simple Bank Account Class

It is now time to make our first class (i.e., define our first object).  We will make a BankAccount object.

When creating a new type of object (a class), there are always a set of steps that need to be made:
 


Of course, as usual, to make sure everything works, we'll need to write some test code ... so we make a main method as follows:

Here is the output from this testing:
 
Account #432883 with balance $0.0
Account #432883 with balance $553.67
Account #432883 with balance $153.49997

Seeing the results, we realize that the printing method does not round off to two decimal places.  Also, we notice that the math is slightly off.  We can easily fix this:

Here is the output from this testing:
 
Account #432883 with balance $0.00
Account #432883 with balance $553.67
Account #432883 with balance $153.50

We can even make more than one bank account:

Here is the output from this part of the testing:
 
Account #431881 with balance $300.00
Account #549432 with balance -$100.00

Woops!  Looks like we forgot to make a balance check before withdrawing fro the account : ).
Can we fix this ?   Certainly.

// Withdraw some money from the account and return the amount withdrawn
public float withdraw(float anAmount) {
    if (anAmount <= balance)
        balance -= anAmount;
    return(anAmount);
}
Clearly, we needed more testing before stating that our code was correct.
This testing is actually still not sufficient.  We haven't tested the get/set methods at all.  We can do this easily. Here is the output from this part of the testing:
 
The Bank Account's owner is Bob
The Bank Account's number is 554343
The Bank Account's balance is 3500.0

Hey!  How come the balance has no dollar sign ?  And how come it's not showing two decimal places ?  You should be able to explain this easily.



Example Methods:

We can create special "example" methods which are used to create example of BankAccounts.    Example methods:

Here is an example method for a BankAccount class:
public static BankAccount example1() {
    BankAccount ba = new BankAccount();
    ba.setOwnerName("Bob");
    ba.setAccountNumber(554343);
    ba.deposit(1000);
    return ba;
}
Notice a few things: Here is how we can use this in our main method (examine the difference between this main matho and the one above): Notice that the code is simpler now and our testing is more clear.

What if we call the method twice ?   Is it the same account returned ?  NO WAY!   Every time you call the method a new account is created.   It will just happen to have the same initial values in it:

publicstatic void main(String args[]) {
    BankAccount bobsAccount, marysAccount;

    bobsAccount = BankAccount.example1();
    marysAccount = BankAccount.example1();
    marysAccount.setOwnerName("Mary");
    marysAccount.setAccountNumber(431881);
    System.out.println(bobsAccount);
    System.out.println(marysAccount);
}

This prints out (depending on the toString method though):
 
Account #554343 with balance $1000.00
Account #431881 with balance $1000.00

In fact, since the example method is static and we are calling this it from another tatic method (i.e., the main method), we do not need to specify the class:

publicstatic void main(String args[]) {
    BankAccount bobsAccount, marysAccount;

    bobsAccount = example1();
    marysAccount = example1();
    marysAccount.setOwnerName("Mary");
    marysAccount.setAccountNumber(431881);
    System.out.println(bobsAccount);
    System.out.println(marysAccount);
}

However, if we wanted to use the example methods from outside of the BankAccount class, we would need to supply the classname.

Just to confuse you even further....it is actually possible to call a class method from an instance of that class!!!  This is very weird and counter-intuitive...but hey....the java guys like to come up with weird stuff:

    bobsAccount = new BankAccount().example1();   //this works!
    marysAccount = bobsAccount.example1() //this works too!  bobsAccount is used to get a new account !  VERY Weird!

Lastly, we can make many more example methods and use them similarily:

public static BankAccount example2() {
    BankAccount ba = new BankAccount();
    ba.setOwnerName("Biff");
    ba.setAccountNumber(121212);
    ba.deposit(1000);
    ba.deposit(2000);
    return ba;
}
public static BankAccount emptyAccountExample() {
    BankAccount ba = new BankAccount();
    ba.setOwnerName("John Doe");
    ba.setAccountNumber(554343);
    return ba;
}



 3.5 Adding a Class Variable (i.e., static field)


Most of the methods we write are instance methods since they are meant to apply to particular objects.   We can always write static methods, that perform some function and they represent messages that are sent to the class, not to instances.   The example below shows how we may want to use a static variable to represent the last number that we gave out to an incoming employee.   Each time an employee starts work in the company, we may assign him.her a new employee number based on this last account number, incrementing it each time.   We can also see that a static method can be written that takes two Employees as parameters and returns the employee who has the higher salary.

Looking at the BankAccount code, we notice something unrealistic.  When creating a new bank account, we probably should not be allowed to specify the account number.  Usually, these are assigned automatically to the customer.   What number does a new customer get anyway ?

Let us assume that the first customer gets 000001, the second gets 000002, the third 000003 and so on.  That means, that if we keep a count as to how many customers there are, we will know which number to give the next customer.   We'll keep a counter called LastAccountNumber which will store the account number that was given out last.  The next customer will get one more than this.

How do we do this ?  Well, just make a class variable (i.e., static) called LastAccountNumber which has an initial value of zero.  Then, when a new BankAccount is created, give it an account number which is one more than the LastAccountNumber and increment it for the next time.

We must make a modification to the constructor so that it does not allow the user to specify the account number.
 

public class BankAccount2 {
    // This class variable remembers the last account number that was given
    // out the last time a new bank account was created.
    static int LastAccountNumber = 0;

    // These are the instance variables
    private int     accountNumber;
    private String  ownerName;
    private float   balance;

    // This is the constructor.  It initializes the bank account
    // to have the name as specified by the given parameter.
    // The account number is set by a class variable.
    public BankAccount2(String name) {
        accountNumber = ++LastAccountNumber;
        ownerName = name;
        balance = 0.0f;
    }

    ...
    // I left out the get/set/toString/deposit/withdraw methods
    ...

    // This is the testing
    public static void main(String args[]) {
        BankAccount2 marksAccount, jimsAccount, bettysAccount;

        marksAccount = new BankAccount2("Mark Lanthier");
        System.out.println(marksAccount);
        jimsAccount = new BankAccount2();
        System.out.println(jimsAccount);
        bettysAccount = new BankAccount2("Betty Boop");
        System.out.println(bettysAccount);
    }

}

Note as well that we had to change two constructors (the default one, and the other one).  To reduce the amount of code changes required during code maintenance, often a programmer may do what is known as "chaining" methods (in this case chaining constructors).  What we will do is have one constructor call the other one:

    public BankAccount2() {
        this("unknown"); // Calls the constructor below.
    }

    public BankAccount2(String name) {
        accountNumber = ++LastAccountNumber;
        ownerName = name;
        balance = 0.0f;
    }

In addition to these changes, we will probably want to remove the setAccountNumber() set method for setting the account number since it is no longer allowed.  In fact, in this case, we probably do not want the set method for the balance either since we have a deposit() and withdraw() methods that make the changes as necessary.

Also, if we want to display account numbers with leading zeros, we can use the decimal format class on the account number as well:

    public String toString() {
        return("Account #" + new java.text.DecimalFormat("000000").format(accountNumber) +
              " with balance " + new java.text.DecimalFormat("$0.00").format(balance));
    }


Here are the results of testing.  Notice that successively created bank accounts have unique numbers.
 
Account #000001 with balance $0.00
Account #000002 with balance $0.00
Account #000003 with balance $0.00


 3.6 Testing Requirements

When creating a class of your own, you must be sure to thoroughly test it.  Unfortunately, testing is often tedious and is therefore poorly done and ignored by many programmers.  In this course, we will not accept this laziness.  You MUST always prove to the best of your abilities that your code works.

After creating a class, make a main function that tests at least the following:

After the first two or three assignments, you will not have to test your constructor nor the get/set methods anymore, but you'll still have to test the others.  Here is what is required for testing: Once you've created your testing code, run the tests and ensure that a clear explanation of what is happening appears in the console window.  For instance, here is what we'd like to see as nice testing output for the BankAccount class: As you can see, the output gives a very clear indication as to what was done to the bank account and in what order.   From this output, the T.A. can easily deduce that your code seems to be working fine.  Of course, to make this nice output, you'll have to add more System.out.println calls.

But what about when multiple Bank Accounts are used ?  Apply common sense again.  You must always indicate which account has been changed and how:

Note that if you do not supply adequate testing, you WILL lose marks on your assignments.  Once you get your output from the console, you must print it out and hand it in with your assignment.  To do this through DOS, it is easiest to "direct" the output of your program to a file of your choosing: When the above line is typed in the DOS window, the output of your program goes to a file with the name "bank.out" and does not appear in the console window.  You can then view this file using the notepad program.   Now you can print this file as your program output.
 


 3.7 Investigating a Simple Class (Random)


We'll examine now a very simple class of object, the random number generator class called Random. We will briefly look at how to make a random generator object as well as investigate a few methods that are available.

We've seen earlier how to get a random number using the Math class.  Now we'll see a richer form of generating random numbers.  To do this, we'll make use of the Random class in the java.util package.  This class represents a random number generator to which we can repeatedly ask for random numbers.

There are two ways to make a random number generator.  The first is the default constructor, the second allows the user to specify a seed.
 
Random     r = new Random();
Random     r = new Random(seedValue);

Random numbers are actually not random at all.  It is impossible to get a truly random number from a computer.  Instead, the random number generator generates a sequence of numbers that only appear to be random (to the average person).  The seedValue is the starting point for this sequence of random numbers.  If we make a generator with the same seed value each time we run the program, we'll get the same sequence of random numbers (this is good for testing).

We can set the seed of a generator rafter we make it as follows:

We can then ask it for random numbers: This returns a random number of the type asked for (i.e., integer, long, float or double).  For integers and longs, we get back a number within a large range.  If we want to get a number in the range of a to b, then we should do as follows: For instance, to simulate the rolling of a die (single dice), we need a random number from 1 to 6: To simulate the rolling of two dice, we need a number from 2 to 12: For floats and doubles, the random number x that we actually get back lies in the range 0.0 <= x < 1.0.  That is, it is always a positive (or zero) number less than 1. To simulate the rolling of a single die using a random float we do this:
 3.8 Investigating the Date and Calendar Classes


It is often necessary to use dates and times when programming.  We see this easily since there was a lot of "hype" surrounding the "year 2000 problem".  Lets take a look at the Date class provided in the java.util package.  The Date class allows us to make data objects that incorporate time as well.

To make a new instance of Date we do the following:
 
Date    today = new Date();

This generates an instance of Date with the current time and date of the computer's clock.  In the class Date itself, there is no easy way to create a specific date (e.g., February 29, 1992).  To do this, we'll have to make use of another class called Calendar.

What does a date look like ?  Well, consider the following code:

import java.util.Date;
public class DateTest {
    public static void main (String args[]) {
        System.out.println(new Date());
    }
}


Here is the output:
 
Wed Sep 09 11:05:35 EDT 1998

It shows the day, month, date, hours, minutes, seconds, zone and year of the Date object.

Now how do we make a specific date ?  Use the Calendar class.  The class message getInstance() returns an instance of Calendar:
 
Calendar    today = Calendar.getInstance();

Once we have a Calendar instance,  we can  use the get() method to obtain information about some specific attribute of the date as follows:

Here are the valid attributes which are class variables in Calendar: We can then use set(anAttribute, aValue) to set the value of an attribute as follows: We can also use the set() method with 3, 4, or 5 parameters as follows:
 
aCalendar.set(int year, int month, int day);
aCalendar.set(int year, int month, int day, int hour, int minute);
aCalendar.set(int year, int month, int day, int hour, int minute, int seconds);

In order to display the date and time, we must get the time from the Calendar using getTime().

Of course we can always use get() to display a specific field.

Example

Here is a simple example that creates two dates.  One representing today, the other representing a future date:

Here is the output:
 
Here is today:
Wed Sep 09 15:15:50 EDT 1998
1998
8
9
Here is the future:
Fri Mar 05 00:00:00 EST 2010
2010
2
5

Notice that the months start at 0, not 1.   Although we can create and display simple dates, we have not done any manipulation at all.  For instance, we may want to know how many working days there are between two dates.  There are many more functions in the Calendar and Date classes, but we will not discuss them any further here.  You'd have to look at the API for the Date, Calendar and SimpleDateFormat classes.

For instance, the following program tests out some simple date formats:

import java.util.Date;
import java.text.SimpleDateFormat;
public class DateFormatTest {
    public static void main (String args[]) {
        Date aDate = new Date();
        System.out.println(aDate);
        System.out.println(new SimpleDateFormat("yyyy/MM/dd").format(aDate));
        System.out.println(new SimpleDateFormat("yy/MM/dd").format(aDate));
        System.out.println(new SimpleDateFormat("MM/dd").format(aDate));
        System.out.println(new SimpleDateFormat("MMM dd,yyyy").format(aDate));
        System.out.println(new SimpleDateFormat("MMMM dd,yyyy").format(aDate));
    }
}
Here is the output from my testing that I ran the afternoon of September 11th:
 
Mon Sep 11 14:33:19 EDT 2000
2000/09/11
00/09/11
09/11
Sep 11,2000
September 11, 2000