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.
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.
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);