7. Objects that Do Things

Key concepts

  1. Simple methods
  2. Methods with parameters
  3. Methods that return values

Introduction

So far, we only know how to make "passive" objects: objects that have some important qualities that intuitively belong together. For instance, we are using the BankAccount object to aggregate data that belongs to an account -- its account number, balance, and so on. While this can be useful, we'd like to make our objects be able to do interesting things (respond to messages), like the objects we saw earlier. We'd also like to be able to hide the internal details of a BankAccount object (its representation) behind a veneer of useful methods.

Simple Methods

Let's suppose that we want to assess a checking fee onto a given BankAccount object. Assuming the fee is three dollars, we can do this as follows:
BankAccount account = new BankAccount();
// set the values of its pieces:
account.balance = 100.25;
account.number = 1234;
account.name = "Bob";
account.balance = account.balance - 3.0;
This works, but not as well as we would like? Why? At this point imagine two different people. One person is the implementor of the BankAccount class. She is responsible for the design, implementation, and maintinence of of the BankAccount class. The other person is a client of the BankAccount class. They use BankAccount objects as an existing abstraction in a system they are building. In small programs, it is at times difficult to distinguish between these two personalities, because the same physical person is often both implementor and client of a given class. However, in large-scale projects consisting of millions of lines of code, there are often sharp divisions between the clients and implementors of given classes. Whatever the size of the project, it is helpful to imagine this split personality whenever you design and build a new class.

With this division in mind, we don't necessarily want to show the client the inside of the BankAccount object. Why not? Because it violates the abstraction we want to develop. We don't want clients to have to know too much about the internal structure of a BankAccount. We don't want them to know not because it's a trade secret, but because not having to know about the internal structure frees them to think about BankAccounts at the appropriate level -- the abstract level. Maybe it's better if we give the responsibility for assessing the monthly fee to the BankAccount object itself. In this view, a BankAccount literally knows how to deduct a fee from its own balance.

Let's modify the BankAccount class by adding a single method to its definition:

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

  void assessCheckingFee() {
    this.balance = this.balance - 3.0;
  }
}
We've just written a method for the BankAccount class. There are many things going on in our first method definition, so lets take it apart slowly. The basic pattern for a method definition is the following:
<return-type> <method-name> (<parameter-list>) {
  <body-statements>
}
Since every method definition must be contained inside of a class definition, we can refine our pattern for a class definition now:
class <class-name> {
  <instance variable declarations>
  <method definitions>
}
In the above example, we've defined just about the simplest method possible. It is a method that takes no arguments, and hence has an empty parameter list. Furthermore, it returns no value, and hence has a return type called void, which is a special name in Java used for just that purpose -- methods that do not return any values. We'll see examples of methods that actually return values and take arguments shortly, but let's use the method we just defined:
BankAccount account = new BankAccount();
// set the values of its pieces:
account.balance = 100.25;
account.number = 1234;
account.name = "Bob";

// now assess the checking fee:
account.assessCheckingFee();
After we send the assessCheckingFee message to the account, the balance in the account is modified -- its state is changed. After the message is sent, the balance should be $97.25. Notice also how much more readable this program fragment is: we are sending the account object a nicely named message, which makes it clear exactly what is happening to the object: a fee is being assessed. This is much more meaningful than a seemingly random assignment statement that is subtracting three dollars from the previous balance.

At this point, only one real mystery about our assessCheckingFee method remains: what about that mysterious word, this? The word this is a special, implicitly defined name that refers to the object that is receiving the message. We use it to access, using the "dot" notation, the names that belong to the object that receives the message.

Methods with Parameters

At this point, our BankAccount class is still not very useful. We can create new BankAccount objects and assess fees on the account, but not much else. Clearly, only bankers would be happy using these kinds of bank accounts! To have a truly useful bank account, we need to be able to make deposits into the account. Again, we can currently do this simply by setting certain values, as we've done above:
account.balance = account.balance + 15.0;
This is a less than wonderful solution because it again forces the "user" of the bank account to manipulate the internal pieces of the account. We'd like to be able to say something like this:
account.deposit(15.0);
In other words, we'd like to send a message called deposit that takes a single argument, an amount to deposit. This method should "grow" the current balance of the account by that amount. Let's extend our BankAccount class by adding a new method:
class BankAccount {
  int number;
  double balance;
  String name;

  void assessCheckingFee() {
    this.balance = this.balance - 3.0;
  }

  void deposit(double amount) {
    this.balance = this.balance + amount;
  }
}
Notice the similarities between our new method, deposit, and the assessCheckingFee. Both methods have a return type of void -- they do not return any values. Both methods manipulate the state of the object -- they change the value of balance. The only real difference is that the deposit method changes the value of balance by an amount specified by the parameter. In the parentheses after the method name, we've added a single parameter declaration, which is very similar to name declarations that we've seen before: it contains a type and a name of our choosing. When the method is invoked (the message is sent) that name will be bound to the value of the argument.

Methods that return values

Suppose we want to figure out how much our account would be worth if we moved it to Europe. Of course, the currency of the European Union is not dollars, but rather the Euro. We'd like to be able to ask our account what its balance would be in Euros. In other words, we'd like to send a message of the following form:
double euros = account.balanceInEuros();
We can further modify our class to support this kind of operation:
class BankAccount {
  int number;
  double balance;
  String name;

  double balanceInEuros() {
    return this.balance * 1.13;
  }

  // other method definitions here
}
We've just added a method that takes no arguments and returns a value of type double. We've specified the return type, by adding the word double before the method name. We have also introduced the Java reserved word return that evaluates its expression and causes the value to be returned to the place the method was invoked.

There is of course a problem with our method. Currency conversion rates change on a frequent basis, and we have hard-coded the conversion rate between Dollars and Euros into our method. A better solution is to further abstract our method, and parameterize it by the conversion rate. This also makes the method useful for converting into any currency, given that we know the conversion rate. Let's replace our old method with an improved one:

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

  double convertBalance(double foreignUnitsPerDollar) {
    return this.balance * foreignUnitsPerDollar;
  }

  // other method definitions here
}
We can now calculate our balance in Euros, or in another currency, such as Baht, the currency of Thailand:
double eurosPerDollar = 1.17;
double bahtPerDollar = 45.39;
double euros = account.convertBalance(eurosPerDollar);
double baht = account.convertBalance(bahtPerDollar);

Ben Dugan & UW-CSE, Copyright (c) 2001.