Objectives
In this chapter you’ll learn:
• What classes, objects, methods and instance variables are.
• How to declare a class and use it to create an object.
• How to declare methods in a class to implement the class’s behaviors.
• How to declare instance variables in a class to implement the class’s attributes.
• How to call an object’s methods to make those methods perform their tasks.
• The differences between instance variables of a class and local variables of a method.
• How to use a constructor to ensure that an object’s data is initialized when the object is created.
• The differences between primitive and reference types.
You will see something new. Two things. And I call them Thing One and Thing Two.
—Dr. Theodor Seuss Geisel
Nothing can have value without being an object of utility.
—Karl Marx
Your public servants serve you right.
—Adlai E. Stevenson
Knowing how to answer one who speaks, To reply to one who sends a message.
—Amenemope
Outline
3.1 Introduction
3.2 Classes, Objects, Methods and Instance Variables
3.3 Declaring a Class with a Method and Instantiating an Object of a Class
3.4 Declaring a Method with a Parameter
3.5 Instance Variables, set Methods and get Methods
3.6 Primitive Types vs. Reference Types
3.7 Initializing Objects with Constructors
3.8 Floating-Point Numbers and Type double
3.9 (Optional) Software Engineering Case Study: Identifying the Classes in a Requirements Document
3.10 Wrap-Up
We introduced the basic terminology and concepts of object-oriented programming in Section 1.9. In Chapter 2, you began to use those concepts to create simple applications that displayed messages to the user, obtained information from the user, performed calculations and made decisions. One common feature of every application in Chapter 2 was that all the statements that performed tasks were located in method main
. Typically, the applications you develop in this book will consist of two or more classes, each containing one or more methods. If you become part of a development team in industry, you might work on applications that contain hundreds, or even thousands, of classes. In this chapter, we present a simple framework for organizing object-oriented applications in Java.
First, we motivate the notion of classes with a real-world example. Then we present five complete working applications to demonstrate creating and using your own classes. The first four of these examples begin our case study on developing a grade-book class that instructors can use to maintain student test scores. This case study is enhanced over the next several chapters, culminating with the version presented in Chapter 7, Arrays. The last example in the chapter introduces floating-point numbers—that is, numbers containing decimal points, such as 0.0345, –7.23 and 100.7—in the context of a bank account class that maintains a customer’s balance.
Let’s begin with a simple analogy to help you understand classes and their contents. Suppose you want to drive a car and make it go faster by pressing down on its accelerator pedal. What must happen before you can do this? Well, before you can drive a car, someone has to design it. A car typically begins as engineering drawings, similar to the blueprints used to design a house. These engineering drawings include the design for an accelerator pedal to make the car go faster. The pedal “hides” from the driver the complex mechanisms that actually make the car go faster, just as the brake pedal “hides” the mechanisms that slow the car and the steering wheel “hides” the mechanisms that turn the car. This enables people with little or no knowledge of how engines work to drive a car easily.
Unfortunately, you cannot drive the engineering drawings of a car. Before you can drive a car, the car must be built from the engineering drawings that describe it. A completed car has an actual accelerator pedal to make the car go faster, but even that’s not enough—the car won’t accelerate on its own, so the driver must press the accelerator pedal.
Now let’s use our car example to introduce the key programming concepts of this section. Performing a task in a program requires a method. The method describes the mechanisms that actually perform its tasks. The method hides from its user the complex tasks that it performs, just as the accelerator pedal of a car hides from the driver the complex mechanisms of making the car go faster. In Java, we begin by creating a program unit called a class to house a method, just as a car’s engineering drawings house the design of an accelerator pedal. In a class, you provide one or more methods that are designed to perform the class’s tasks. For example, a class that represents a bank account might contain one method to deposit money to an account, another to withdraw money from an account and a third to inquire what the current balance is.
Just as you cannot drive an engineering drawing of a car, you cannot “drive” a class. Just as someone has to build a car from its engineering drawings before you can actually drive a car, you must build an object of a class before you can get a program to perform the tasks the class describes how to do. That is one reason Java is known as an object-oriented programming language.
When you drive a car, pressing its gas pedal sends a message to the car to perform a task—that is, make the car go faster. Similarly, you send messages to an object—each message is known as a method call and tells a method of the object to perform its task.
Thus far, we’ve used the car analogy to introduce classes, objects and methods. In addition to a car’s capabilities, a car also has many attributes, such as its color, the number of doors, the amount of gas in its tank, its current speed and its total miles driven (i.e., its odometer reading). Like the car’s capabilities, these attributes are represented as part of a car’s design in its engineering diagrams. As you drive a car, these attributes are always associated with the car. Every car maintains its own attributes. For example, each car knows how much gas is in its own gas tank, but not how much is in the tanks of other cars. Similarly, an object has attributes that are carried with the object as it is used in a program. These attributes are specified as part of the object’s class. For example, a bank account object has a balance attribute that represents the amount of money in the account. Each bank account object knows the balance in the account it represents, but not the balances of the other accounts in the bank. Attributes are specified by the class’s instance variables.
The remainder of this chapter presents examples that demonstrate the concepts we introduced in the context of the car analogy. The first four examples incrementally build a GradeBook
class to demonstrate these concepts:
1. The first example presents a GradeBook
class with one method that simply displays a welcome message when it is called. We then show how to create an object of that class and call the method so that it displays the welcome message.
2. The second example modifies the first by allowing the method to receive a course name as an argument and by displaying the name as part of the welcome message.
3. The third example shows how to store the course name in a GradeBook
object. For this version of the class, we also show how to use methods to set the course name and obtain the course name.
4. The fourth example demonstrates how the data in a GradeBook
object can be initialized when the object is created—the initialization is performed by the class’s constructor.
The last example in the chapter presents an Account
class that reinforces the concepts presented in the first four examples and introduces floating-point numbers. For this purpose, we present an Account
class that represents a bank account and maintains its balance as a floating-point number. The class contains two methods—one that credits a deposit to the account, thus increasing the balance, and another that retrieves the balance. The class’s constructor allows the balance of each Account
object to be initialized as the object is created. We create two Account
objects and make deposits into each to show that each object maintains its own balance. The example also demonstrates how to input and display floating-point numbers.
We begin with an example that consists of classes GradeBook
(Fig. 3.1) and GradeBookTest
(Fig. 3.2). Class GradeBook
(declared in file GradeBook.java
) will be used to display a message on the screen (Fig. 3.2) welcoming the instructor to the grade-book application. Class GradeBookTest
(declared in file GradeBookTest.java
) is an application class in which the main
method will use class GradeBook
. Each class declaration that begins with keyword public
must be stored in a file that has the same name as the class and ends with the .java
file-name extension. Thus, classes GradeBook
and GradeBookTest
must be declared in separate files, because each class is declared public
.
Fig. 3.1. Class declaration with one method.
Fig. 3.2. Creating an object of class GradeBook
and calling its displayMessage
method.
Common Programming Error 3.1
Declaring more than one public
class in the same file is a compilation error.
GradeBook
The GradeBook
class declaration (Fig. 3.1) contains a displayMessage
method (lines 7–10) that displays a message on the screen. Line 9 of the class performs the work of displaying the message. Recall that a class is like a blueprint—we’ll need to make an object of this class and call its method to get line 9 to execute and display its message.
The class declaration begins in line 4. The keyword public
is an access modifier. For now, we’ll simply declare every class public
. Every class declaration contains keyword class
followed immediately by the class’s name. Every class’s body is enclosed in a pair of left and right braces ({
and }
), as in lines 5 and 12 of class GradeBook
.
In Chapter 2, each class we declared had one method named main
. Class GradeBook
also has one method—displayMessage
(lines 7–10). Recall that main
is a special method that is always called automatically by the Java Virtual Machine (JVM) when you execute an application. Most methods do not get called automatically. As you’ll soon see, you must call method displayMessage
to tell it to perform its task.
The method declaration begins with keyword public
to indicate that the method is “available to the public”—that is, it can be called from outside the class declaration’s body by methods of other classes. Keyword void
indicates that this method will perform a task but will not return (i.e., give back) any information to its calling method when it completes its task. You have already used methods that return information—for example, in Chapter 2 you used Scanner
method nextInt
to input an integer typed by the user at the keyboard. When nextInt
inputs a value, it returns that value for use in the program.
The name of the method, displayMessage
, follows the return type. By convention, method names begin with a lowercase first letter and all subsequent words in the name begin with a capital letter. The parentheses after the method name indicate that this is a method. An empty set of parentheses, as shown in line 7, indicates that this method does not require additional information to perform its task. Line 7 is commonly referred to as the method header. Every method’s body is delimited by left and right braces ({
and }
), as in lines 8 and 10.
The body of a method contains statement(s) that perform the method’s task. In this case, the method contains one statement (line 9) that displays the message "Welcome to the Grade Book!"
followed by a newline in the command window. After this statement executes, the method has completed its task.
Next, we’d like to use class GradeBook
in an application. As you learned in Chapter 2, method main
begins the execution of every application. A class that contains method main
is a Java application. Such a class is special because the JVM can use main
as an entry point to begin execution. Class GradeBook
is not an application because it does not contain main
. Therefore, if you try to execute GradeBook
by typing java GradeBook
in the command window, you’ll receive an error message like:
Exception in thread "main" java.lang.NoSuchMethodError: main
This was not a problem in Chapter 2, because every class you declared had a main
method. To fix this problem for the GradeBook
, we must either declare a separate class that contains a main
method or place a main
method in class GradeBook
. To help you prepare for the larger programs you’ll encounter later in this book and in industry, we use a separate class (GradeBookTest
in this example) containing method main
to test each new class we create in this chapter.
GradeBookTest
The GradeBookTest
class declaration (Fig. 3.2) contains the main
method that will control our application’s execution. Any class that contains main
declared as shown in line 7 can be used to execute an application. The GradeBookTest
class declaration begins in line 4 and ends in line 16. The class contains only a main
method, which is typical of many classes that begin an application’s execution.
Lines 7–14 declare method main
. Recall from Chapter 2 that the main
header must appear as shown in line 7; otherwise, the application will not execute. A key part of enabling the JVM to locate and call method main
to begin the application’s execution is the static
keyword (line 7), which indicates that main
is a static
method. A static
method is special because it can be called without first creating an object of the class in which the method is declared. We thoroughly explain static
methods in Chapter 6, Methods: A Deeper Look.
In this application, we’d like to call class GradeBook
’s displayMessage
method to display the welcome message in the command window. Typically, you cannot call a method that belongs to another class until you create an object of that class, as shown in line 10. We begin by declaring variable myGradeBook
. Note that the variable’s type is GradeBook
—the class we declared in Fig. 3.1. Each new class you create becomes a new type that can be used to declare variables and create objects. Programmers can declare new class types as needed; this is one reason why Java is known as an extensible language.
Variable myGradeBook
is initialized with the result of the class instance creation expression new GradeBook()
. Keyword new
creates a new object of the class specified to the right of the keyword (i.e., GradeBook
). The parentheses to the right of GradeBook
are required. As you’ll learn in Section 3.7, those parentheses in combination with a class name represent a call to a constructor, which is similar to a method, but is used only at the time an object is created to initialize the object’s data. In that section you’ll see that data can be placed in parentheses to specify initial values for the object’s data. For now, we simply leave the parentheses empty.
Just as we can use object System.out
to call methods print
, printf
and println
, we can use object myGradeBook
to call method displayMessage
. Line 13 calls the method displayMessage
(lines 7–10 of Fig. 3.1) using myGradeBook
followed by a dot separator (.
), the method name displayMessage
and an empty set of parentheses. This call causes the displayMessage
method to perform its task. This method call differs from those in Chapter 2 that displayed information in a command window—each of those method calls provided arguments that specified the data to display. At the beginning of line 13, “myGradeBook.
” indicates that main
should use the myGradeBook
object that was created in line 10. Line 7 of Fig. 3.1 indicates that method displayMessage
has an empty parameter list—that is, displayMessage
does not require additional information to perform its task. For this reason, the method call (line 13 of Fig. 3.2) specifies an empty set of parentheses after the method name to indicate that no arguments are being passed to method displayMessage
. When method displayMessage
completes its task, method main
continues executing in line 14. This is the end of method main
, so the program terminates.
You must compile the classes in Fig. 3.1 and Fig. 3.2 before you can execute the application. First, change to the directory that contains the application’s source-code files. Next, type the command
javac GradeBook.java GradeBookTest.java
to compile both classes at once. If the directory containing the application includes only this application’s files, you can compile all the classes in the directory with the command
javac *.java
The asterisk (*
) in *.java
indicates that all files in the current directory that end with the file name extension “.java
” should be compiled.
GradeBook
Figure 3.3 presents a UML class diagram for class GradeBook
of Fig. 3.1. Recall from Section 1.9 that the UML is a graphical language used by programmers to represent object-oriented systems in a standardized manner. In the UML, each class is modeled in a class diagram as a rectangle with three compartments. The top compartment contains the name of the class centered horizontally in boldface type. The middle compartment contains the class’s attributes, which correspond to instance variables in Java. In Fig. 3.3, the middle compartment is empty because the version of class GradeBook
in Fig. 3.1 does not have any attributes. The bottom compartment contains the class’s operations, which correspond to methods in Java. The UML models operations by listing the operation name preceded by an access modifier and followed by a set of parentheses. Class GradeBook
has one method, displayMessage
, so the bottom compartment of Fig. 3.3 lists one operation with this name. Method displayMessage
does not require additional information to perform its tasks, so the parentheses following the method name in the class diagram are empty, just as they were in the method’s declaration in line 7 of Fig. 3.1. The plus sign (+
) in front of the operation name indicates that displayMessage
is a public
operation in the UML (i.e., a public
method in Java). We’ll often use UML class diagrams to summarize a class’s attributes and operations.
Fig. 3.3. UML class diagram indicating that class GradeBook
has a public displayMessage
operation.
In our car analogy from Section 3.2, we discussed the fact that pressing a car’s gas pedal sends a message to the car to perform a task—make the car go faster. But how fast should the car accelerate? As you know, the farther down you press the pedal, the faster the car accelerates. So the message to the car actually includes the task to perform and additional information that helps the car perform the task. This additional information is known as a parameter—the value of the parameter helps the car determine how fast to accelerate. Similarly, a method can require one or more parameters that represent additional information it needs to perform its task. A method call supplies values—called arguments—for each of the method’s parameters. For example, the method System.out.println
requires an argument that specifies the data to output in a command window. Similarly, to make a deposit into a bank account, a deposit
method specifies a parameter that represents the deposit amount. When the deposit
method is called, an argument value representing the deposit amount is assigned to the method’s parameter. The method then makes a deposit of that amount.
Our next example declares class GradeBook
(Fig. 3.4) with a displayMessage
method that displays the course name as part of the welcome message. (See the sample execution in Fig. 3.5.) The new displayMessage
method requires a parameter that represents the course name to output.
Fig. 3.4. Class declaration with one method that has a parameter.
Fig. 3.5. Creating a GradeBook
object and passing a String
to its displayMessage
method.
Before discussing the new features of class GradeBook
, let’s see how the new class is used from the main
method of class GradeBookTest
(Fig. 3.5). Line 12 creates a Scanner
named input
for reading the course name from the user. Line 15 creates an object of class GradeBook
and assigns it to variable myGradeBook
. Line 18 prompts the user to enter a course name. Line 19 reads the name from the user and assigns it to the nameOfCourse
variable, using Scanner
method nextLine
to perform the input. The user types the course name and presses Enter to submit the course name to the program. Note that pressing Enter inserts a newline character at the end of the characters typed by the user. Method nextLine
reads characters typed by the user until the newline character is encountered, then returns a String
containing the characters up to, but not including, the newline. The newline character is discarded. Class Scanner
also provides a similar method—next
—that reads individual words. When the user presses Enter after typing input, method next
reads characters until a white-space character (such as a space, tab or newline) is encountered, then returns a String
containing the characters up to, but not including, the white-space character (which is discarded). All information after the first white-space character is not lost—it can be read by other statements that call the Scanner
’s methods later in the program.
Line 24 calls myGradeBooks
’s displayMessage
method. The variable nameOfCourse
in parentheses is the argument that is passed to method displayMessage
so that the method can perform its task. The value of variable nameOfCourse
in main
becomes the value of method displayMessage
’s parameter courseName
in line 7 of Fig. 3.4. When you execute this application, notice that method displayMessage
outputs the name you type as part of the welcome message (Fig. 3.5).
Software Engineering Observation 3.1
Normally, objects are created with new
. One exception is a string literal that is contained in quotes, such as "hello"
. String literals are references to String
objects that are implicitly created by Java.
When you declare a method, you must specify whether the method requires data to perform its task. To do so, you place additional information in the method’s parameter list, which is located in the parentheses that follow the method name. The parameter list may contain any number of parameters, including none at all. Empty parentheses following the method name (as in Fig. 3.1, line 7) indicate that a method does not require any parameters. In Fig. 3.4, displayMessage
’s parameter list (line 7) declares that the method requires one parameter. Each parameter must specify a type and an identifier. In this case, the type String
and the identifier courseName
indicate that method displayMessage
requires a String
to perform its task. At the time the method is called, the argument value in the call is assigned to the corresponding parameter (in this case, courseName
) in the method header. Then, the method body uses the parameter courseName
to access the value. Lines 9–10 of Fig. 3.4 display parameter courseName
’s value, using the %s
format specifier in printf
’s format string. Note that the parameter variable’s name (Fig. 3.4, line 7) can be the same or different from the argument variable’s name (Fig. 3.5, line 24).
A method can specify multiple parameters by separating each parameter from the next with a comma (we’ll see an example of this in Chapter 6). The number of arguments in a method call must match the number of parameters in the parameter list of the called method’s declaration. Also, the argument types in the method call must be “consistent with” the types of the corresponding parameters in the method’s declaration. (As you’ll learn in subsequent chapters, an argument’s type and its corresponding parameter’s type are not always required to be identical.) In our example, the method call passes one argument of type String
(nameOfCourse
is declared as a String
in line 19 of Fig. 3.5) and the method declaration specifies one parameter of type String
(line 7 in Fig. 3.4). So in this example the type of the argument in the method call exactly matches the type of the parameter in the method header.
Common Programming Error 3.2
A compilation error occurs if the number of arguments in a method call does not match the number of parameters in the method declaration.
Common Programming Error 3.3
A compilation error occurs if the types of the arguments in a method call are not consistent with the types of the corresponding parameters in the method declaration.
GradeBook
The UML class diagram of Fig. 3.6 models class GradeBook
of Fig. 3.4. Like Fig. 3.1, this GradeBook
class contains public
operation displayMessage
. However, this version of displayMessage
has a parameter. The UML models a parameter a bit differently from Java by listing the parameter name, followed by a colon and the parameter type in the parentheses following the operation name. The UML has its own data types similar to those of Java (but as you’ll see, not all the UML data types have the same names as the corresponding Java types). The UML type String
does correspond to the Java type String
. GradeBook
method displayMessage
(Fig. 3.4) has a String
parameter named courseName
, so Fig. 3.6 lists courseName : String
between the parentheses following displayMessage
.
Fig. 3.6. UML class diagram indicating that class GradeBook
has a displayMessage
operation with a courseName
parameter of UML type String
.
import
DeclarationsNotice the import
declaration in Fig. 3.5 (line 4). This indicates to the compiler that the program uses class Scanner
. Why do we need to import class Scanner
, but not classes System
, String
or GradeBook
? Most classes you’ll use in Java programs must be imported. Classes System
and String
are in package java.lang
, which is implicitly imported into every Java program, so all programs can use package java.lang
’s classes without explicitly importing them.
There is a special relationship between classes that are compiled in the same directory on disk, like classes GradeBook
and GradeBookTest
. By default, such classes are considered to be in the same package—known as the default package. Classes in the same package are implicitly imported into the source code files of other classes in the same package. Thus, an import
declaration is not required when one class in a package uses another in the same package—such as when class GradeBookTest
uses class GradeBook
.
The import
declaration in line 4 is not required if we always refer to class Scanner
as java.util.Scanner
, which includes the full package name and class name. This is known as the class’s fully qualified class name. For example, line 12 could be written as
java.util.Scanner input = new java.util.Scanner( System.in );
Software Engineering Observation 3.2
The Java compiler does not require import
declarations in a Java source code file if the fully qualified class name is specified every time a class name is used in the source code. But most Java programmers consider using fully qualified names to be cumbersome, and instead prefer to use import
declarations.
In Chapter 2, we declared all of an application’s variables in the application’s main
method. Variables declared in the body of a particular method are known as local variables and can be used only in that method. When that method terminates, the values of its local variables are lost. Recall from Section 3.2 that an object has attributes that are carried with the object as it is used in a program. Such attributes exist before a method is called on an object and after the method completes execution.
A class normally consists of one or more methods that manipulate the attributes that belong to a particular object of the class. Attributes are represented as variables in a class declaration. Such variables are called fields and are declared inside a class declaration but outside the bodies of the class’s method declarations. When each object of a class maintains its own copy of an attribute, the field that represents the attribute is also known as an instance variable—each object (instance) of the class has a separate instance of the variable in memory. The example in this section demonstrates a GradeBook
class that contains a courseName
instance variable to represent a particular GradeBook
object’s course name.
GradeBook
Class with an Instance Variable, a set Method and a get MethodIn our next application (Fig. 3.7–Fig. 3.8), class GradeBook
(Fig. 3.7) maintains the course name as an instance variable so that it can be used or modified at any time during an application’s execution. The class contains three methods—setCourseName
, getCourseName
and displayMessage
. Method setCourseName
stores a course name in a GradeBook
. Method getCourseName
obtains a GradeBook
’s course name. Method displayMessage
, which now specifies no parameters, still displays a welcome message that includes the course name; as you’ll see, the method now obtains the course name by calling another method in the same class—getCourseName
.
Fig. 3.7. GradeBook
class that contains a courseName
instance variable and methods to set and get its value.
Fig. 3.8. Creating and manipulating a GradeBook
object.
A typical instructor teaches more than one course, each with its own course name. Line 7 declares that courseName
is a variable of type String
. Because the variable is declared in the body of the class but outside the bodies of the class’s methods (lines 10–13, 16–19 and 22–28), line 7 is a declaration for an instance variable. Every instance (i.e., object) of class GradeBook
contains one copy of each instance variable. For example, if there are two GradeBook
objects, each object has its own copy of courseName
(one per object). A benefit of making courseName
an instance variable is that all the methods of the class (in this case, GradeBook
) can manipulate any instance variables that appear in the class (in this case, courseName
).
public
and private
Most instance variable declarations are preceded with the keyword private
(as in line 7). Like public
, keyword private
is an access modifier. Variables or methods declared with access modifier private
are accessible only to methods of the class in which they are declared. Thus, variable courseName
can be used only in methods setCourseName
, getCourseName
and displayMessage
of (every object of) class GradeBook
.
Software Engineering Observation 3.3
Precede every field and method declaration with an access modifier. As a rule of thumb, instance variables should be declared private
and methods should be declared public
. (We’ll see that it is appropriate to declare certain methods private
, if they will be accessed only by other methods of the class.)
Good Programming Practice 3.1
We prefer to list the fields of a class first, so that, as you read the code, you see the names and types of the variables before you see them used in the methods of the class. It is possible to list the class’s fields anywhere in the class outside its method declarations, but scattering them tends to lead to hard-to-read code.
Good Programming Practice 3.2
Place a blank line between method declarations to separate the methods and enhance program readability.
Declaring instance variables with access modifier private
is known as data hiding. When a program creates (instantiates) an object of class GradeBook
, variable courseName
is encapsulated (hidden) in the object and can be accessed only by methods of the object’s class. In class GradeBook
, methods setCourseName
and getCourseName
manipulate the instance variable courseName
.
Method setCourseName
(lines 10–13) does not return any data when it completes its task, so its return type is void
. The method receives one parameter—name
—which represents the course name that will be passed to the method as an argument. Line 12 assigns name
to instance variable courseName
.
Method getCourseName
(lines 16–19) returns a particular GradeBook
object’s courseName
. The method has an empty parameter list, so it does not require additional information to perform its task. The method specifies that it returns a String
—this is known as the method’s return type. When a method that specifies a return type is called and completes its task, the method returns a result to its calling method. For example, when you go to an automated teller machine (ATM) and request your account balance, you expect the ATM to give you back a value that represents your balance. Similarly, when a statement calls method getCourseName
on a GradeBook
object, the statement expects to receive the GradeBook
’s course name (in this case, a String
, as specified in the method declaration’s return type). If you have a method square
that returns the square of its argument, you would expect the statement
int result = square( 2 );
to return 4
from method square
and assign 4
to the variable result
. If you have a method maximum
that returns the largest of three integer arguments, you would expect the following statement
int biggest = maximum( 27, 114, 51 );
to return 114
from method maximum
and assign 114 to variable biggest
.
Note that the statements in lines 12 and 18 each use courseName
even though it was not declared in any of the methods. We can use courseName
in the methods of class GradeBook
because courseName
is a field of the class. Also note that the order in which methods are declared in a class does not determine when they are called at execution time. So method getCourseName
could be declared before method setCourseName
.
Method displayMessage
(lines 22–28) does not return any data when it completes its task, so its return type is void
. The method does not receive parameters, so the parameter list is empty. Lines 26–27 output a welcome message that includes the value of instance variable courseName
. Once again, we need to create an object of class GradeBook
and call its methods before the welcome message can be displayed.
GradeBookTest
Class That Demonstrates Class GradeBook
Class GradeBookTest
(Fig. 3.8) creates one object of class GradeBook
and demonstrates its methods. Line 11 creates a Scanner
that will be used to obtain a course name from the user. Line 14 creates a GradeBook
object and assigns it to local variable myGradeBook
of type GradeBook
. Lines 17–18 display the initial course name calling the object’s getCourseName
method. Note that the first line of the output shows the name “null
.” Unlike local variables, which are not automatically initialized, every field has a default initial value—a value provided by Java when the programmer does not specify the field’s initial value. Thus, fields are not required to be explicitly initialized before they are used in a program—unless they must be initialized to values other than their default values. The default value for a field of type String
(like courseName
in this example) is null
, which we say more about in Section 3.6.
Line 21 prompts the user to enter a course name. Local String
variable theName
(declared in line 22) is initialized with the course name entered by the user, which is returned by the call to the nextLine
method of the Scanner
object input
. Line 23 calls object myGradeBook
’s setCourseName
method and supplies theName
as the method’s argument. When the method is called, the argument’s value is assigned to parameter name
(line 10, Fig. 3.7) of method setCourseName
(lines 10–13, Fig. 3.7). Then the parameter’s value is assigned to instance variable courseName
(line 12, Fig. 3.7). Line 24 (Fig. 3.8) skips a line in the output, then line 27 calls object myGradeBook
’s displayMessage
method to display the welcome message containing the course name.
A class’s private
fields can be manipulated only by methods of that class. So a client of an object—that is, any class that calls the object’s methods—calls the class’s public
methods to manipulate the private
fields of an object of the class. This is why the statements in method main
(Fig. 3.8) call the setCourseName
, getCourseName
and displayMessage
methods on a GradeBook
object. Classes often provide public
methods to allow clients of the class to set (i.e., assign values to) or get (i.e., obtain the values of) private
instance variables. The names of these methods need not begin with set or get, but this naming convention is highly recommended in Java and is required for special Java software components called JavaBeans that can simplify programming in many Java integrated development environments (IDEs). The method that sets instance variable courseName
in this example is called setCourseName
, and the method that gets the value of instance variable courseName
is called getCourseName
.
GradeBook
with an Instance Variable and set and get MethodsFigure 3.9 contains an updated UML class diagram for the version of class GradeBook
in Fig. 3.7. This diagram models class GradeBook
’s instance variable courseName
as an attribute in the middle compartment of the class. The UML represents instance variables as attributes by listing the attribute name, followed by a colon and the attribute type. The UML type of attribute courseName
is String
. Instance variable courseName
is private
in Java, so the class diagram lists a minus sign (–
) in front of the corresponding attribute’s name. Class GradeBook
contains three public
methods, so the class diagram lists three operations in the third compartment. Recall that the plus (+) sign before each operation name indicates that the operation is public
. Operation setCourseName
has a String
parameter called name
. The UML indicates the return type of an operation by placing a colon and the return type after the parentheses following the operation name. Method getCourseName
of class GradeBook
(Fig. 3.7) has a String
return type in Java, so the class diagram shows a String
return type in the UML. Note that operations setCourseName
and displayMessage
do not return values (i.e., they return void
in Java), so the UML class diagram does not specify a return type after the parentheses of these operations.
Fig. 3.9. UML class diagram indicating that class GradeBook
has a courseName
attribute of UML type String
and three operations—setCourseName
(with a name
parameter of UML type String
), getCourseName
(which returns UML type String
) and displayMessage
.
Data types in Java are divided into two categories—primitive types and reference types (sometimes called nonprimitive types). The primitive types are boolean
, byte
, char
, short
, int
, long
, float
and double
. All nonprimitive types are reference types, so classes, which specify the types of objects, are reference types.
A primitive-type variable can store exactly one value of its declared type at a time. For example, an int
variable can store one whole number (such as 7) at a time. When another value is assigned to that variable, its initial value is replaced. Primitive-type instance variables are initialized by default—variables of types byte
, char
, short
, int
, long
, float
and double
are initialized to 0, and variables of type boolean
are initialized to false
. You can specify your own initial values for primitive-type variables. Recall that local variables are not initialized by default.
Any attempt to use a local variable that has not been initialized results in a compilation error.
Programs use variables of reference types (normally called references) to store the locations of objects in the computer’s memory. Such a variable is said to refer to an object in the program. Objects that are referenced may each contain many instance variables and methods. Line 14 of Fig. 3.8 creates an object of class GradeBook
, and the variable myGradeBook
contains a reference to that GradeBook
object. Reference-type instance variables are initialized by default to the value null
—a reserved word that represents a “reference to nothing.” This is why the first call to getCourseName
in line 18 of Fig. 3.8 returned null
—the value of courseName
had not been set, so the default initial value null
was returned. The complete list of reserved words and keywords is listed in Appendix C, Keywords and Reserved Words.
A reference to an object is required to invoke (i.e., call) the object’s methods. In the application of Fig. 3.8, the statements in method main
use the variable myGradeBook
to send messages to the GradeBook
object. These messages are calls to methods (like setCourseName
and getCourseName
) that enable the program to interact with the GradeBook
object. For example, the statement in line 23 uses myGradeBook
to send the setCourseName
message to the GradeBook
object. The message includes the argument that setCourseName
requires to perform its task. The GradeBook
object uses this information to set the courseName
instance variable. Note that primitive-type variables do not refer to objects, so such variables cannot be used to invoke methods.
Software Engineering Observation 3.4
A variable’s declared type (e.g., int
, double
or GradeBook
) indicates whether the variable is of a primitive or a reference type. If a variable’s type is not one of the eight primitive types, then it is a reference type. For example, Account account1
indicates that account1
is a reference to an Account
object).
As mentioned in Section 3.5, when an object of class GradeBook
(Fig. 3.7) is created, its instance variable courseName
is initialized to null
by default. What if you want to provide a course name when you create a GradeBook
object? Each class you declare can provide a constructor that can be used to initialize an object of a class when the object is created. In fact, Java requires a constructor call for every object that is created. Keyword new
calls the class’s constructor to perform the initialization. The constructor call is indicated by the class name followed by parentheses—the constructor must have the same name as the class. For example, line 14 of Fig. 3.8 first uses new
to create a GradeBook
object. The empty parentheses after “new GradeBook"
indicate a call to the class’s constructor without arguments. By default, the compiler provides a default constructor with no parameters in any class that does not explicitly include a constructor. When a class has only the default constructor, its instance variables are initialized to their default values. Variables of types char
, byte
, short
, int
, long
, float
and double
are initialized to 0
, variables of type boolean
are initialized to false
, and reference-type variables are initialized to null
.
When you declare a class, you can provide your own constructor to specify custom initialization for objects of your class. For example, you might want to specify a course name for a GradeBook
object when the object is created, as in
GradeBook myGradeBook =
new GradeBook( "CS101 Introduction to Java Programming" );
In this case, the argument "CS101 Introduction to Java Programming"
is passed to the GradeBook
object’s constructor and used to initialize the courseName
. The preceding statement requires that the class provide a constructor with a String
parameter. Figure 3.10 contains a modified GradeBook
class with such a constructor.
Fig. 3.10. GradeBook
class with a constructor to initialize the course name.
Lines 9–12 declare the constructor for class GradeBook
. A constructor must have the same name as its class. Like a method, a constructor specifies in its parameter list the data it requires to perform its task. When you create a new object (as we’ll do in Fig. 3.11), this data is placed in the parentheses that follow the class name. Line 9 indicates that class GradeBook
’s constructor has a String
parameter called name
. The name
passed to the constructor is assigned to instance variable courseName
in line 11 of the constructor’s body.
Fig. 3.11. GradeBook
constructor used to specify the course name at the time each GradeBook
object is created.
Figure 3.11 demonstrates initializing GradeBook
objects using the constructor. Lines 11–12 create and initialize the GradeBook
object gradeBook1
. The constructor of class GradeBook
is called with the argument "CS101 Introduction to Java Programming"
to initialize the course name. The class instance creation expression to the right of the =
in lines 11–12 returns a reference to the new object, which is assigned to the variable gradeBook1
. Lines 13–14 repeat this process for another GradeBook
object gradeBook2
, this time passing the argument "CS102 Data Structures in Java"
to initialize the course name for gradeBook2
. Lines 17–20 use each object’s getCourseName
method to obtain the course names and show that they were indeed initialized when the objects were created. In the introduction to Section 3.5, you learned that each instance (i.e., object) of a class contains its own copy of the class’s instance variables. The output confirms that each GradeBook
maintains its own copy of instance variable courseName
.
Like methods, constructors also can take arguments. However, an important difference between constructors and methods is that constructors cannot return values, so they cannot specify a return type (not even void
). Normally, constructors are declared public
. If a class does not include a constructor, the class’s instance variables are initialized to their default values. If a programmer declares any constructors for a class, the Java compiler will not create a default constructor for that class.
Error-Prevention Tip 3.2
Unless default initialization of your class’s instance variables is acceptable, provide a constructor to ensure that your class’s instance variables are properly initialized with meaningful values when each new object of your class is created.
GradeBook
’s UML Class DiagramThe UML class diagram of Fig. 3.12 models class GradeBook
of Fig. 3.10, which has a constructor that has a name
parameter of type String
. Like operations, the UML models constructors in the third compartment of a class in a class diagram. To distinguish a constructor from a class’s operations, the UML requires that the word “constructor” be placed between guillemets (« and ») before the constructor’s name. It is customary to list constructors before other operations in the third compartment.
Fig. 3.12. UML class diagram indicating that class GradeBook
has a constructor that has a name
parameter of UML type String
.
double
In our next application, we depart temporarily from our GradeBook
case study to declare a class called Account
that maintains the balance of a bank account. Most account balances are not whole numbers (e.g., 0, –22 and 1024). For this reason, class Account
represents the account balance as a floating-point number (i.e., a number with a decimal point, such as 7.33, 0.0975 or 1000.12345). Java provides two primitive types for storing floating-point numbers in memory—float
and double
. The primary difference between them is that double
variables can store numbers with larger magnitude and finer detail (i.e., more digits to the right of the decimal point—also known as the number’s precision) than float
variables.
Variables of type float
represent single-precision floating-point numbers and have seven significant digits. Variables of type double
represent double-precision floating-point numbers. These require twice as much memory as float
variables and provide 15 significant digits—approximately double the precision of float
variables. For the range of values required by most programs, variables of type float
should suffice, but you can use double
to “play it safe.” In some applications, even variables of type double
will be inadequate—such applications are beyond the scope of this book. Most programmers represent floating-point numbers with type double
. In fact, Java treats all floating-point numbers you type in a program’s source code (such as 7.33 and 0.0975) as double
values by default. Such values in the source code are known as floating-point literals. See Appendix D, Primitive Types, for the ranges of values for float
s and double
s.
Although floating-point numbers are not always 100% precise, they have numerous applications. For example, when we speak of a “normal” body temperature of 98.6, we do not need to be precise to a large number of digits. When we read the temperature on a thermometer as 98.6, it may actually be 98.5999473210643. Calling this number simply 98.6 is fine for most applications involving body temperatures. Due to the imprecise nature of floating-point numbers, type double
is preferred over type float
because double
variables can represent floating-point numbers more accurately. For this reason, we use type double
throughout the book.
Floating-point numbers also arise as a result of division. In conventional arithmetic, when we divide 10 by 3, the result is 3.3333333..., with the sequence of 3s repeating infinitely. The computer allocates only a fixed amount of space to hold such a value, so clearly the stored floating-point value can be only an approximation.
Common Programming Error 3.4
Using floating-point numbers in a manner that assumes they are represented precisely can lead to logic errors.
Account
Class with an Instance Variable of Type double
Our next application (Figs. 3.13–3.14) contains a class named Account
(Fig. 3.13) that maintains the balance of a bank account. A typical bank services many accounts, each with its own balance, so line 7 declares an instance variable named balance
of type double
. Variable balance
is an instance variable because it is declared in the body of the class but outside the class’s method declarations (lines 10–16, 19–22 and 25–28). Every instance (i.e., object) of class Account
contains its own copy of balance
.
Fig. 3.13. Account
class with an instance variable of type double
.
Fig. 3.14. Inputting and outputting floating-point numbers with Account
objects.
Class Account
contains a constructor and two methods. Since it is common for someone opening an account to place money in the account immediately, the constructor (lines 10–16) receives a parameter initialBalance
of type double
that represents the account’s starting balance. Lines 14–15 ensure that initialBalance
is greater than 0.0
. If so, initialBalance
’s value is assigned to instance variable balance
. Otherwise, balance
remains at 0.0
—its default initial value.
Method credit
(lines 19–22) does not return any data when it completes its task, so its return type is void
. The method receives one parameter named amount
—a double
value that will be added to the balance. Line 21 adds amount
to the current value of balance
, then assigns the result to balance
(thus replacing the prior balance amount).
Method getBalance
(lines 25–28) allows clients of the class (i.e., other classes that use this class) to obtain the value of a particular Account
object’s balance
. The method specifies return type double
and an empty parameter list.
Once again, note that the statements in lines 15, 21 and 27 use instance variable balance
even though it was not declared in any of the methods. We can use balance
in these methods because it is an instance variable of the class.
AccountTest
Class to Use Class Account
Class AccountTest
(Fig. 3.14) creates two Account
objects (lines 10–11) and initializes them with 50.00
and -7.53
, respectively. Lines 14–17 output the balance in each Account
by calling the Account
’s getBalance
method. When method getBalance
is called for account1
from line 15, the value of account1
’s balance is returned from line 27 of Fig. 3.13 and displayed by the System.out.printf
statement (Fig. 3.14, lines 14–15). Similarly, when method getBalance
is called for account2
from line 17, the value of the account2
’s balance is returned from line 27 of Fig. 3.13 and displayed by the System.out.printf
statement (Fig. 3.14, lines 16–17). Note that the balance of account2
is 0.00
because the constructor ensured that the account could not begin with a negative balance. The value is output by printf
with the format specifier %.2f
. The format specifier %f
is used to output values of type float
or double
. The .2
between %
and f
represents the number of decimal places (2
) that should be output to the right of the decimal point in the floating-point number—also known as the number’s precision. Any floating-point value output with %.2f
will be rounded to the hundredths position—for example, 123.457 would be rounded to 123.46, and 27.333 would be rounded to 27.33.
Line 20 creates a Scanner
that will be used to obtain deposit amounts from a user. Line 21 declares local variable depositAmount
to store each deposit amount entered by the user. Unlike the instance variable balance
in class Account
, local variable depositAmount
in main
is not initialized to 0.0 by default. However, this variable does not need to be initialized here because its value will be determined by the user’s input.
Line 23 prompts the user to enter a deposit amount for account1
. Line 24 obtains the input from the user by calling Scanner
object input
’s nextDouble
method, which returns a double
value entered by the user. Lines 25–26 display the deposit amount. Line 27 calls object account1
’s credit
method and supplies depositAmount
as the method’s argument. When the method is called, the argument’s value is assigned to parameter amount
(line 19 of Fig. 3.13) of method credit
(lines 19–22 of Fig. 3.13), then method credit
adds that value to the balance
(line 21 of Fig. 3.13). Lines 30–33 (Fig. 3.14) output the balances of both Account
s again to show that only account1
’s balance changed.
Line 35 prompts the user to enter a deposit amount for account2
. Line 36 obtains the input from the user by calling Scanner
object input
’s nextDouble
method. Lines 37–38 display the deposit amount. Line 39 calls object account2
’s credit
method and supplies depositAmount
as the method’s argument, then method credit
adds that value to the balance. Finally, lines 42–45 output the balances of both Account
s again to show that only account2
’s balance changed.
Account
The UML class diagram in Fig. 3.15 models class Account
of Fig. 3.13. The diagram models the private
attribute balance
with UML type Double
to correspond to the class’s instance variable balance
of Java type double
. The diagram models class Account
’s constructor with a parameter initialBalance
of UML type Double
in the third compartment of the class. The class’s two public
methods are modeled as operations in the third compartment as well. The diagram models operation credit
with an amount
parameter of UML type Double
(because the corresponding method has an amount
parameter of Java type double
) and operation getBalance
with a return type of Double
(because the corresponding Java method returns a double
value).
Fig. 3.15. UML class diagram indicating that class Account
has a private balance
attribute of UML type Double
, a constructor (with a parameter of UML type Double
) and two public
operations—credit
(with an amount
parameter of UML type Double
) and getBalance
(returns UML type Double
).
Now we begin designing the ATM system that we introduced in Chapter 2. In this section, we identify the classes that are needed to build the ATM system by analyzing the nouns and noun phrases that appear in the requirements document. We introduce UML class diagrams to model the relationships between these classes. This is an important first step in defining the structure of our system.
We begin our OOD process by identifying the classes required to build the ATM system. We’ll eventually describe these classes using UML class diagrams and implement these classes in Java. First, we review the requirements document of Section 2.8 and identify key nouns and noun phrases to help us identify classes that comprise the ATM system. We may decide that some of these nouns and noun phrases are attributes of other classes in the system. We may also conclude that some of the nouns do not correspond to parts of the system and thus should not be modeled at all. Additional classes may become apparent to us as we proceed through the design process.
Figure 3.16 lists the nouns and noun phrases found in the requirements document of Section 2.8. We list them from left to right in the order in which we first encounter them in the requirements document. We list only the singular form of each noun or noun phrase.
Fig. 3.16. Nouns and noun phrases in the ATM requirements document.
We create classes only for the nouns and noun phrases that have significance in the ATM system. We don’t model “bank” as a class, because the bank is not a part of the ATM system—the bank simply wants us to build the ATM. “Customer” and “user” also represent outside entities—they are important because they interact with our ATM system, but we do not need to model them as classes in the ATM software. Recall that we modeled an ATM user (i.e., a bank customer) as the actor in the use case diagram of Fig. 2.16.
We do not model “$20 bill” or “deposit envelope” as classes. These are physical objects in the real world, but they are not part of what is being automated. We can adequately represent the presence of bills in the system using an attribute of the class that models the cash dispenser. (We assign attributes to the ATM system’s classes in Section 4.12.) For example, the cash dispenser maintains a count of the number of bills it contains. The requirements document does not say anything about what the system should do with deposit envelopes after it receives them. We can assume that simply acknowledging the receipt of an envelope—an operation performed by the class that models the deposit slot—is sufficient to represent the presence of an envelope in the system. (We assign operations to the ATM system’s classes in Section 6.19.)
In our simplified ATM system, representing various amounts of “money,” including an account’s “balance,” as attributes of classes seems most appropriate. Likewise, the nouns “account number” and “PIN” represent significant pieces of information in the ATM system. They are important attributes of a bank account. They do not, however, exhibit behaviors. Thus, we can most appropriately model them as attributes of an account class.
Though the requirements document frequently describes a “transaction” in a general sense, we do not model the broad notion of a financial transaction at this time. Instead, we model the three types of transactions (i.e., “balance inquiry,” “withdrawal” and “deposit”) as individual classes. These classes possess specific attributes needed for executing the transactions they represent. For example, a withdrawal needs to know the amount of money the user wants to withdraw. A balance inquiry, however, does not require any additional data. Furthermore, the three transaction classes exhibit unique behaviors. A withdrawal includes dispensing cash to the user, whereas a deposit involves receiving deposit envelopes from the user. [Note: In Section 10.8, we “factor out” common features of all transactions into a general “transaction” class using the object-oriented concept of inheritance.]
We determine the classes for our system based on the remaining nouns and noun phrases from Fig. 3.16. Each of these refers to one or more of the following:
• ATM
• screen
• keypad
• cash dispenser
• deposit slot
• account
• bank database
• balance inquiry
• withdrawal
• deposit
The elements of this list are likely to be classes we’ll need to implement our system.
We can now model the classes in our system based on the list we’ve created. We capitalize class names in the design process—a UML convention—as we’ll do when we write the actual Java code that implements our design. If the name of a class contains more than one word, we run the words together and capitalize each word (e.g., MultipleWordName
). Using this convention, we create classes ATM
, Screen
, Keypad
, CashDispenser
, DepositSlot
, Account
, BankDatabase
, BalanceInquiry
, Withdrawal
and Deposit
. We construct our system using these classes as building blocks. Before we begin building the system, however, we must gain a better understanding of how the classes relate to one another.
The UML enables us to model, via class diagrams, the classes in the ATM system and their interrelationships. Figure 3.17 represents class ATM
. In the UML, each class is modeled as a rectangle with three compartments. The top compartment contains the name of the class centered horizontally in boldface. The middle compartment contains the class’s attributes. (We discuss attributes in Section 4.12 and Section 5.9.) The bottom compartment contains the class’s operations (discussed in Section 6.19). In Fig. 3.17, the middle and bottom compartments are empty because we have not yet determined this class’s attributes and operations.
Fig. 3.17. Representing a class in the UML using a class diagram.
Class diagrams also show the relationships between the classes of the system. Figure 3.18 shows how our classes ATM
and Withdrawal
relate to one another. For the moment, we choose to model only this subset of classes for simplicity. We present a more complete class diagram later in this section. Notice that the rectangles representing classes in this diagram are not subdivided into compartments. The UML allows the suppression of class attributes and operations in this manner to create more readable diagrams, when appropriate. Such a diagram is said to be an elided diagram—one in which some information, such as the contents of the second and third compartments, is not modeled. We’ll place information in these compartments in Section 4.12 and Section 6.19.
Fig. 3.18. Class diagram showing an association among classes.
In Fig. 3.18, the solid line that connects the two classes represents an association—a relationship between classes. The numbers near each end of the line are multiplicity values, which indicate how many objects of each class participate in the association. In this case, following the line from one end to the other reveals that, at any given moment, one ATM
object participates in an association with either zero or one Withdrawal
objects—zero if the current user is not currently performing a transaction or has requested a different type of transaction, and one if the user has requested a withdrawal. The UML can model many types of multiplicity. Figure 3.19 lists and explains the multiplicity types.
Fig. 3.19. Multiplicity types.
An association can be named. For example, the word Executes
above the line connecting classes ATM
and Withdrawal
in Fig. 3.18 indicates the name of that association. This part of the diagram reads “one object of class ATM
executes zero or one objects of class Withdrawal
.” Note that association names are directional, as indicated by the filled arrowhead—so it would be improper, for example, to read the preceding association from right to left as “zero or one objects of class Withdrawal
execute one object of class ATM
.”
The word currentTransaction
at the Withdrawal
end of the association line in Fig. 3.18 is a role name, which identifies the role the Withdrawal
object plays in its relationship with the ATM
. A role name adds meaning to an association between classes by identifying the role a class plays in the context of an association. A class can play several roles in the same system. For example, in a school personnel system, a person may play the role of “professor” when relating to students. The same person may take on the role of “colleague” when participating in an association with another professor, and “coach” when coaching student athletes. In Fig. 3.18, the role name currentTransaction
indicates that the Withdrawal
object participating in the Executes
association with an object of class ATM
represents the transaction currently being processed by the ATM. In other contexts, a Withdrawal
object may take on other roles (e.g., the previous transaction). Notice that we do not specify a role name for the ATM
end of the Executes
association. Role names in class diagrams are often omitted when the meaning of an association is clear without them.
In addition to indicating simple relationships, associations can specify more complex relationships, such as objects of one class being composed of objects of other classes. Consider a real-world automated teller machine. What “pieces” does a manufacturer put together to build a working ATM? Our requirements document tells us that the ATM is composed of a screen, a keypad, a cash dispenser and a deposit slot.
In Fig. 3.20, the solid diamonds attached to the association lines of class ATM
indicate that class ATM
has a composition relationship with classes Screen
, Keypad
, CashDispenser
and DepositSlot
. Composition implies a whole/part relationship. The class that has the composition symbol (the solid diamond) on its end of the association line is the whole (in this case, ATM
), and the classes on the other end of the association lines are the parts—in this case, classes Screen
, Keypad
, CashDispenser
and DepositSlot
. The compositions in Fig. 3.20 indicate that an object of class ATM
is formed from one object of class Screen
, one object of class CashDispenser
, one object of class Keypad
and one object of class DepositSlot
. The ATM has a screen, a keypad, a cash dispenser and a deposit slot. The has-a relationship defines composition. (We’ll see in the Software Engineering Case Study section in Chapter 10 that the is-a relationship defines inheritance.)
Fig. 3.20. Class diagram showing composition relationships.
According to the UML specification (www.uml.org), composition relationships have the following properties:
1. Only one class in the relationship can represent the whole (i.e., the diamond can be placed on only one end of the association line). For example, either the screen is part of the ATM or the ATM is part of the screen, but the screen and the ATM cannot both represent the whole in the relationship.
2. The parts in the composition relationship exist only as long as the whole, and the whole is responsible for the creation and destruction of its parts. For example, the act of constructing an ATM includes manufacturing its parts. Also, if the ATM is destroyed, its screen, keypad, cash dispenser and deposit slot are also destroyed.
3. A part may belong to only one whole at a time, although the part may be removed and attached to another whole, which then assumes responsibility for the part.
The solid diamonds in our class diagrams indicate composition relationships that fulfill these three properties. If a has-a relationship does not satisfy one or more of these criteria, the UML specifies that hollow diamonds be attached to the ends of association lines to indicate aggregation—a weaker form of composition. For example, a personal computer and a computer monitor participate in an aggregation relationship—the computer has a monitor, but the two parts can exist independently, and the same monitor can be attached to multiple computers at once, thus violating the second and third properties of composition.
Figure 3.21 shows a class diagram for the ATM system. This diagram models most of the classes that we identified earlier in this section, as well as the associations between them that we can infer from the requirements document. [Note: Classes BalanceInquiry
and Deposit
participate in associations similar to those of class Withdrawal
, so we have chosen to omit them from this diagram to keep the diagram simple. In Chapter 10, we expand our class diagram to include all the classes in the ATM system.]
Fig. 3.21. Class diagram for the ATM system model.
Figure 3.21 presents a graphical model of the structure of the ATM system. This class diagram includes classes BankDatabase
and Account
, and several associations that were not present in either Fig. 3.18 or Fig. 3.20. The class diagram shows that class ATM
has a one-to-one relationship with class BankDatabase
—one ATM
object authenticates users against one BankDatabase
object. In Fig. 3.21, we also model the fact that the bank’s database contains information about many accounts—one object of class BankDatabase
participates in a composition relationship with zero or more objects of class Account
. Recall from Fig. 3.19 that the multiplicity value 0..* at the Account
end of the association between class BankDatabase
and class Account
indicates that zero or more objects of class Account
take part in the association. Class BankDatabase
has a one-to-many relationship with class Account
—the BankDatabase
stores many Account
s. Similarly, class Account
has a many-to-one relationship with class BankDatabase
—there can be many Account
s stored in the BankDatabase
. [Note: Recall from Fig. 3.19 that the multiplicity value * is identical to 0..*. We include 0..* in our class diagrams for clarity.]
Figure 3.21 also indicates that if the user is performing a withdrawal, “one object of class Withdrawal
accesses/modifies an account balance through one object of class BankDatabase
.” We could have created an association directly between class Withdrawal
and class Account
. The requirements document, however, states that the “ATM must interact with the bank’s account information database” to perform transactions. A bank account contains sensitive information, and systems engineers must always consider the security of personal data when designing a system. Thus, only the BankDatabase
can access and manipulate an account directly. All other parts of the system must interact with the database to retrieve or update account information (e.g., an account balance).
The class diagram in Fig. 3.21 also models associations between class Withdrawal
and classes Screen
, CashDispenser
and Keypad
. A withdrawal transaction includes prompting the user to choose a withdrawal amount and receiving numeric input. These actions require the use of the screen and the keypad, respectively. Furthermore, dispensing cash to the user requires access to the cash dispenser.
Classes BalanceInquiry
and Deposit
, though not shown in Fig. 3.21, take part in several associations with the other classes of the ATM system. Like class Withdrawal
, each of these classes associates with classes ATM
and BankDatabase
. An object of class BalanceInquiry
also associates with an object of class Screen
to display the balance of an account to the user. Class Deposit
associates with classes Screen
, Keypad
and DepositSlot
. Like withdrawals, deposit transactions require use of the screen and the keypad to display prompts and receive input, respectively. To receive deposit envelopes, an object of class Deposit
accesses the deposit slot.
We have now identified the classes in our ATM system (although we may discover others as we proceed with the design and implementation). In Section 4.12, we determine the attributes for each of these classes, and in Section 5.9, we use these attributes to examine how the system changes over time.
3.1 Suppose we have a class Car
that represents a car. Think of some of the different pieces that a manufacturer would put together to produce a whole car. Create a class diagram (similar to Fig. 3.20) that models some of the composition relationships of class Car
.
3.2 Suppose we have a class File
that represents an electronic document in a standalone, non-networked computer represented by class Computer
. What sort of association exists between class Computer
and class File
?
a. Class Computer
has a one-to-one relationship with class File
.
b. Class Computer
has a many-to-one relationship with class File
.
c. Class Computer
has a one-to-many relationship with class File
.
d. Class Computer
has a many-to-many relationship with class File
.
3.3 State whether the following statement is true or false, and if false, explain why: A UML diagram in which a class’s second and third compartments are not modeled is said to be an elided diagram.
3.4 Modify the class diagram of Fig. 3.21 to include class Deposit
instead of class Withdrawal
.
3.1 [Note: Answers may vary.] Figure 3.22 presents a class diagram that shows some of the composition relationships of a class Car
.
Fig. 3.22. Class diagram showing composition relationships of a class Car
.
3.2 c. [Note: In a computer network, this relationship could be many-to-many.]
3.3 True.
3.4 Figure 3.23 presents a class diagram for the ATM including class Deposit
instead of class Withdrawal
(as in Fig. 3.21). Note that Deposit
does not access CashDispenser
, but does access DepositSlot
.
Fig. 3.23. Class diagram for the ATM system model including class Deposit
.
In this chapter, you learned the basic concepts of classes, objects, methods and instance variables—these will be used in most Java applications you create. In particular, you learned how to declare instance variables of a class to maintain data for each object of the class, and how to declare methods that operate on that data. You learned how to call a method to tell it to perform its task and how to pass information to methods as arguments. You learned the difference between a local variable of a method and an instance variable of a class and that only instance variables are initialized automatically. You also learned how to use a class’s constructor to specify the initial values for an object’s instance variables. Throughout the chapter, you saw how the UML can be used to create class diagrams that model the constructors, methods and attributes of classes. Finally, you learned about floating-point numbers—how to store them with variables of primitive type double
, how to input them with a Scanner
object and how to format them with printf
and format specifier %f
for display purposes. In the next chapter we begin our introduction to control statements, which specify the order in which a program’s actions are performed. You’ll use these in your methods to specify how they should perform their tasks.