Chapter 8
Saving Time and Money: Reusing Existing Code
In This Chapter
Adding new life to old code
Tweaking your code
Making changes without spending a fortune
Once upon a time, there was a beautiful princess. When the princess turned 25 (the optimal age for strength, good looks, and fine moral character), her kind father brought her a gift in a lovely golden box. Anxious to know what was in the box, the princess ripped off the golden wrapping paper.
When the box was finally opened, the princess was thrilled. To her surprise, her father had given her what she had always wanted — a computer program that always ran correctly. The program did everything the princess wanted and did it all exactly the way she wanted it to be done. The princess was happy, and so was her kind, old father.
As time went on, the computer program never failed. For years on end, the princess changed her needs, expected more out of life, made increasing demands, expanded her career, reached for more and more fulfillment, juggled the desires of her husband and her kids, stretched the budget, and sought peace within her soul. Through all this, the program remained her steady, faithful companion.
As the princess grew old, the program became old along with her. One evening, as she sat by the fireside, she posed a daunting question to the program. “How do you do it?” she asked. “How do you manage to keep giving the right answers, time after time, year after year?”
“Clean living,” replied the program. “I swim 20 apps each day, I take C++ to Word off viruses, I avoid hogarithmic algorithms, I link Java in moderation, I say GNU to bugs, I don’t smoke to backup, and I never byte off more than I can queue.”
Needless to say, the princess was stunned.
Defining a Class (What It Means to Be an Employee)
Wouldn’t it be nice if every piece of software did just what you wanted it to do? In an ideal world, you could just buy a program, make it work right away, plug it seamlessly into new situations, and update it easily whenever your needs change. Unfortunately, software of this kind doesn’t exist. (Nothing of this kind exists.) The truth is that no matter what you want to do, you can find software that does some of it, but not all of it.
This is one of the reasons why object-oriented programming has been so successful. For years, companies were buying prewritten code only to discover that the code didn’t do what they wanted it to do. So what did the companies do about it? They started messing with the code. Their programmers dug deep into the program files, changed variable names, moved subprograms around, reworked formulas, and generally made the code worse. The reality was that if a program didn’t already do what you wanted it to do (even if it did something ever so close to what you wanted), you could never improve the situation by mucking around inside the code. The best option was always to chuck the whole program (expensive as that was) and start all over again. What a sad state of affairs!
With object-oriented programming, a big change has come about. At its heart, an object-oriented program is made to be modified. With correctly written software, you can take advantage of features that are already built-in, add new features of your own, and override features that don’t suit your needs. And the best part is that the changes you make are clean. No clawing and digging into other people’s brittle program code. Instead, you make nice, orderly additions and modifications without touching the existing code’s internal logic. It’s the ideal solution.
The last word on employees
When you write an object-oriented program, you start by thinking about the data. You’re writing about accounts. So what’s an account? You’re writing code to handle button clicks. So what’s a button? You’re writing a program to send payroll checks to employees. What’s an employee?
In this chapter’s first example, an employee is someone with a name and a job title. Sure, employees have other characteristics, but for now I stick to the basics. The code in Listing 8-1 defines what it means to be an employee.
Listing 8-1: What Is an Employee?
import static java.lang.System.out;
public class Employee {
private String name;
private String jobTitle;
public void setName(String nameIn) {
name = nameIn;
}
public String getName() {
return name;
}
public void setJobTitle(String jobTitleIn) {
jobTitle = jobTitleIn;
}
public String getJobTitle() {
return jobTitle;
}
public void cutCheck(double amountPaid) {
out.printf(“Pay to the order of %s “, name);
out.printf(“(%s) ***$”, jobTitle);
out.printf(“%,.2f
”, amountPaid);
}
}
According to Listing 8-1, each employee has seven features. Two of these features are fairly simple. Each employee has a name and a job title. (In Listing 8-1, the Employee
class has a name
field and a jobTitle
field.)
And what else does an employee have? Each employee has four methods to handle the values of the employee’s name and job title. These methods are setName
, getName
, setJobTitle
, and getJobTitle
. I explain methods like these (accessor methods) in Chapter 7.
On top of all that, each employee has a cutCheck
method. The idea is that the method that writes payroll checks has to belong to one class or another. Because most of the information in the payroll check is customized for a particular employee, you may as well put the cutCheck
method inside the Employee
class.
For details about the printf
calls in the cutCheck
method, see the section entitled “Cutting a check,” later in this chapter.
Putting your class to good use
The Employee
class in Listing 8-1 has no main
method, so there’s no starting point for executing code. To fix this deficiency, the programmer writes a separate program with a main
method and uses that program to create Employee
instances. Listing 8-2 shows a class with a main
method — one that puts the code in Listing 8-1 to the test.
Listing 8-2: Writing Payroll Checks
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
class DoPayroll {
public static void main(String args[])
throws IOException {
Scanner diskScanner =
new Scanner(new File(“EmployeeInfo.txt”));
for (int empNum = 1; empNum <= 3; empNum++) {
payOneEmployee(diskScanner);
}
}
static void payOneEmployee(Scanner aScanner) {
Employee anEmployee = new Employee();
anEmployee.setName(aScanner.nextLine());
anEmployee.setJobTitle(aScanner.nextLine());
anEmployee.cutCheck(aScanner.nextDouble());
aScanner.nextLine();
}
}
The DoPayroll
class in Listing 8-2 has two methods. One of the methods, main
, calls the other method, payOneEmployee
, three times. Each time around, the payOneEmployee
method gets stuff from the EmployeeInfo.txt
file and feeds this stuff to the Employee
class’s methods.
Here’s how the variable name anEmployee is reused and recycled:
The first time that payOneEmployee
is called, the statement anEmployee = new Employee()
makes anEmployee
refer to a new object.
The second time that payOneEmployee
is called, the computer executes the same statement again. This second execution creates a new incarnation of the anEmployee
variable that refers to a brand-new object.
The third time around, all the same stuff happens again. A new anEmployee
variable ends up referring to a third object.
The whole story is pictured in Figure 8-1.
Cutting a check
Listing 8-1 has three printf
calls. Each printf
call has a format string (like “(%s) ***$”
) and a variable (like jobTitle
). Each format string has a placeholder (like %s
) that determines where and how the variable’s value is displayed.
For example, in the second printf
call, the format string has a %s
placeholder. This %s
holds a place for the jobTitle
variable’s value. According to Java’s rules, the notation %s
always holds a place for a string and, sure enough, the variable jobTitle
is declared to be of type String
in Listing 8-1. Parentheses and some other characters surround the %s
placeholder, so parentheses surround each job title in the program’s output. (See Figure 8-2.)
Back in Listing 8-1, notice the comma inside the %,.2f
placeholder. The comma tells the program to use grouping separators. That’s why, in Figure 8-2, you see $5,000.00
, $7,000.00
, and $10,000.00
instead of $5000.00
, $7000.00
, and $10000.00
.
Working with Disk Files (A Brief Detour)
In previous chapters, programs read characters from the computer’s keyboard. But the code in Listing 8-2 reads characters from a specific file. The file (named EmployeeInfo.txt) lives on your computer’s hard drive.
This EmployeeInfo.txt
file is like a word processing document. The file can contain letters, digits, and other characters. But unlike a word processing document, the EmployeeInfo.txt
file contains no formatting — no italics, no bold, no font sizes, nothing of that kind.
The EmployeeInfo.txt
file contains only ordinary characters — the kinds of keystrokes that you type while you play a guessing game from Chapters 5 or 6. Of course, getting guesses from a user’s keyboard and reading employee data from a disk file aren’t exactly the same. In a guessing game, the program displays prompts, such as Enter an int from 1 to 10
. The game program conducts a back-and-forth dialogue with the person sitting at the keyboard. In contrast, Listing 8-2 has no dialogue. This DoPayroll
program reads characters from a hard drive and doesn’t prompt or interact with anyone.
Most of this chapter is about code reuse. But Listing 8-2 stumbles upon an important idea — an idea that’s not directly related to code reuse. Unlike the examples in previous chapters, Listing 8-2 reads data from a stored disk file. So in the following sections, I take a short side trip to explore disk files.
Storing data in a file
The code in Listing 8-2 doesn’t run unless you have some employee data sitting in a file. Listing 8-2 says that this file is EmployeeInfo.txt
. So before running the code of Listing 8-2, I created a small EmployeeInfo.txt
file. The file is shown in Figure 8-3; refer to Figure 8-2 for the resulting output.
Copying and pasting code
In almost any computer programming language, reading data from a file can be tricky. You add extra lines of code to tell the computer what to do. Sometimes you can copy and paste these lines from other peoples’ code. For example, you can follow the pattern in Listing 8-2:
/*
* The pattern in Listing 8-2
*/
import java.util.Scanner;
import java.io.File;
import java.io.IOException;
class
SomeClassName
{
public static void main(String args[])
throws IOException
{
Scanner
scannerName
=
new Scanner(
new File(“
SomeFileName
”
));
//
Some code goes here
scannerName
.nextInt();
scannerName
.nextDouble();
scannerName
.next();
scannerName
.nextLine();
//
Some code goes here
}
}
You want to read data from a file. You start by imagining that you’re reading from the keyboard. Put the usual Scanner
and next
codes into your program. Then add some extra items from the Listing 8-2 pattern:
Add two new import
declarations — one for java.io.File
and another for java.io.IOException
.
Type throws IOException in your method’s header.
Type new File(“”) in your call to new Scanner
.
Take a file that’s already on your hard drive. Type that filename inside the quotation marks.
Take the word that you use for the name of your scanner. Reuse that word in calls to next
, nextInt
, nextDouble
, and so on.
Occasionally, copying and pasting code can get you into trouble. Maybe you’re writing a program that doesn’t fit the simple Listing 8-2 pattern. You need to tweak the pattern a bit. But in order to tweak the pattern, you need to understand some of the ideas behind the pattern.
That’s how the next section comes to your rescue. It covers some of these ideas.
Reading from a file
In previous chapters, programs read characters from the computer’s keyboard. These programs use things like Scanner
, System.in
, and nextDouble
— things defined in Java’s API. The DoPayroll
program in Listing 8-2 puts a new spin on this story. Instead of reading characters from the keyboard, the program reads characters from the EmployeeInfo.txt
file. The file lives on your computer’s hard drive.
To read characters from a file, you use some of the same things that help you read characters from the keyboard. You use Scanner
, nextDouble
, and other goodies. But in addition to these goodies, you have a few extra hurdles to jump. Here’s a list:
You need a new File
object. To be more precise, you need a new instance of the API’s File
class. You get this new instance with code like
new File(“EmployeeInfo.txt”)
The stuff in quotation marks is the name of a file — a file on your computer’s hard drive. The file contains characters like those shown previously in Figure 8-3.
At this point, the terminology makes mountains out of molehills. Sure, I use the phrases new File
object and new File
instance, but all you’re doing is making new File(“EmployeeInfo.txt”)
stand for a file on your hard drive. After you shove new File(“EmployeeInfo.txt”)
into new Scanner
,
Scanner
diskScanner
=
new Scanner(new File(“EmployeeInfo.txt”)
);
you can forget all about the new File
business. From that point on in the code, diskScanner
stands for the EmployeeInfo.txt
filename on your computer’s hard drive. (The name diskScanner
stands for a file on your hard drive just as, in previous examples, the name keyboard
stands for those buttons that you press day-in and day-out.)
Creating a new File
object in Listing 8-2 is like creating a new Employee
object later in the same listing. It’s also like creating a new Account
object in the examples of Chapter 7. The only difference is that the Employee
and Account
classes are defined in this book’s examples. The File
class is defined in Java’s API.
When you connect to a disk file with new Scanner
, don’t forget the new File
part. If you write new Scanner(“EmployeeInfo.txt”)
without new File
, the compiler won’t mind. (You won’t get any warnings or error messages before you run the code.) But when you run the code, you won’t get anything like the results that you expect to get.
You must refer to the File
class by its full name — java.io.File
. You can do this with an import declaration like the one in Listing 8-2. Alternatively, you can clutter up your code with a statement like
Scanner diskScanner =
new Scanner(new java.io.
File(“EmployeeInfo.txt”));
You need a throws IOException
clause. Lots of things can go wrong when your program connects to EmployeeInfo.txt
. For one thing, your hard drive may not have a file named EmployeeInfo.txt. For another, the file EmployeeInfo.txt
may be in the wrong directory. To brace for this kind of calamity, the Java programming language takes certain precautions. The language insists that when a disk file is involved, you acknowledge the possible dangers of calling new Scanner
.
You can acknowledge the hazards in several possible ways, but the simplest way is to use a throws
clause. In Listing 8-2, the main
method’s header ends with the words throws IOException. By adding these two words, you appease the Java compiler. It’s as if you’re saying “I know that calling new Scanner
can lead to problems. You don’t have to remind me.” And, sure enough, adding throws IOException
to your main
method keeps the compiler from complaining. (Without this throws
clause, you get an unreported exception
error message.)
For the full story on Java exceptions, read Chapter 12. In the meantime, add throws IOException
to the header of any method that calls new Scanner(new File(...
.
You must refer to the IOException
class by its full name — java.io.IOException
.
You can do this with an import
declaration like the one in Listing 8-2. Alternatively, you can enlarge the main
method’s throws
clause:
public static void main(String args[])
throws java.io.
IOException {
You must pass the file scanner’s name to the payOneEmployee
method.
In Listing 7-5 in Chapter 7, the getInterest
method has a parameter named percentageRate
. Whenever you call the getInterest
method, you hand an extra, up-to-date piece of information to the method. (You hand a number — an interest rate — to the method. Figure 7-7 illustrates the idea.)
The same thing happens in Listing 8-2. The payOneEmployee
method has a parameter named aScanner
. Whenever you call the payOne-Employee
method, you hand an extra, up-to-date piece of information to the method. (You hand a scanner — a reference to a disk file — to the method.)
You may wonder why the payOneEmployee
method needs a parameter. After all, in Listing 8-2, the payOneEmployee
method always reads data from the same file. Why bother informing this method, each time you call it, that the disk file is still the EmployeeInfo.txt
file?
Well, there are plenty of ways to shuffle the code in Listing 8-2. Some ways don’t involve a parameter. But the way that this example has arranged things, you have two separate methods — a main
method and a payOneEmployee
method. You create a scanner once inside the main
method and then use the scanner three times — once inside each call to the payOneEmployee
method.
Anything that you define inside a method is like a private joke that’s known only to the code inside that method. So, the diskScanner
that you define inside the main
method isn’t automatically known inside the payOneEmployee
method. To make the payOneEmployee
method aware of the disk file, you pass diskScanner
from the main
method to the payOneEmployee
method.
Who moved my file?
When you download the code from this book’s website, you’re going to find files named Employee.java and DoPayroll.java — the code in Listings 8-1 and 8-2. You’ll also find a file named EmployeeInfo.txt
. That’s good, because if Java can’t find the EmployeeInfo.txt
file, the whole project doesn’t run properly. Instead, you get a FileNotFoundException
.
In general, when you get a FileNotFoundException
, some file that your program needs isn’t available to it. This is an easy mistake to make. It can be frustrating because to you, a file such as EmployeeInfo.txt
may look like it’s available to your program. But remember — computers are stupid. If you make a tiny mistake, the computer can’t read between the lines for you. So if your EmployeeInfo.txt
file isn’t in the right directory on your hard drive or the filename is spelled incorrectly, the computer chokes when it tries to run your code.
Sometimes you know darn well that an EmployeeInfo.txt
(or whatever.xyz
) file exists on your hard drive. But when you run your program, you still get a mean-looking FileNotFoundException
. When this happens, the file is usually in the wrong directory on your hard drive. (Of course, it depends on your point of view. Maybe the file is in the right directory, but you’ve told your Java program to look for the file in the wrong directory.) When this happens, try copying the file to some other directories on your hard drive and rerunning your code. Stare carefully at the names and locations of files on your hard drive until you figure out what’s wrong.
Adding directory names to your filenames
You can specify a file’s exact location in your Java code. Code like new File(“C:\Users\bburd\workspace\Listing08-01-02\EmployeeInfo.txt”)
looks really ugly, but it works.
In the previous paragraph, notice the double backslashes in “C: \Users\bburd\workspace . .
.
”
. If you’re a Windows user, you’d be tempted to write C:Usersburdworkspace . . .
with single backslashes. But in Java, the single backslash has its own special meaning. (For example, back in Listing 7-7,
means to go to the next line.) So in Java, to indicate a backslash inside a quoted string, you use a double backslash instead.
Reading a line at a time
In Listing 8-2, the payOneEmployee
method illustrates some useful tricks for reading data. In particular, every scanner that you create has a nextLine
method. (You might not use this nextLine
method, but the method is available nonetheless.) When you call a scanner’s nextLine
method, the method grabs everything up to the end of the current line of text. In Listing 8-2, a call to nextLine
can read a whole line from the EmployeeInfo.txt
file. (In another program, a scanner’s nextLine
call may read everything the user types on the keyboard up to the pressing of the Enter key.)
Notice my careful choice of words: nextLine
reads everything up to the end of the current line. Unfortunately, what it means to read up to the end of the current line isn’t always what you think it means. Intermingling nextInt
, nextDouble
, and nextLine
calls can be messy. You have to watch what you’re doing and check your program’s output carefully.
To understand all this, you need to be painfully aware of a data file’s line breaks. Think of a line break as an extra character, stuck between one line of text and the next. Then imagine that calling nextLine
means to read everything up to and including the next line break.
Now take a look at Figure 8-4.
If one call to nextLine
reads Barry Burd[LineBreak]
, the subsequent call to nextLine
reads CEO[LineBreak]
.
If one call to nextDouble
reads the number 5000.00, the subsequent call to nextLine
reads the [LineBreak]
that comes immediately after the number 5000.00. (That’s all the nextLine
reads — a [LineBreak]
and nothing more.)
If a call to nextLine
reads the [LineBreak]
after the number 5000.00, the subsequent call to nextLine
reads Harriet Ritter[LineBreak]
.
So after reading the number 5000.00, you need two calls to nextLine
in order to scoop up the name Harriet Ritter. The mistake that I usually make is to forget the first of those two calls.
Defining Subclasses (What It Means to Be a Full-Time or Part-Time Employee)
This time last year, your company paid $10 million for a piece of software. That software came in the Employee.class
file. People at Burd Brain Consulting (the company that created the software) don’t want you to know about the innards of the software (otherwise, you may steal their ideas). So you don’t have the Java program file that the software came from. (In other words, you don’t have Employee.java
.) You can run the bytecode in the Employee.class
file. You can also read the documentation in a web page named Employee.html
. But you can’t see the statements inside the Employee.java
program, and you can’t change any of the program’s code.
Since this time last year, your company has grown. Unlike the old days, your company now has two kinds of employees: full-time and part-time. Each full-time employee is on a fixed, weekly salary. (If the employee works nights and weekends, then in return for this monumental effort, the employee receives a hearty handshake.) In contrast, each part-time employee works for an hourly wage. Your company deducts an amount from each full-time employee’s paycheck to pay for the company’s benefits package. Part-time employees, however, don’t get benefits.
The question is, how can the software that your company bought last year keep up with the company’s growth? You invested in a great program to handle employees and their payroll, but the program doesn’t differentiate between your full-time and part-time employees. You have several options:
Call your next-door neighbor, whose 12-year-old child knows more about computer programming than anyone in your company. Get this uppity little brat to take the employee software apart, rewrite it, and hand it back to you with all the changes and additions your company requires.
On second thought, you can’t do that. No matter how smart that kid is, the complexities of the employee software will probably confuse the kid. By the time you get the software back, it’ll be filled with bugs and inconsistencies. Besides, you don’t even have the Employee.java
file to hand to the kid. All you have is the Employee.class
file, which can’t be read or modified with a text editor. (See Chapter 2.) Besides, your kid just beat up the neighbor’s kid. You don’t want to give your neighbor the satisfaction of seeing you beg for the whiz kid’s help.
Scrap the $10 million employee software. Get someone in your company to rewrite the software from scratch.
In other words, say goodbye to your time and money.
Write a new front end for the employee software. That is, build a piece of code that does some preliminary processing on full-time employees and then hands the preliminary results to your $10 million software. Do the same for part-time employees.
This idea could be decent or spell disaster. Are you sure that the existing employee software has convenient hooks in it? (That is, does the employee software contain entry points that allow your front-end software to easily send preliminary data to the expensive employee software?) Remember, this plan treats the existing software as one big, monolithic lump, which can become cumbersome. Dividing the labor between your front-end code and the existing employee program is difficult. And if you add layer upon layer to existing black box code, you’ll probably end up with a fairly inefficient system.
Call Burd Brain Consulting, the company that sold you the employee software. Tell Dr. Burd that you want the next version of his software to differentiate between full-time and part-time employees.
“No problem,” says Dr. Burd. “It’ll be ready by the start of the next fiscal quarter.” That evening, Dr. Burd makes a discreet phone call to his next-door neighbor. . . .
Create two new Java classes named FullTimeEmployee
and PartTimeEmployee
. Have each new class extend the existing functionality of the expensive Employee
class, but have each new class define its own specialized functionality for certain kinds of employees.
Way to go! Figure 8-5 shows the structure that you want to create.
Creating a subclass
In Listing 8-1, I define an Employee
class. I can use what I define in Listing 8-1 and extend the definition to create new, more specialized classes. So in Listing 8-3, I define a new class — a FullTimeEmployee
class.
Listing 8-3: What Is a FullTimeEmployee?
public class FullTimeEmployee
extends Employee
{
private double weeklySalary;
private double benefitDeduction;
public void setWeeklySalary(double weeklySalaryIn) {
weeklySalary = weeklySalaryIn;
}
public double getWeeklySalary() {
return weeklySalary;
}
public void setBenefitDeduction(double benefitDedIn) {
benefitDeduction = benefitDedIn;
}
public double getBenefitDeduction() {
return benefitDeduction;
}
public double findPaymentAmount() {
return weeklySalary - benefitDeduction;
}
}
Looking at Listing 8-3, you can see that each instance of the FullTimeEmployee
class has two fields: weeklySalary
and benefitDeduction
. But are those the only fields that each FullTimeEmployee
instance has? No, they’re not. The first line of Listing 8-3 says that the FullTimeEmployee
class extends the existing Employee
class. This means that in addition to having a weeklySalary
and a benefitDeduction
, each FullTimeEmployee
instance also has two other fields: name
and jobTitle
. These two fields come from the definition of the Employee
class, which you can find in Listing 8-1.
In Listing 8-3, the magic word is extends
. When one class extends an existing class, the extending class automatically inherits functionality that’s defined in the existing class. So, the FullTimeEmployee
class inherits the name
and jobTitle
fields. The FullTimeEmployee
class also inherits all the methods that are declared in the Employee
class — setName
, getName
, setJobTitle
, getJobTitle
, and cutCheck
. The FullTimeEmployee
class is a subclass of the Employee
class. That means the Employee
class is the superclass of the FullTimeEmployee
class. You can also talk in terms of blood relatives. The FullTimeEmployee
class is the child of the Employee
class, and the Employee
class is the parent of the FullTimeEmployee
class.
It’s almost (but not quite) as if the FullTimeEmployee
class were defined by the code in Listing 8-4.
Listing 8-4: Fake (But Informative) Code
import static java.lang.System.out;
public class FullTimeEmployee {
private String name;
private String jobTitle;
private double weeklySalary;
private double benefitDeduction;
public void setName(String nameIn) {
name = nameIn;
}
public String getName() {
return name;
}
public void setJobTitle(String jobTitleIn) {
jobTitle = jobTitleIn;
}
public String getJobTitle() {
return jobTitle;
}
public void setWeeklySalary(double weeklySalaryIn) {
weeklySalary = weeklySalaryIn;
}
public double getWeeklySalary() {
return weeklySalary;
}
public void setBenefitDeduction(double benefitDedIn) {
benefitDeduction = benefitDedIn;
}
public double getBenefitDeduction() {
return benefitDeduction;
}
public double findPaymentAmount() {
return weeklySalary - benefitDeduction;
}
public void cutCheck(double amountPaid) {
out.printf(“Pay to the order of %s “, name);
out.printf(“(%s) ***$”, jobTitle);
out.printf(“%,.2f
”, amountPaid);
}
}
Creating subclasses is habit-forming
After you’re accustomed to extending classes, you can get extend-happy. If you created a FullTimeEmployee
class, you might as well create a PartTimeEmployee
class, as shown in Listing 8-5.
Listing 8-5: What Is a PartTimeEmployee?
public class PartTimeEmployee
extends Employee
{
private double hourlyRate;
public void setHourlyRate(double rateIn) {
hourlyRate = rateIn;
}
public double getHourlyRate() {
return hourlyRate;
}
public double findPaymentAmount(int hours) {
return hourlyRate * hours;
}
}
Unlike the FullTimeEmployee
class, PartTimeEmployee
has no salary or deduction. Instead PartTimeEmployee
has an hourlyRate
field. (Adding a numberOfHoursWorked
field would also be a possibility. I chose not to do this, figuring that the number of hours a part-time employee works will change drastically from week to week.)
Using Subclasses
The previous section tells a story about creating subclasses. It’s a good story, but it’s incomplete. Creating subclasses is fine, but you gain nothing from these subclasses unless you write code to use them. So in this section, you explore code that uses subclasses.
Now the time has come for you to classify yourself as either a type-F person, a type-P person, or a type-T person. (I’m this book’s author so I get to make up some personality types. I can even point to someone in public and say “Look! He’s a type-T person!”)
A type-F person wants to see the fundamentals. (The letter F stands for fundamentals.) “Show me a program that lays out the principles in their barest, most basic form,” says the type-F person. A type-F person isn’t worried about bells and whistles. The bells come later, and the whistles may never come at all. If you’re a type-F person, you want to see a program that uses the FullTimeEmployee
and PartTimeEmployee
subclasses, and then moves out of your way so you can get some work done.
A type-P person wants practical applications. (The letter P stands for practical.) Type-P people need to see ideas in context; otherwise the ideas float away too quickly. “Show me a program that demonstrates the usefulness of the FullTimeEmployee
and PartTimeEmployee
subclasses,” says the type-P person. “I have no use for your stinking abstractions. I want real-life examples, and I want them now!”
A type-T person is inspired by something that I write about briefly in Chapter 7. The T person wants to test the code in the FullTimeEmployee
and PartTimeEmployee
subclasses. Testing the code means putting the code through its paces — checking the output’s accuracy when the input is ordinary, when the input is unexpected, and even when the input is completely unrealistic. What’s more, the type-T person wants to use a standard, easily recognizable outline for the testing code so that other programmers can quickly understand the test results. The type-T person creates JUnit tests that use the FullTimeEmployee
and PartTimeEmployee
subclasses.
Listing 8-6, which is for the type-F crowd, is lean and simple and makes good bedtime reading.
Listing 8-6 shows you a bare-bones program that uses the subclasses FullTimeEmployee
and PartTimeEmployee
. Figure 8-6 shows the program’s output.
Listing 8-6: Putting Subclasses to Good Use
class DoPayrollTypeF {
public static void main(String args[]) {
FullTimeEmployee ftEmployee = new FullTimeEmployee();
ftEmployee.setName(“Barry Burd”);
ftEmployee.setJobTitle(“CEO”);
ftEmployee.setWeeklySalary(5000.00);
ftEmployee.setBenefitDeduction(500.00);
ftEmployee.cutCheck(ftEmployee.findPaymentAmount());
System.out.println();
PartTimeEmployee ptEmployee = new PartTimeEmployee();
ptEmployee.setName(“Steve Surace”);
ptEmployee.setJobTitle(“Driver”);
ptEmployee.setHourlyRate(7.53);
ptEmployee.cutCheck
(ptEmployee.findPaymentAmount(10));
}
}
To understand Listing 8-6, you need to keep an eye on three classes: Employee
, FullTimeEmployee
, and PartTimeEmployee
. (For a look at the code that defines these classes, see Listings 8-1, 8-3, and 8-5.)
The first half of Listing 8-6 deals with a full-time employee. Notice how so many methods are available for use with the ftEmployee
variable. For instance, you can call ftEmployee.setWeeklySalary
because ftEmployee
has type FullTimeEmployee
. You can also call ftEmployee.setName
because the FullTimeEmployee
class extends the Employee
class.
Because cutCheck
is declared in the Employee
class, you can call ftEmployee.cutCheck
. But you can also call ftEmployee.findPaymentAmount
because a findPaymentAmount
method is in the FullTimeEmployee
class.
Making types match
Look again at the first half of Listing 8-6. Take special notice of that last statement — the one in which the full-time employee is actually cut a check. The statement forms a nice, long chain of values and their types. You can see this by reading the statement from the inside out.
Method ftEmployee.findPaymentAmount
is called with an empty parameter list (Listing 8-6). That’s good, because the findPaymentAmount
method takes no parameters (Listing 8-3).
The findPaymentAmount
method returns a value of type double
(again, Listing 8-3).
The double
value that ftEmployee.findPaymentAmount
returns is passed to method ftEmployee.cutCheck
(Listing 8-6). That’s good, because the cutCheck
method takes one parameter of type double
(Listing 8-1).
For a fanciful graphic illustration, see Figure 8-7.
The second half of the story
In the second half of Listing 8-6, the code creates an object of type PartTimeEmployee
. A variable of type PartTimeEmployee
can do some of the same things a FullTimeEmployee
variable can do. But the PartTimeEmployee
class doesn’t have the setWeeklySalary
and setBenefitDeduction
methods. Instead, the PartTimeEmployee
class has the setHourlyRate
method. (See Listing 8-5.) So, in Listing 8-6, the next-to-last line is a call to the setHourlyRate
method.
The last line of Listing 8-6 is by far the most interesting. On that line, the code hands the number 10
(the number of hours worked) to the findPaymentAmount
method. Compare this with the earlier call to findPaymentAmount
— the call for the full-time employee in the first half of Listing 8-6. Between the two subclasses, FullTimeEmployee
and PartTimeEmployee
, are two different findPaymentAmount
methods. The two methods have two different kinds of parameter lists:
The FullTimeEmployee
class’s findPaymentAmount
method takes no parameters (Listing 8-3).
The PartTimeEmployee
class’s findPaymentAmount
method takes one int
parameter (Listing 8-5).
This is par for the course. Finding the payment amount for a part-time employee isn’t the same as finding the payment amount for a full-time employee. A part-time employee’s pay changes each week, depending on the number of hours the employee works in a week. The full-time employee’s pay stays the same each week. So the FullTimeEmployee
and PartTimeEmployee
classes both have findPaymentAmount
methods, but each class’s method works quite differently.
Overriding Existing Methods (Changing the Payments for Some of Your Employees)
Wouldn’t you know it! Some knucklehead in the human resources department offered double pay for overtime to one of your part-time employees. Now word is getting around, and some of the other part-timers want double pay for their overtime work. If this keeps up, you’ll end up in the poorhouse, so you need to send out a memo to all the part-time employees, explaining why earning more money is not to their benefit.
In the meantime, you have two kinds of part-time employees — the ones who receive double pay for overtime hours and the ones who don’t — so you need to modify your payroll software. What are your options?
Well, you can dig right into the PartTimeEmployee
class code, make a few changes, and hope for the best. (Not a good idea!)
You can follow the previous section’s advice and create a subclass of the existing PartTimeEmployee
class. “But wait,” you say. “The existing PartTimeEmployee
class already has a findPaymentAmount
method. Do I need some tricky way of bypassing this existing find-PaymentAmount
method for each double-pay-for-overtime employee?”
At this point, you can thank your lucky stars that you’re doing object-oriented programming in Java. With object-oriented programming, you can create a subclass that overrides the functionality of its parent class. Listing 8-7 has just such a subclass.
Listing 8-7: Yet Another Subclass
public class PartTimeWithOver
extends PartTimeEmployee
{
@Override
public double findPaymentAmount(int hours)
{
if(hours <= 40) {
return getHourlyRate() * hours;
} else {
return getHourlyRate() * 40 +
getHourlyRate() * 2 * (hours - 40);
}
}
}
Figure 8-8 shows the relationship between the code in Listing 8-7 and other pieces of code in this chapter. In particular, PartTimeWithOver
is a subclass of a subclass. In object-oriented programming, a chain of this kind is not the least bit unusual. In fact, as subclasses go, this chain is rather short.
The PartTimeWithOver
class extends the PartTimeEmployee
class, but PartTimeWithOver
picks and chooses what it wants to inherit from the PartTimeEmployee
class. Because PartTimeWithOver
has its own declaration for the findPaymentAmount
method, the PartTimeWithOver
class doesn’t inherit a findPaymentAmount
method from its parent. (See Figure 8-9.)
According to the official terminology, the PartTimeWithOver
class overrides its parent class’s findPaymentAmount
method. If you create an object from the PartTimeWithOver
class, that object has the name
, jobTitle
, hourlyRate
, and cutCheck
of the PartTimeEmployee
class, but the object has the findPaymentAmount
method that’s defined in Listing 8-7.
A Java annotation
The word @Override
in Listing 8-7 is an example of an annotation. A Java annotation tells your computer something about your code. In particular, the @Override
annotation in Listing 8-7 tells the Java compiler to be on the lookout for a common coding error. The annotation says “make sure that the method immediately following this annotation has the same stuff (the same name, the same parameters, and so on) as one of the methods in the superclass. If not, then display an error message.”
So if I accidentally type
public double findPaymentAmount(
double
hours) {
instead of int hours
as in Listings 8-5 and 8-7, the compiler reminds me that my new findPaymentAmount
method doesn’t really override anything that’s in Listing 8-5.
Java has other kinds of annotations (such as @Deprecated
and @SuppressWarnings
). You can read a bit about the @SuppressWarnings
annotation in Chapter 9.
Using methods from classes and subclasses
If you need clarification on this notion of overriding a method, look at the code in Listing 8-8. A run of that code is shown in Figure 8-10.
Listing 8-8: Testing the Code from Listing 8-7
class DoPayrollTypeF {
public static void main(String args[]) {
FullTimeEmployee ftEmployee = new FullTimeEmployee();
ftEmployee.setName(“Barry Burd”);
ftEmployee.setJobTitle(“CEO”);
ftEmployee.setWeeklySalary(5000.00);
ftEmployee.setBenefitDeduction(500.00);
ftEmployee.cutCheck(ftEmployee.findPaymentAmount());
PartTimeEmployee ptEmployee = new PartTimeEmployee();
ptEmployee.setName(“Chris Apelian”);
ptEmployee.setJobTitle(“Computer Book Author”);
ptEmployee.setHourlyRate(7.53);
ptEmployee.cutCheck
(ptEmployee.findPaymentAmount(50));
PartTimeWithOver ptoEmployee =
new PartTimeWithOver();
ptoEmployee.setName(“Steve Surace”);
ptoEmployee.setJobTitle(“Driver”);
ptoEmployee.setHourlyRate(7.53);
ptoEmployee.cutCheck
(ptoEmployee.findPaymentAmount(50));
}
}
The code in Listing 8-8 writes checks to three employees. The first employee is a full-timer. The second is a part-time employee who hasn’t yet gotten wind of the overtime payment scheme. The third employee knows about the overtime payment scheme and demands a fair wage.
With the subclasses, all three of these employees coexist in Listing 8-8. Sure, one subclass comes from the old PartTimeEmployee
class, but that doesn’t mean you can’t create an object from the PartTimeEmployee
class. In fact, Java is very smart about this. Listing 8-8 has three calls to the findPaymentAmount
method, and each call reaches out to a different version of the method.
In the first call, ftEmployee.findPaymentAmount
, the ftEmployee
variable is an instance of the FullTimeEmployee
class. So the method that’s called is the one in Listing 8-3.
In the second call, ptEmployee.findPaymentAmount
, the pt-Employee
variable is an instance of the PartTimeEmployee
class. So the method that’s called is the one in Listing 8-5.
In the third call, ptoEmployee.findPaymentAmount
, the pto-Employee
variable is an instance of the PartTimeWithOver
class. So the method that’s called is the one in Listing 8-7.
This code is fantastic. It’s clean, elegant, and efficient. With all the money that you save on software, you can afford to pay everyone double for overtime hours. (Whether you do that or keep the money for yourself is another story.)