9. Object Wrapup

Key concepts

  1. Interface
  2. Initializing Objects: the Constructor
  3. Information hiding (a little)

Introduction

At this point, we're pretty close to having a complete and reasonably useful BankAccount class. What's left to do? It's worth thinking about the operations that we really need to make our BankAccount objects really useful. What are they? Mostly the answer to this question comes from thinking about the things we do with our real life bank accounts.
  1. opening a new account
  2. withdrawing money
  3. depositing money
  4. checking our balance
  5. transferring a balance to a new account
While there are surely more operations that we might want to perform on an account, this set seems like a reasonable place to start. We will often talk about the set of operations supported by an object as the object's interface. An interface is just a set of method signatures and commentary about the behavior of those methods that allows us to reason about the behavior of an object, without having to worry about the internal details (representation) of the object.

Constructors

Let's go ahead and wrestle with just the first operation, the opening of a new account. Currently, when we create a new account, we must initialize all of its pieces to reasonable initial values, as follows:
BankAccount account = new BankAccount();
account.balance = 100.25;
account.number = 1234;
account.name = "Bob";
This four step operation is clearly less than conventient. Fortunately, Java provides us with a way to set the values of the pieces of a new BankAccount object at creation time. It's called the constructor. A constructor is like a special method whose only purpose is to initialize the instance variables of a new object. Here's an example:
class BankAccount {
  int number;
  double balance;
  String name;

  BankAccount(int acctNumber, double initBalance, String acctName) {
    this.number = acctNumber;
    this.balance = initBalance;
    this.name = acctName;
  }
}
Notice that the name of the constructor is the same as the name of the class, and that the constructor does not specifiy a return type. In a sense, the return type of the constructor is the type of the class itself. Let's use our new constructor:
BankAccount account = new BankAccount(1234, 100.25, "Bob");
That's it! Notice that the constructor is only used as part of a "new" expression. When the new object is created, it is invoked to set the values of the instance variables of the new object. What happens if we try this now?
BankAccount anAccount = new BankAccount();
The compiler will not like this. The reason is that when we define a class, Java provides us with a "default" constructor (with no arguments). However, if we go on and define a constructor that takes arguments, then Java no longer lets us use the default constructor. (At this point, if want an argument-less constructor then we must explicitly provide one. We'll see how in a later lesson.) This seems annoying, but why might it actually be a good thing?

Rounding out the interface

Let's go ahead and add the rest of the operations to our class definition. Many of these are similar to the ones we explored in the previous lesson.
class BankAccount {
  int number;
  double balance;
  String name;

  BankAccount(int acctNumber, double initBalance, String acctName) {
    this.number = acctNumber;
    this.balance = initBalance;
    this.name = acctName;
  }

  void deposit(double amount) {
    this.balance = this.balance + amount;
  }

  double getBalance() {
    return this.balance;
  }

  void withdraw(double amount) {
    this.balance = this.balance - amount;
  }

  void transferFrom(BankAccount other, double amount) {
    other.withdraw(amount);
    this.deposit(amount);
  }
}
The first two methods don't contain any real mysteries. Can you see a potential problem with the third method (withdraw)? Would a real bank be happy with this kind of account? We need for our BankAccount object to do something reasonable in the case that there is not enough money in the account. We'll present some solutions for managing situations such as these in the next lesson.

Finally, the transferFrom method shows us an example of a method that relies on other methods we have written. This method takes two arguments: another BankAccount object and an amount to transfer. It first withdraws the specified amount from the other account, and then deposits that amount into the receiver account. Now let's look at some BankAccount objects in action:

BankAccount account1 = new BankAccount(1234, 225.34, "Bill");
BankAccount account2 = new BankAccount(23455, 125.0, "George");
account1.transferFrom(account2, 75.0);
double newBalance = account2.getBalance();
What are the new balances of the two accounts after the above statements are evaluated?

Information Hiding

Notice that we're hiding the representation of our BankAccount objects behind an interface that consists of a set of operations that we can perform on such objects. This is generally regarded as being a good engineering principle, because it gives the implementor of the BankAccount class considerable flexibility in terms of how she might want to represent individual bank account objects.

As a trivial example, consider the different ways we could represent the balance of a BankAccount object:

  1. total number of pennies (an integer)
  2. number of dollars (a rational number)
  3. an integer number of dollars and integer number of pennies
Each approach may have its benefits and drawbacks, but the decision of which representation to use should be internal to the BankAccount class. It should have no impact on clients of that class. In other words, software they write that uses BankAccount objects should not need to worry about how the balance of an account is represented internally.

We can enforce the visibility rules in Java by modifying our declarations with the keywords public and private. As a rule of thumb, we'll want to make all instance variables private and most of our methods public:

public class BankAccount {
  private int number;
  private double balance;
  private String name;

  public BankAccount(int acctNumber, double initBalance, String acctName) {
    this.number = acctNumber;
    this.balance = initBalance;
    this.name = acctName;
  }

  public void deposit(double amount) {
    this.balance = this.balance + amount;
  }

  // And so on with our other methods...
}
Now let's see what happens when we try to directly access instance variables of a BankAccount object:
BankAccount account = new BankAccount(1234, 225.34, "Bill");

account.balance = 1000.0;      // ERROR!!
The compiler or interpreter will not allow this kind of access now, and will flag the error as such. Notice also that we also labeled the class itself as being public. This allows our class to be used anywhere within the universe of Java objects.

Documenting the Interface

Finally, each method should be preceded by some commentary describing what the method does; the meaning and allowable values of its parameters; the meaning of its return value, if any; and any assumptions it makes about the world. We're going to introduce a convention for writing comments that we'll use throughout the rest of the text.
/**
  The BankAccount class implements a simple bank account.
  @author Ben Dugan
  @version 11/11/2001
*/
public class BankAccount {
  private int number;
  private double balance;
  private String name;

  /** 
    Create a new BankAccount object, with the given initial values.
    @param acctNumber an account number to use
    @param initBalance an initial balance
    @param acctName the name of the account owner
  */
  public BankAccount(int acctNumber, double initBalance, String acctName) {
    this.number = acctNumber;
    this.balance = initBalance;
    this.name = acctName;
  }

  /** 
    Increase the balance by the given amount.
    @param amount the amount to deposit  
  */
  public void deposit(double amount) {
    this.balance = this.balance + amount;
  }

  /** 
    Answer the current balance. 
    @return the current balance
  */
  public double getBalance() {
    return this.balance;
  }

  /** 
    Decrease the balance by the given amount.  This method does not
    guard against overdrawing the account.
    @param amount the amount to withdraw
  */ 
  public void withdraw(double amount) {
    this.balance = this.balance - amount;
  }

  /** 
    Transfer money between accounts.
    @param other a BankAccount to tranfer from
    @param amount an amount to transfer
  */
  public void transferFrom(BankAccount other, double amount) {
    other.withdraw(amount);
    this.deposit(amount);
  }
}
Notice that we are using a new style of writing comments. Any text between /* and */ is ignored by Java compilers or interpreters. Furthermore, by starting our comments with an additional asterisk (/**) we are saying that we want these comments to be consumed by a documentation generating tool that can consume our program text file and produce nicely formatted, human readable documentation about our class. We are also making use of special tags, such as @author, @version, @param and @return, which tell the documentation tool that we are expressing information regarding the author, the version number of the class, a parameter, or the return value, respectively. The documentation tool will lay out and format these pieces of documentation differently.
Ben Dugan & UW-CSE, Copyright (c) 2001.