Chapter 7
Thinking in Terms of Classes and Objects
In This Chapter
Thinking like a real object-oriented programmer
Passing values to and from methods
Hiding details in your object-oriented code
As a computer book author, I’ve been told this over and over again — I shouldn’t expect people to read sections and chapters in their logical order. People jump around, picking what they need and skipping what they don’t feel like reading. With that in mind, I realize that you may have skipped Chapter 1. If that’s the case, please don’t feel guilty. You can compensate in just 60 seconds by reading the following information culled from Chapter 1:
Because Java is an object-oriented programming language, your primary goal is to describe classes and objects. A class is the idea behind a certain kind of thing. An object is a concrete instance of a class. The programmer defines a class, and from the class definition, the computer makes individual objects.
Of course, you can certainly choose to skip over the 60-second summary paragraph. If that’s the case, you may want to recoup some of your losses. You can do that by reading the following two-word summary of Chapter 1:
Classes; objects.
Defining a Class (What It Means to Be an Account)
What distinguishes one bank account from another? If you ask a banker this question, you hear a long sales pitch. The banker describes interest rates, fees, penalties — the whole routine. Fortunately for you, I’m not interested in all that. Instead, I want to know how my account is different from your account. After all, my account is named Barry Burd, trading as Burd Brain Consulting, and your account is named Jane Q. Reader, trading as Budding Java Expert. My account has $24.02 in it. How about yours?
When you come right down to it, the differences between one account and another can be summarized as values of variables. Maybe there’s a variable named balance. For me, the value of balance
is 24.02
. For you, the value of balance
is 55.63
. The question is, when writing a computer program to deal with accounts, how do I separate my balance
variable from your balance
variable?
The answer is to create two separate objects. Let one balance
variable live inside one of the objects and let the other balance
variable live inside the other object. While you’re at it, put a name
variable and an address
variable in each of the objects. And there you have it — two objects, and each object represents an account. More precisely, each object is an instance of the Account
class. (See Figure 7-1.)
So far, so good. However, you still haven’t solved the original problem. In your computer program, how do you refer to my balance
variable, as opposed to your balance
variable? Well, you have two objects sitting around, so maybe you have variables to refer to these two objects. Create one variable named myAccount and another variable named yourAccount. The myAccount
variable refers to my object (my instance of the Account
class) with all the stuff that’s inside it. To refer to my balance, write
myAccount.balance
To refer to my name, write
myAccount.name
Then yourAccount.balance
refers to the value in your object’s balance
variable, and yourAccount.name refers to the value of your object’s name
variable. To tell the computer how much I have in my account, you can write
myAccount.balance = 24.02;
To display your name on the screen, you can write
out.println(yourAccount.name);
These ideas come together in Listings 7-1 and 7-2. Here’s Listing 7-1:
Listing 7-1: What It Means to Be an Account
public class Account {
String name;
String address;
double balance;
}
The Account
class in Listing 7-1 defines what it means to be an Account
. In particular, Listing 7-1 tells you that each of the Account
class’s instances has three variables — name
, address
, and balance
. This is consistent with the information in Figure 7-1. Java programmers have a special name for variables of this kind (variables that belong to instances of classes). Each of these variables — name
, address
, and balance
— is called a field.
If you’ve been grappling with the material in Chapters 4 through 6, the code for class Account
(Listing 7-1) may come as a big shock to you. Can you really define a complete Java class with only four lines of code (give or take a curly brace)? You certainly can. In fact, the Account
class in Listing 7-1 is quite representative of what Java programmers think of when they think class. A class is a grouping of existing things. In the Account
class of Listing 7-1, those existing things are two String
values and a double
value.
A public class
According to the code in Listing 7-1, the Account
class is public
. A public class is available for use by all other classes. So, for example, if you write an ATMController program in some remote corner of cyberspace, then your ATMController program can contain code, such as myAccount.balance = 24.02
, making use of the Account
class declared in Listing 7-1. (Of course, your code has to know where in cyberspace I’ve stored the code in Listing 7-1, but that’s another story.)
In the next section, Listing 7-2 finally manages to use the Account
class. Therefore, you might say to yourself, “The Account
class has to be public because another class (the code in Listing 7-2) uses the Account
class.” Unfortunately, the real lowdown about public classes is a bit more complicated. In fact, when the planets align themselves correctly, one class can make use of another class’s code, even though the other class isn’t public. And in this chapter, the word public
in Listing 7-1 isn’t necessary. Listings 7-1 and 7-2 run just as well with or without the Account
class’s being public.
In Listing 7-1, declaring the Account
class to be public makes me feel good. Yes, programmers do certain things in order to feel good. In this example, my esthetic sense of goodness comes from the fact that an Account
class is useful to many other programmers. When I create a class that declares something useful and nameable — an Account
, an Engine
, a Customer
, a BrainWave
, a Headache
, or a SevenLayerCake
class — I declare the class to be public.
Declaring variables and creating objects
A young fellow approaches me while I’m walking down the street. He tells me to print “You’ll love Java!” so I print those words. If you must know, I print them with chalk on the sidewalk. But where I print the words doesn’t matter. What matters is that some guy issues instructions, and I follow the instructions.
Later that day, an elderly woman sits next to me on a park bench. She says, “An account has a name, an address, and a balance.” And I say, “That’s fine, but what do you want me to do about it?” In response she just stares at me, so I don’t do anything about her account pronouncement. I just sit there, she sits there, and we both do absolutely nothing.
Listing 7-1 is like the elderly woman. This listing defines what it means to be an Account
, but the listing doesn’t tell me to do anything with my account, or with anyone else’s account. In order to do something, I need a second piece of code. I need another class — a class that contains a main
method. Fortunately, while the woman and I sit quietly on the park bench, a young child comes by with Listing 7-2.
Listing 7-2: Dealing with Account Objects
import static java.lang.System.out;
class UseAccount {
public static void main(String args[]) {
Account myAccount;
Account yourAccount;
myAccount = new Account();
yourAccount = new Account();
myAccount.name = “Barry Burd”;
myAccount.address = “222 Cyberspace Lane”;
myAccount.balance = 24.02;
yourAccount.name = “Jane Q. Public”;
yourAccount.address = “111 Consumer Street”;
yourAccount.balance = 55.63;
out.print(myAccount.name);
out.print(“ (“);
out.print(myAccount.address);
out.print(“) has $”);
out.print(myAccount.balance);
out.println();
out.print(yourAccount.name);
out.print(“ (“);
out.print(yourAccount.address);
out.print(“) has $”);
out.print(yourAccount.balance);
}
}
Taken together, the two classes — Account
and UseAccount
— form one complete program. The code in Listing 7-2 defines the UseAccount
class, and the UseAccount
class has a main
method. This main
method has variables of its own — yourAccount
and myAccount
.
In a way, the first two lines inside the main
method of Listing 7-2 are misleading. Some people read Account yourAccount
as if it’s supposed to mean, “yourAccount
is an Account
,” or “The variable yourAccount
refers to an instance of the Account
class.” That’s not really what this first line means. Instead, the line Account yourAccount
means, “If and when I make the variable yourAccount
refer to something, that something will be an instance of the Account
class.” So, what’s the difference?
The difference is that simply declaring Account yourAccount
doesn’t make the yourAccount
variable refer to an object. All the declaration does is reserve the variable name yourAccount so that the name can eventually refer to an instance of the Account
class. The creation of an actual object doesn’t come until later in the code, when the computer executes new Account()
.
When the computer executes the assignment yourAccount = new Account()
, the computer creates a new object (a new instance of the Account
class) and makes the variable yourAccount
refer to that new object. (The equal sign makes the variable refer to the new object.) Figure 7-2 illustrates the situation.
To test the claim that I made in the last few paragraphs, I added an extra line to the code of Listing 7-2. I tried to print yourAccount.name after declaring yourAccount
, but before calling new Account()
.
Account myAccount;
Account yourAccount;
out.println(yourAccount.name);
myAccount = new Account();
yourAccount = new Account();
When I tried to compile the new code, I got this error message: variable yourAccount might not have been initialized
. That settles it. Before you do new Account()
, you can’t print the name
variable of an object because an object doesn’t exist.
Initializing a variable
In Chapter 4, I announce that you can initialize a primitive type variable as part of the variable’s declaration.
int weightOfAPerson = 150;
You can do the same thing with reference type variables, such as myAccount
and yourAccount
in Listing 7-2. You can combine the first four lines in the listing’s main
method into just two lines, like this:
Account myAccount = new Account();
Account yourAccount = new Account();
If you combine lines this way, you automatically avoid the variable might not have been initialized
error that I describe in the previous section. Sometimes you find a situation in which you can’t initialize a variable. But when you can initialize, it’s usually a plus.
Using an object’s fields
After you’ve bitten off and chewed the main
method’s first four lines, the rest of the code in Listing 7-2 is sensible and straightforward. You have three lines that put values in the myAccount
object’s fields, three lines that put values in the yourAccount
object’s fields, and four lines that do some printing. Figure 7-3 shows the program’s output.
One program; several classes
Each program in Chapters 3 to 6 consists of a single class. That’s great for a book’s introductory chapters. But in real life, a typical program consists of hundreds or even thousands of classes. The program that spans Listings 7-1 and 7-2 consists of two classes. Sure, having two classes isn’t like having thousands of classes, but it’s a step in that direction.
In practice, most programmers put each class in a file of its own. When you create a program, such as the one in Listings 7-1 and 7-2, you create two files on your computer’s hard drive. Therefore, when you download this section’s example from the web, you get two separate files — Account.java
and UseAccount.java
.
Defining a Method within a Class (Displaying an Account)
Imagine a table containing the information about two accounts. (If you have trouble imagining such a thing, just look at Table 7-1.)
Table 7-1 Without Object-Oriented Programming
Name |
Address |
Balance |
Barry Burd |
222 Cyberspace Lane |
24.02 |
Jane Q. Public |
111 Consumer Street |
55.63 |
In Table 7-1, each account has three things — a name, an address, and a balance. That’s how things were done before object-oriented programming came along. But object-oriented programming involved a big shift in thinking. With object-oriented programming, each account can have a name, an address, a balance, and a way of being displayed.
In object-oriented programming, each object has its own built-in functionality. An account knows how to display itself. A string can tell you whether it has the same characters inside it as another string has. A PrintStream
instance, such as System.out
, knows how to do println
. In object-oriented programming, each object has its own methods. These methods are little subprograms that you can call to have an object do things to (or for) itself.
And why is this a good idea? It’s good because you’re making pieces of data take responsibility for themselves. With object-oriented programming, all the functionality that’s associated with an account is collected inside the code for the Account
class. Everything you have to know about a string is located in the file String.java
. Anything having to do with year numbers (whether they have two or four digits, for instance) is handled right inside the Year
class. Therefore, if anybody has problems with your Account
class or your Year
class, he or she knows just where to look for all the code. That’s great!
So imagine an enhanced account table. In this new table, each object has built-in functionality. Each account knows how to display itself on the screen. Each row of the table has its own copy of a display
method. Of course, you don’t need much imagination to picture this table. I just happen to have a table you can look at. It’s Table 7-2.
An account that displays itself
In Table 7-2, each account object has four things — a name, an address, a balance, and a way of displaying itself on the screen. After you make the jump to object-oriented thinking, you’ll never turn back. Listings 7-3 and 7-4 show programs that implements the ideas in Table 7-2.
Listing 7-3: An Account Displays Itself
import static java.lang.System.out;
public class Account {
String name;
String address;
double balance;
public void display() {
out.print(name);
out.print(“ (“);
out.print(address);
out.print(“) has $”);
out.print(balance);
}
}
Listing 7-4: Using the Improved Account Class
class UseAccount {
public static void main(String args[]) {
Account myAccount = new Account();
Account yourAccount = new Account();
myAccount.name = “Barry Burd”;
myAccount.address = “222 Cyberspace Lane”;
myAccount.balance = 24.02;
yourAccount.name = “Jane Q. Public”;
yourAccount.address = “111 Consumer Street”;
yourAccount.balance = 55.63;
myAccount.display();
System.out.println();
yourAccount.display();
}
}
A run of the code in Listings 7-3 and 7-4 looks just like a run for Listings 7-1 and 7-2. You can see the action back in Figure 7-3.
In Listing 7-3, the Account
class has four things in it — a name
, an address
, a balance
, and a display
method. These things match up with the four columns in Table 7-2. So each instance of the Account
class has a name, an address, a balance, and a way of displaying itself. The way you call these things is nice and uniform. To refer to the name stored in myAccount
, you write
myAccount.name
To get myAccount
to display itself on the screen, you write
myAccount.display()
The only difference is the parentheses.
The display method’s header
Look again at Listings 7-3 and 7-4. A call to the display
method is inside the UseAccount
class’s main
method, but the declaration of the display
method is up in the Account
class. The declaration has a header and a body. (See Chapter 3.) The header has three words and some parentheses:
The word public serves roughly the same purpose as the word public in Listing 7-1. Roughly speaking, any code can contain a call to a public method, even if the calling code and the public method belong to two different classes. In this section’s example, the decision to make the display
method public is a matter of taste. Normally, when I create a method that’s useful in a wide variety of applications, I declare the method to be public.
The word void tells the computer that when the display
method is called, the display
method doesn’t return anything to the place that called it. To see a method that does return something to the place that called it, see the next section.
The word display is the method’s name. Every method must have a name. Otherwise, you don’t have a way to call the method.
The parentheses contain all the things you’re going to pass to the method when you call it. When you call a method, you can pass information to that method on the fly. The display
method in Listing 7-3 looks strange because the parentheses in the method’s header have nothing inside them. This nothingness indicates that no information is passed to the display
method when you call it. For a meatier example, see the next section.
Sending Values to and from Methods (Calculating Interest)
Think about sending someone to the supermarket to buy bread. When you do this, you say, “Go to the supermarket and buy some bread.” (Try it at home. You’ll have a fresh loaf of bread in no time at all!) Of course, some other time, you send that same person to the supermarket to buy bananas. You say, “Go to the supermarket and buy some bananas.” And what’s the point of all this? Well, you have a method, and you have some on-the-fly information that you pass to the method when you call it. The method is named goToTheSupermarketAndBuySome. The on-the-fly information is either bread or bananas, depending on your culinary needs. In Java, the method calls would look like this:
goToTheSupermarketAndBuySome(bread);
goToTheSupermarketAndBuySome(bananas);
The things in parentheses are called parameters or parameter lists. With parameters, your methods become much more versatile. Instead of getting the same thing each time, you can send somebody to the supermarket to buy bread one time, bananas another time, and birdseed the third time. When you call your goToTheSupermarketAndBuySome
method, you decide right there what you’re going to ask your pal to buy.
And what happens when your friend returns from the supermarket? “Here’s the bread you asked me to buy,” says your friend. By carrying out your wishes, your friend returns something to you. You make a method call, and the method returns information (or a loaf of bread).
The thing returned to you is called the method’s return value. The general type of thing that is returned to you is called the method’s return type. These concepts are made more concrete in Listings 7-5 and 7-6.
Listing 7-5: An Account That Calculates Its Own Interest
import static java.lang.System.out;
public class Account {
String name;
String address;
double balance;
public void display() {
out.print(name);
out.print(“ (“);
out.print(address);
out.print(“) has $”);
out.print(balance);
}
public double getInterest(double percentageRate) {
return balance * percentageRate / 100.00;
}
}
Listing 7-6: Calculating Interest
import static java.lang.System.out;
class UseAccount {
public static void main(String args[]) {
Account myAccount = new Account();
Account yourAccount = new Account();
myAccount.name = “Barry Burd”;
myAccount.address = “222 Cyberspace Lane”;
myAccount.balance = 24.02;
yourAccount.name = “Jane Q. Public”;
yourAccount.address = “111 Consumer Street”;
yourAccount.balance = 55.63;
myAccount.display();
out.print(“ plus $”);
out.print(
myAccount.getInterest(5.00)
);
out.println(“ interest “);
yourAccount.display();
double yourInterestRate = 7.00;
out.print(“ plus $”);
double yourInterestAmount =
yourAccount.getInterest(yourInterestRate)
;
out.print(yourInterestAmount);
out.println(“ interest “);
}
}
Figure 7-4 shows the output of the code in Listings 7-5 and 7-6. In Listing 7-5, the Account
class has a getInterest
method. This getInterest
method is called twice from the main
method in Listing 7-6. The actual account balances and interest rates are different each time.
In the first call, the balance is 24.02, and the interest rate is 5.00. The first call, myAccount.getInterest(5.00)
, refers to the myAccount
object and to the values stored in the myAccount
object’s fields. (See Figure 7-5.) When this call is made, the expression balance * percentageRate / 100.00
stands for 24.02 * 5.00 / 100.00.
In the second call, the balance is 55.63, and the interest rate is 7.00. In the main
method, just before this second call is made, the variable yourInterestRate
is assigned the value 7.00
. The call itself, yourAccount.getInterest(yourInterestRate)
, refers to the yourAccount
object and to the values stored in the yourAccount
object’s fields. (Again, see Figure 7-5.) So, when the call is made, the expression balance * percentageRate / 100.00
stands for 55.63 * 7.00 / 100.00.
By the way, the main
method in Listing 7-3 contains two calls to getInterest
. One call has the literal 5.00
in its parameter list; the other call has the variable yourInterestRate
in its parameter list. Why does one call use a literal and the other call use a variable? No reason. I just want to show you that you can do it either way.
Passing a value to a method
Take a look at the getInterest
method’s header. (As you read the explanation in the next few bullets, you can follow some of the ideas visually with the diagram in Figure 7-6.)
The word double tells the computer that when the getInterest
method is called, the getInterest
method returns a double
value back to the place that called it. The statement in the getInterest
method’s body confirms this. The statement says return balance * percentageRate / 100.00
, and the expression balance * percentageRate / 100.00
has type double
. (That’s because all the things in the expression — balance
, percentageRate
, and 100.00
— have type double
.)
When the getInterest
method is called, the return
statement calculates balance * percentageRate / 100.00
and hands the calculation’s result back to the code that called the method.
The word getInterest is the method’s name. That’s the name you use to call the method when you’re writing the code for the UseAccount
class.
The parentheses contain all the things that you pass to the method when you call it. When you call a method, you can pass information to that method on the fly. This information is the method’s parameter list. The getInterest
method’s header says that the getInterest
method takes one piece of information and that piece of information must be of type double
.
public double getInterest(double
percentageRate)
Sure enough, if you look at the first call to getInterest
(down in the useAccount
class’s main
method), that call has the number 5.00
in it. And 5.00
is a double
literal. When I call getInterest
, I’m giving the method a value of type double
.
If you don’t remember what a literal is, see Chapter 4.
The same story holds true for the second call to getInterest
. Down near the bottom of Listing 7-6, I call getInterest
and feed the variable yourInterestRate
to the method in its parameter list. Luckily for me, I declared yourInterestRate
to be of type double
just a few lines before that.
When you run the code in Listings 7-5 and 7-6, the flow of action isn’t from top to bottom. The action goes from main
to getInterest
, then back to main
, then back to getInterest
, and finally back to main
again. Figure 7-7 shows the whole business.
Returning a value from the getInterest method
When the getInterest
method is called, the method executes the one statement that’s in the method’s body: a return
statement. The return
statement computes the value of balance * percentageRate / 100.00
. If balance
happens to be 24.02, and percentageRate
is 5.00, the value of the expression is 1.201
— around $1.20. (Because the computer works exclusively with 0s and 1s, the computer gets this number wrong by an ever so tiny amount. The computer gets 1.2009999999999998. That’s just something that humans have to live with.)
Anyway, after this value is calculated, the computer executes the return
, which sends the value back to the place in main
where getInterest
was called. At that point in the process, the entire method call — myAccount.getInterest(5.00)
— takes on the value 1.2009999999999998. The call itself is inside a println
:
out.println(myAccount.getInterest(5.00));
So the println
ends up with the following meaning:
out.println(1.2009999999999998);
The whole process, in which a value is passed back to the method call, is illustrated in Figure 7-8.
Making Numbers Look Good
Looking back at Figure 7-4, you may be concerned that the interest on my account is only $1.2009999999999998. Seemingly, the bank is cheating me out of two hundred-trillionths of a cent. I should go straight there and demand my fair interest. Maybe you and I should go together. We’ll kick up some fur at that old bank and bust this scam right open. If my guess is correct, this is part of a big salami scam. In a salami scam, someone shaves little slices off millions of accounts. People don’t notice their tiny little losses, but the person doing the shaving collects enough for a quick escape to Barbados (or for a whole truckload of salami).
But, wait a minute! Nothing is motivating you to come with me to the bank. Checking back at Figure 7-4, I see that you’re way ahead of the game. According to my calculations, the program overpays you by three hundred-trillionths of a cent. Between the two of us, we’re ahead by a hundred-trillionth of a cent. What gives?
Well, because computers use 0s (zeros) and 1s and don’t have an infinite amount of space to do calculations, such inaccuracies as the ones shown in Figure 7-4 are normal. The quickest solution is to display the inaccurate numbers in a more sensible fashion. You can round the numbers and display only two digits beyond the decimal point, and some handy tools from Java’s API (Application Programming Interface) can help. Listing 7-7 shows the code, and Figure 7-9 displays the pleasant result.
Listing 7-7: Making Your Numbers Look Right
import static java.lang.System.out;
class UseAccount {
public static void main(String args[]) {
Account myAccount = new Account();
Account yourAccount = new Account();
myAccount.balance = 24.02;
yourAccount.balance = 55.63;
double myInterest = myAccount.getInterest(5.00);
double yourInterest = yourAccount.getInterest(7.00);
out.printf(“$%4.2f
”, myInterest);
out.printf(“$%5.2f
”, myInterest);
out.printf(“$%.2f
”, myInterest);
out.printf(“$%3.2f
”, myInterest);
out.printf(“$%.2f $%.2f”,
myInterest, yourInterest);
}
}
Listing 7-7 uses a handy method named printf
. When you call printf
, you always put at least two parameters inside the call’s parentheses.
The first parameter is a format string.
The format string uses funny-looking codes to describe exactly how the other parameters are displayed.
All the other parameters (after the first) are values to be displayed.
Look at the last printf
call of Listing 7-7. The first parameter’s format string has two placeholders for numbers. The first placeholder (%.2f
) describes the display of myInterest
. The second placeholder (another %.2f
) describes the display of yourInterest
. To find out exactly how these format strings work, see Figures 7-10 through 7-14.
Hiding Details with Accessor Methods (Why You Shouldn’t Micromanage a Bank Teller)
Put down this book and put on your hat. You’ve been such a loyal reader that I’m taking you out to lunch!
I’ve got just one problem. I’m a bit short on cash. Would you mind if, on the way to lunch, we stopped at an automatic teller machine and picked up a few bucks? Also, we have to use your account. My account is a little low.
Fortunately, the teller machine is easy to use. Just step right up and enter your PIN. After entering your PIN, the machine asks which of several variable names you want to use for your current balance. You have a choice of balance324
, myBal
, currentBalance
, b$
, BALANCE
, asj999
, or conStanTinople
. Having selected a variable name, you’re ready to select a memory location for the variable’s value. You can select any number between 022FFF and 0555AA. (Those numbers are in hexadecimal format.) After you configure the teller machine’s software, you can easily get your cash. You did bring a screwdriver, didn’t you?
Good programming
When it comes to good computer programming practice, one word stands out above all others — simplicity. When you’re writing complicated code, the last thing you want is to deal with somebody else’s misnamed variables, convoluted solutions to problems, or clever, last-minute kludges. You want a clean interface that makes you solve your own problems and no one else’s.
In the automatic teller machine scenario that I describe earlier, the big problem is that the machine’s design forces you to worry about other people’s concerns. When you should be thinking about getting money for lunch, you’re thinking instead about variables and storage locations. Sure, someone has to work out the teller machine’s engineering problems, but the banking customer isn’t the person.
So, everything connected with every aspect of a computer program has to be simple, right? Well, no. That’s not right. Sometimes, to make things simple in the long run, you have to do lots of preparatory work up front. The people who built the automated teller machine worked hard to make sure that the machine is consumer-proof. The machine’s interface, with its screen messages and buttons, makes the machine a very complicated, but carefully designed, device.
The point is that making things look simple takes some planning. In the case of object-oriented programming, one of the ways to make things look simple is to keep code outside a class from directly using fields defined inside the class. Take a peek at the code in Listing 7-1. You’re working at a company that has just spent $10 million for the code in the Account
class. (That’s more than a million and a half per line!) Now your job is to write the UseAccount
class. You would like to write
myAccount.name = “Barry Burd”;
but doing so would be getting you too far inside the guts of the Account
class. After all, people who use an automatic teller machine aren’t allowed to program the machine’s variables. They can’t use the machine’s keypad to type the statement
balanceOnAccount29872865457 =
balanceOnAccount29872865457 + 1000000.00;
Instead, they push buttons that do the job in an orderly manner. That’s how a programmer achieves safety and simplicity.
So, to keep things nice and orderly, you need to change the Account
class from Listing 7-1 by outlawing such statements as the following:
myAccount.name = “Barry Burd”;
and
out.print(yourAccount.balance);
Of course, this poses a problem. You’re the person who’s writing the code for the UseAccount
class. If you can’t write myAccount.name or yourAccount.balance
, how are you going to accomplish anything at all? The answer lies in things called accessor methods. Listings 7-8 and 7-9 demonstrate these methods.
Listing 7-8: Hide Those Fields
public class Account {
private String name;
private String address;
private double balance;
public void setName(String n) {
name = n;
}
public String getName() {
return name;
}
public void setAddress(String a) {
address = a;
}
public String getAddress() {
return address;
}
public void setBalance(double b) {
balance = b;
}
public double getBalance() {
return balance;
}
}
Listing 7-9: Calling Accessor Methods
import static java.lang.System.out;
class UseAccount {
public static void main(String args[]) {
Account myAccount = new Account();
Account yourAccount = new Account();
myAccount.
setName(
“Barry Burd”)
;
myAccount.
setAddress(
“222 Cyberspace Lane”)
;
myAccount.
setBalance(
24.02)
;
yourAccount.
setName(
“Jane Q. Public”)
;
yourAccount.
setAddress(
“111 Consumer Street”)
;
yourAccount.
setBalance(
55.63)
;
out.print(myAccount.
getName()
);
out.print(“ (“);
out.print(myAccount.
getAddress()
);
out.print(“) has $”);
out.print(myAccount.
getBalance()
);
out.println();
out.print(yourAccount.
getName()
);
out.print(“ (“);
out.print(yourAccount.
getAddress()
);
out.print(“) has $”);
out.print(yourAccount.
getBalance()
);
}
}
A run of the code in Listings 7-8 and 7-9 looks no different from a run of Listings 7-1 and 7-2. Either program’s run is shown back in Figure 7-3. The big difference is that in Listing 7-8, the Account
class enforces the carefully controlled use of its name
, address
, and balance
fields.
Public lives and private dreams: Making a field inaccessible
Notice the addition of the word private in front of each of the Account
class’s field declarations. The word private
is a Java keyword. When a field is declared private, no code outside of the class can make direct reference to that field. So if you put myAccount.name = “Barry Burd”
in the UseAccount
class of Listing 7-9, you get the error message name has private access in Account
.
Instead of referencing myAccount.name, the UseAccount
programmer must call method myAccount.setName
or method myAccount.getName
. These methods, setName
and getName
, are called accessor methods, because they provide access to the Account
class’s name
field. (Actually, the term accessor method isn’t formally a part of the Java programming language. It’s just the term that people use for methods that do this sort of thing.) To zoom in even more, setName
is called a setter method, and getName
is called a getter method. (I bet you won’t forget that terminology!)
Notice that all the setter and getter methods in Listing 7-8 are declared to be public. This ensures that anyone from anywhere can call these two methods. The idea here is that manipulating the actual fields from outside the Account
code is impossible, but you can easily reach the approved setter and getter methods for using those fields.
Think again about the automatic teller machine. Someone using the ATM can’t type a command that directly changes the value in his or her account’s balance
field, but the procedure for depositing a million-dollar check is easy to follow. The people who build the teller machines know that if the check depositing procedure is complicated, plenty of customers will mess it up royally. So that’s the story — make impossible anything that people shouldn’t do and make sure that the tasks people should be doing are easy.
Enforcing rules with accessor methods
Go back to Listing 7-8 and take a quick look at the setName
method. Imagine putting the method’s assignment statement inside an if
statement.
public void setName(String n) {
if (!n.equals(“”)) {
name = n;
}
}
Now, if the programmer in charge of the UseAccount
class writes myAccount.setName(“”)
, the call to setName
doesn’t have any effect. Furthermore, because the name
field is private, the following statement is illegal in the UseAccount
class:
myAccount.name = “”;
Of course, a call such as myAccount.setName(“Joe Schmoe”)
still works because “Joe Schmoe”
doesn’t equal the empty string “”
.
That’s cool. With a private field and an accessor method, you can prevent someone from assigning the empty string to an account’s name
field. With more elaborate if
statements, you can enforce any rules you want.