Objectives
In this chapter you’ll learn:
• Encapsulation and data hiding.
• The notions of data abstraction and abstract data types (ADTs).
• To use keyword this
.
• To use static
variables and methods.
• To import static
members of a class.
• To use the enum
type to create sets of constants with unique identifiers.
• To declare enum
constants with parameters.
• To organize classes in packages to promote reuse.
Instead of this absurd division into sexes, they ought to class people as static and dynamic.
—Evelyn Waugh
Is it a world to hide virtues in?
—William Shakespeare
But what, to serve our private ends, Forbids the cheating of our friends?
—Charles Churchill
This above all: to thine own self be true.
—William Shakespeare
Don’t be “consistent,” but be simply true.
—Oliver Wendell Holmes, Jr.
Outline
8.1 Introduction
8.2 Time
Class Case Study
8.3 Controlling Access to Members
8.4 Referring to the Current Object’s Members with the this
Reference
8.5 Time
Class Case Study: Overloaded Constructors
8.6 Default and No-Argument Constructors
8.7 Notes on Set and Get Methods
8.8 Composition
8.9 Enumerations
8.10 Garbage Collection and Method finalize
8.11 static
Class Members
8.12 static
Import
8.13 final
Instance Variables
8.14 Software Reusability
8.15 Data Abstraction and Encapsulation
8.16 Time
Class Case Study: Creating Packages
8.17 Package Access
8.18 (Optional) Software Engineering Case Study: Starting to Program the Classes of the ATM System
8.19 Wrap-Up
In our prior discussions of object-oriented programs, we introduced many basic concepts and terminology that relate to Java object-oriented programming (OOP). We also discussed our program development methodology: We selected appropriate variables and methods for each program and specified the manner in which an object of our class collaborated with objects of Java API classes to accomplish the program’s overall goals.
In this chapter, we take a deeper look at building classes, controlling access to members of a class and creating constructors. We discuss composition—a capability that allows a class to have references to objects of other classes as members. We reexamine the use of set and get methods and further explore the class type enum
that enables programmers to declare and manipulate sets of unique identifiers that represent constant values. In Section 6.10, we introduced the basic enum
type, which appeared within another class and simply declared a set of constants. In this chapter, we discuss the relationship between enum
types and classes, demonstrating that an enum
, like a class, can be declared in its own file with constructors, methods and fields. The chapter also discusses static
class members and final
instance variables in detail. We investigate issues such as software reusability, data abstraction and encapsulation. Finally, we explain how to organize classes in packages to help manage large applications and promote reuse, then show a special relationship between classes in the same package.
Chapter 9, Object-Oriented Programming: Inheritance, and Chapter 10, Object-Oriented Programming: Polymorphism, introduce two additional key object-oriented programming technologies.
Time
Class Case StudyTime1
Class DeclarationOur first example consists of two classes—Time1
(Fig. 8.1) and Time1Test
(Fig. 8.2). Class Time1
represents the time of day. Class Time1Test
is an application class in which the main
method creates one object of class Time1
and invokes its methods. These classes must be declared in separate files because they are both public
classes. The output of this program appears in Fig. 8.2.
Fig. 8.1. Time1
class declaration maintains the time in 24-hour format.
Fig. 8.2. Time1
object used in an application.
Class Time1
contains three private
instance variables of type int
(Fig. 8.1, lines 6–8)—hour
, minute
and second
—that represent the time in universal-time format (24-hour clock format in which hours are in the range 0–23). Class Time1
contains public
methods setTime
(lines 12–17), toUniversalString
(lines 20–23) and toString
(lines 26–31). These methods are also called the public
services or the public
interface that the class provides to its clients.
In this example, class Time1
does not declare a constructor, so the class has a default constructor that is supplied by the compiler. Each instance variable implicitly receives the default value 0
for an int
. Note that instance variables also can be initialized when they are declared in the class body using the same initialization syntax as with a local variable.
Method setTime
(lines 12–17) is a public
method that declares three int
parameters and uses them to set the time. A conditional expression tests each argument to determine whether the value is in a specified range. For example, the hour
value (line 14) must be greater than or equal to 0 and less than 24, because universal-time format represents hours as integers from 0 to 23 (e.g., 1 PM is hour 13 and 11 PM is hour 23; midnight is hour 0 and noon is hour 12). Similarly, both minute
and second
values (lines 15 and 16) must be greater than or equal to 0 and less than 60. Any values outside these ranges are set to zero to ensure that a Time1
object always contains consistent data—that is, the object’s data values are always kept in range, even if the values provided as arguments to method setTime
were incorrect. In this example, zero is a consistent value for hour
, minute
and second
.
A value passed to setTime
is a correct value if it is in the allowed range for the member it is initializing. So, any number in the range 0–23 would be a correct value for the hour
. A correct value is always a consistent value. However, a consistent value is not necessarily a correct value. If setTime
sets hour
to 0
because the argument received was out of range, then setTime
is taking an incorrect value and making it consistent, so the object remains in a consistent state at all times. The correct time of day could actually be 11 AM, but because the person may have accidentally entered an out-of-range (incorrect) time, we choose to set the hour to the consistent value of zero. In this case, you might want to indicate that the object is incorrect. In Chapter 13, Exception Handling, you’ll learn elegant techniques that enable your classes to indicate when incorrect values are received.
Software Engineering Observation 8.1
Methods that modify the values of private
variables should verify that the intended new values are proper. If they are not, the set methods should place the private
variables into an appropriate consistent state.
Method toUniversalString
(lines 20–23) takes no arguments and returns a String
in universal-time format, consisting of six digits—two for the hour, two for the minute and two for the second. For example, if the time were 1:30:07 PM, method toUniversalString
would return 13:30:07
. The return
statement (line 22) uses static
method format
of class String
to return a String
containing the formatted hour
, minute
and second
values, each with two digits and possibly a leading 0
(specified with the 0
flag). Method format
is similar to method System.out.printf
except that format
returns a formatted String
rather than displaying it in a command window. The formatted String
is returned by method toUniversalString
.
Method toString
(lines 26–31) takes no arguments and returns a String
in standard-time format, consisting of the hour
, minute
and second
values separated by colons and followed by an AM or PM indicator (e.g., 1:27:06 PM
). Like method toUniversalString
, method toString
uses static String
method format
to format the minute
and second
as two-digit values with leading zeros if necessary. Line 29 uses a conditional operator (?:
) to determine the value for hour
in the string—if the hour
is 0
or 12
(AM or PM), it appears as 12—otherwise, the hour
appears as a value from 1 to 11. The conditional operator in line 30 determines whether AM or PM will be returned as part of the String
.
Recall from Section 6.4 that all objects in Java have a toString
method that returns a String
representation of the object. We chose to return a String
containing the time in standard-time format. Method toString
can be called implicitly whenever a Time1
object appears in the code where a String
is needed, such as the value to output with a %s
format specifier in a call to System.out.printf
.
Time1
As you learned in Chapter 3, each class you declare represents a new type in Java. Therefore, after declaring class Time1
, we can use it as a type in declarations such as
Time1 sunset; // sunset can hold a reference to a Time1 object
The Time1Test
application class (Fig. 8.2) uses class Time1
. Line 9 declares and creates a Time1
object and assigns it to local variable time
. Note that new
implicitly invokes class Time1
’s default constructor, since Time1 does not declare any constructors. Lines 12–16 output the time first in universal-time format (by invoking time
’s toUniversalString
method in line 13), then in standard-time format (by explicitly invoking time
’s toString
method in line 15) to confirm that the Time1
object was initialized properly.
Line 19 invokes method setTime
of the time
object to change the time. Then lines 20–24 output the time again in both formats to confirm that the time was set correctly.
To illustrate that method setTime
maintains the object in a consistent state, line 27 calls method setTime
with arguments of 99 for the hour
, minute
and second
. Lines 28–32 output the time again in both formats to confirm that setTime
maintained the object’s consistent state, then the program terminates. The last two lines of the application’s output show that the time is reset to midnight—the initial value of a Time1
object—after an attempt to set the time with three out-of-range values.
Time1
Class DeclarationConsider several issues of class design with respect to class Time1
. The instance variables hour
, minute
and second
are each declared private
. The actual data representation used within the class is of no concern to the class’s clients. For example, it would be perfectly reasonable for Time1
to represent the time internally as the number of seconds since midnight or the number of minutes and seconds since midnight. Clients could use the same public
methods and get the same results without being aware of this.
Software Engineering Observation 8.2
Classes simplify programming, because the client can use only the public
methods exposed by the class. Such methods are usually client oriented rather than implementation oriented. Clients are neither aware of, nor involved in, a class’s implementation. Clients generally care about what the class does but not how the class does it.
Software Engineering Observation 8.3
Interfaces change less frequently than implementations. When an implementation changes, implementation-dependent code must change accordingly. Hiding the implementation reduces the possibility that other program parts will become dependent on class implementation details.
The access modifiers public
and private
control access to a class’s variables and methods. (In Chapter 9, we’ll introduce the additional access modifier protected
.) As we stated in Section 8.2, the primary purpose of public
methods is to present to the class’s clients a view of the services the class provides (the class’s public interface). Clients of the class need not be concerned with how the class accomplishes its tasks. For this reason, the private
variables and private
methods of a class (i.e., the class’s implementation details) are not directly accessible to the class’s clients.
Figure 8.3 demonstrates that private
class members are not directly accessible outside the class. Lines 9–11 attempt to access directly the private
instance variables hour
, minute
and second
of the Time1
object time
. When this program is compiled, the compiler generates error messages stating that these private
members are not accessible. [Note: This program assumes that the Time1
class from Fig. 8.1 is used.]
Fig. 8.3. Private members of class Time1
are not accessible.
Common Programming Error 8.1
An attempt by a method that is not a member of a class to access a private
member of that class is a compilation error.
this
ReferenceEvery object can access a reference to itself with keyword this
(sometimes called the this
reference). When a non-static
method is called for a particular object, the method’s body implicitly uses keyword this
to refer to the object’s instance variables and other methods. As you’ll see in Fig. 8.4, you can also use keyword this
explicitly in a non-static
method’s body. Section 8.5 shows another interesting use of keyword this
. Section 8.11 explains why keyword this
cannot be used in a static
method.
Fig. 8.4. this
used implicitly and explicitly to refer to members of an object.
We now demonstrate implicit and explicit use of the this
reference to enable class ThisTest
’s main
method to display the private
data of a class SimpleTime
object (Fig. 8.4). Note that this example is the first in which we declare two classes in one file—class ThisTest
is declared in lines 4–11, and class SimpleTime
is declared in lines 14–47. We did this to demonstrate that when you compile a .java
file that contains more than one class, the compiler produces a separate class file with the .class
extension for every compiled class. In this case two separate files are produced—SimpleTime.class
and ThisTest.class
. When one source-code (.java
) file contains multiple class declarations, the class files for those classes are both placed in the same directory by the compiler. Also, note that only class ThisTest
is declared public
in Fig. 8.4. A source-code file can contain only one public
class—otherwise, a compilation error occurs.
Class SimpleTime
(lines 14–47) declares three private
instance variables—hour
, minute
and second
(lines 16–18). The constructor (lines 23–28) receives three int
arguments to initialize a SimpleTime
object. Note that we used parameter names for the constructor (line 23) that are identical to the class’s instance variable names (lines 16–18). We don’t recommend this practice, but we did it here to shadow (hide) the corresponding instance variables so that we could illustrate explicit use of the this
reference. If a method contains a local variable with the same name as a field, that method will refer to the local variable rather than the field. In this case, the local variable shadows the field in the method’s scope. However, the method can use the this
reference to refer to the shadowed field explicitly, as shown in lines 25–27 for SimpleTime
’s shadowed instance variables.
Method buildString
(lines 31–36) returns a String
created by a statement that uses the this
reference explicitly and implicitly. Line 34 uses the this
reference explicitly to call method toUniversalString
. Line 35 uses the this
reference implicitly to call the same method. Note that both lines perform the same task. Programmers typically do not use this
explicitly to reference other methods within the current object. Also, note that line 45 in method toUniversalString
explicitly uses the this
reference to access each instance variable. This is not necessary here, because the method does not have any local variables that shadow the instance variables of the class.
Common Programming Error 8.2
It is often a logic error when a method contains a parameter or local variable that has the same name as a field of the class. In this case, use reference this
if you wish to access the field of the class—otherwise, the method parameter or local variable will be referenced.
Error-Prevention Tip 8.1
Avoid method parameter names or local variable names that conflict with field names. This helps prevent subtle, hard-to-locate bugs.
Application class ThisTest
(lines 4–11) demonstrates class SimpleTime
. Line 8 creates an instance of class SimpleTime
and invokes its constructor. Line 9 invokes the object’s buildString
method, then displays the results.
Performance Tip 8.1
Java conserves storage by maintaining only one copy of each method per class—this method is invoked by every object of the class. Each object, on the other hand, has its own copy of the class’s instance variables (i.e., non-static
fields). Each method of the class implicitly uses this
to determine the specific object of the class to manipulate.
Time
Class Case Study: Overloaded ConstructorsAs you know, you can declare your own constructor to specify how objects of a class should be initialized. Next, we demonstrate a class with several overloaded constructors that enable objects of that class to be initialized in different ways. To overload constructors, simply provide multiple constructor declarations with different signatures. Recall from Section 6.12 that the compiler differentiates signatures by the number of parameters, the types of the parameters and the order of the parameter types in each signature.
Time2
with Overloaded ConstructorsThe default constructor for class Time1
(Fig. 8.1) initialized hour
, minute
and second
to their default 0
values (which is midnight in universal time). The default constructor does not enable the class’s clients to initialize the time with specific nonzero values. Class Time2
(Fig. 8.5) contains five overloaded constructors that provide convenient ways to initialize objects of the new class Time2
. Each constructor initializes the object to begin in a consistent state. In this program, four of the constructors invoke a fifth, which in turn calls method setTime
to ensure that the value supplied for hour
is in the range 0 to 23, and the values for minute
and second
are each in the range 0 to 59. If a value is out of range, it is set to zero by setTime
(once again ensuring that each instance variable remains in a consistent state). The compiler invokes the appropriate constructor by matching the number, types and order of the types of the arguments specified in the constructor call with the number, types and order of the types of the parameters specified in each constructor declaration. Note that class Time2
also provides set and get methods for each instance variable.
Fig. 8.5. Time2
class with overloaded constructors.
Time2
’s ConstructorsLines 12–15 declare a so-called no-argument constructor—that is, a constructor invoked without arguments. Such a constructor simply initializes the object as specified in the constructor’s body. In the body, we introduce a use of the this
reference that is allowed only as the first statement in a constructor’s body. Line 14 uses this
in method-call syntax to invoke the Time2
constructor that takes three arguments (lines 30–33). The no-argument constructor passes values of 0 for the hour
, minute
and second
to the constructor with three parameters. Using the this
reference as shown here is a popular way to reuse initialization code provided by another of the class’s constructors rather than defining similar code in the no-argument constructor’s body. We use this syntax in four of the five Time2
constructors to make the class easier to maintain and modify. If we need to change how objects of class Time2
are initialized, only the constructor that the class’s other constructors call will need to be modified. In fact, even that constructor might not need modification in this example. That constructor simply calls the setTime
method to perform the actual initialization, so it is possible that the changes the class might require would be localized to the set methods.
Common Programming Error 8.3
It is a syntax error when this
is used in a constructor’s body to call another constructor of the same class if that call is not the first statement in the constructor. It is also a syntax error when a method attempts to invoke a constructor directly via this
.
Lines 18–21 declare a Time2
constructor with a single int
parameter representing the hour
, which is passed with 0 for the minute
and second
to the constructor at lines 30–33. Lines 24–27 declare a Time2
constructor that receives two int
parameters representing the hour
and minute
, which are passed with 0 for the second
to the constructor at lines 30–33. Like the no-argument constructor, each of these constructors invokes the constructor at lines 30–33 to minimize code duplication. Lines 30–33 declare the Time2
constructor that receives three int
parameters representing the hour
, minute
and second
. This constructor calls setTime
to initialize the instance variables to consistent values.
Common Programming Error 8.4
A constructor can call methods of the class. Be aware that the instance variables might not yet be in a consistent state, because the constructor is in the process of initializing the object. Using instance variables before they have been initialized properly is a logic error.
Lines 36–40 declare a Time2
constructor that receives a Time2
reference to another Time2
object. In this case, the values from the Time2
argument are passed to the three-argument constructor at lines 30–33 to initialize the hour
, minute
and second
. Note that line 39 could have directly accessed the hour
, minute
and second
values of the constructor’s argument time
with the expressions time.hour
, time.minute
and time.second
—even though hour
, minute
and second
are declared as private
variables of class Time2
. This is due to a special relationship between objects of the same class. We’ll see in a moment why it’s preferable to use the get methods.
Software Engineering Observation 8.4
When one object of a class has a reference to another object of the same class, the first object can access all the second object’s data and methods (including those that are private
).
Time2
’s Set and Get Methods and ConstructorsNote that Time2
’s set and get methods are called throughout the body of the class. In particular, method setTime
calls methods setHour
, setMinute
and setSecond
in lines 47–49, and methods toUniversalString
and toString
call methods getHour
, getMinute
and getSecond
in line 93 and lines 100–101, respectively. In each case, these methods could have accessed the class’s private data directly without calling the set and get methods. However, consider changing the representation of the time from three int
values (requiring 12 bytes of memory) to a single int
value representing the total number of seconds that have elapsed since midnight (requiring only 4 bytes of memory). If we made such a change, only the bodies of the methods that access the private
data directly would need to change—in particular, the individual set and get methods for the hour
, minute
and second
. There would be no need to modify the bodies of methods setTime
, toUniversalString
or toString
because they do not access the data directly. Designing the class in this manner reduces the likelihood of programming errors when altering the class’s implementation.
Similarly, each Time2
constructor could be written to include a copy of the appropriate statements from methods setHour
, setMinute
and setSecond
. Doing so may be slightly more efficient, because the extra constructor call and call to setTime
are eliminated. However, duplicating statements in multiple methods or constructors makes changing the class’s internal data representation more difficult. Having the Time2
constructors call the constructor with three arguments (or even call setTime
directly) requires any changes to the implementation of setTime
to be made only once.
Software Engineering Observation 8.5
When implementing a method of a class, use the class’s set and get methods to access the class’s private
data. This simplifies code maintenance and reduces the likelihood of errors.
Time2
’s Overloaded ConstructorsClass Time2Test
(Fig. 8.6) creates six Time2
objects (lines 8–13) to invoke the overloaded Time2
constructors. Line 8 shows that the no-argument constructor (lines 12–15 of Fig. 8.5) is invoked by placing an empty set of parentheses after the class name when allocating a Time2
object with new
. Lines 9–13 of the program demonstrate passing arguments to the other Time2
constructors. Line 9 invokes the constructor at lines 18–21 of Fig. 8.5. Line 10 invokes the constructor at lines 24–27 of Fig. 8.5. Lines 11–12 invoke the constructor at lines 30–33 of Fig. 8.5. Line 13 invokes the constructor at lines 36–40 of Fig. 8.5. The application displays the String
representation of each initialized Time2
object to confirm that it was initialized properly.
Fig. 8.6. Overloaded constructors used to initialize Time2
objects.
Every class must have at least one constructor. Recall from Section 3.7, that if you do not provide any constructors in a class’s declaration, the compiler creates a default constructor that takes no arguments when it is invoked. The default constructor initializes the instance variables to the initial values specified in their declarations or to their default values (zero for primitive numeric types, false
for boolean
values and null
for references). In Section 9.4.1, you’ll learn that the default constructor performs another task in addition to initializing each instance variable to its default value.
If your class declares constructors, the compiler will not create a default constructor. In this case, to specify the default initialization for objects of your class, you must declare a no-argument constructor—as in lines 12–15 of Fig. 8.5. Like a default constructor, a no-argument constructor is invoked with empty parentheses. Note that the Time2
no-argument constructor explicitly initializes a Time2
object by passing to the three-argument constructor 0 for each parameter. Since 0 is the default value for int
instance variables, the no-argument constructor in this example could actually be declared with an empty body. In this case, each instance variable would receive its default value when the no-argument constructor was called. If we omit the no-argument constructor, clients of this class would not be able to create a Time2
object with the expression new Time2()
.
Common Programming Error 8.5
If a class has constructors, but none of the public
constructors are no-argument constructors, and a program attempts to call a no-argument constructor to initialize an object of the class, a compilation error occurs. A constructor can be called with no arguments only if the class does not have any constructors (in which case the default constructor is called) or if the class has a public
no-argument constructor.
Software Engineering Observation 8.6
Java allows other methods of the class besides its constructors to have the same name as the class and to specify return types. Such methods are not constructors and will not be called when an object of the class is instantiated. Java determines which methods are constructors by locating the methods that have the same name as the class and do not specify a return type.
As you know, a class’s private
fields can be manipulated only by methods of that class. A typical manipulation might be the adjustment of a customer’s bank balance (e.g., a private
instance variable of a class BankAccount
) by a method computeInterest
. 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.
As a naming example, a method that sets instance variable interestRate
would typically be named setInterestRate
and a method that gets the interestRate
would typically be called getInterestRate
. Set methods are also commonly called mutator methods, because they typically change a value. Get methods are also commonly called accessor methods or query methods.
public
DataIt would seem that providing set and get capabilities is essentially the same as making the instance variables public
. This is a subtlety of Java that makes the language so desirable for software engineering. A public
instance variable can be read or written by any method that has a reference to an object that contains the instance variable. If an instance variable is declared private
, a public
get method certainly allows other methods to access the variable, but the get method can control how the client can access the variable. For example, a get method might control the format of the data it returns and thus shield the client code from the actual data representation. A public
set method can—and should—carefully scrutinize attempts to modify the variable’s value to ensure that the new value is appropriate for that data item. For example, an attempt to set the day of the month to 37 would be rejected, an attempt to set a person’s weight to a negative value would be rejected, and so on. Thus, although set and get methods provide access to private
data, the access is restricted by the programmer’s implementation of the methods. This helps promote good software engineering.
The benefits of data integrity are not automatic simply because instance variables are declared private
—the programmer must provide validity checking. Java enables programmers to design better programs in a convenient manner. A class’s set methods can return values indicating that attempts were made to assign invalid data to objects of the class. A client of the class can test the return value of a set method to determine whether the client’s attempt to modify the object was successful and to take appropriate action.
Software Engineering Observation 8.7
When necessary, provide public
methods to change and retrieve the values of private
instance variables. This architecture helps hide the implementation of a class from its clients, which improves program modifiability.
Software Engineering Observation 8.8
Class designers need not provide set or get methods for each private
field. These capabilities should be provided only when it makes sense.
Another common use for accessor methods is to test whether a condition is true or false—such methods are often called predicate methods. An example would be an isEmpty
method for a container class—a class capable of holding many objects, such as a linked list, a stack or a queue. (These data structures are discussed in Chapter 16.) A program might test isEmpty
before attempting to read another item from a container object. A program might test isFull
before attempting to insert another item into a container object.
If only one method performs a particular task, such as setting the hour in a Time2
object, it is easier to debug and maintain the class. If the hour
is not being set properly, the code that actually modifies instance variable hour
is localized to one method’s body—setHour
. Thus, your debugging efforts can be focused on method setHour
.
A class can have references to objects of other classes as members. Such a capability is called composition and is sometimes referred to as a has-a relationship. For example, an object of class AlarmClock
needs to know the current time and the time when it is supposed to sound its alarm, so it is reasonable to include two references to Time
objects as members of the AlarmClock
object.
Software Engineering Observation 8.9
One form of software reuse is composition, in which a class has as members references to objects of other classes.
Our example of composition contains three classes—Date
(Fig. 8.7), Employee
(Fig. 8.8) and EmployeeTest
(Fig. 8.9). Class Date
(Fig. 8.7) declares instance variables month, day and year (lines 6–8) to represent a date. The constructor receives three int
parameters. Line 14 invokes utility method checkMonth
(lines 23–33) to validate the month—an out-of-range value is set to 1
to maintain a consistent state. Line 15 assumes that the value for year
is correct and does not validate it. Line 16 invokes utility method checkDay
(lines 36–52) to validate the value for day
based on the current month
and year
. Lines 42–43 determine whether the day is correct based on the number of days in the particular month
. If the day is not correct, lines 46–47 determine whether the month
is February, the day is 29
and the year
is a leap year. If lines 42–48 do not return a correct value for day
, line 51 returns 1
to maintain the Date
in a consistent state. Note that lines 18–19 in the constructor output the this
reference as a String
. Since this
is a reference to the current Date
object, the object’s toString
method (lines 55–58) is called implicitly to obtain the object’s String
representation.
Fig. 8.7. Date
class declaration.
Fig. 8.8. Employee
class with references to other objects.
Fig. 8.9. Composition demonstration.
Class Employee
(Fig. 8.8) has instance variables firstName
, lastName
, birthDate
and hireDate
. Members birthDate
and hireDate
(lines 8–9) are references to Date
objects. This demonstrates that a class can have as instance variables references to objects of other classes. The Employee
constructor (lines 12–19) takes four parameters—first
, last
, dateOfBirth
and dateOfHire
. The objects referenced by the parameters dateOfBirth
and dateOfHire
are assigned to the Employee
object’s birthDate
and hireDate
instance variables, respectively. Note that when class Employee
’s toString
method is called, it returns a String
containing the String
representations of the two Date
objects. Each of these String
s is obtained with an implicit call to the Date
class’s toString
method.
Class EmployeeTest
(Fig. 8.9) creates two Date
objects (lines 8–9) to represent an Employee
’s birthday and hire date, respectively. Line 10 creates an Employee
and initializes its instance variables by passing to the constructor two String
s (representing the Employee
’s first and last names) and two Date
objects (representing the birthday and hire date). Line 12 implicitly invokes the Employee
’s toString
method to display the values of its instance variables and demonstrate that the object was initialized properly.
In Fig. 6.8 (Craps.java
), we introduced the basic enum
type which defines a set of constants that are represented as unique identifiers. In that program, the enum
constants represented the game’s status. In this section, we discuss the relationship between enum
types and classes. Like classes, all enum
types are reference types. An enum
type is declared with an enum
declaration, which is a comma-separated list of enum
constants—the declaration may optionally include other components of traditional classes, such as constructors, fields and methods. Each enum
declaration declares an enum
class with the following restrictions:
1. enum
types are implicitly final
, because they declare constants that should not be modified.
2. enum
constants are implicitly static
.
3. Any attempt to create an object of an enum type with operator new
results in a compilation error.
The enum
constants can be used anywhere constants can be used, such as in the case
labels of switch
statements and to control enhanced for
statements.
Figure 8.10 illustrates how to declare instance variables, a constructor and methods in an enum
type. The enum
declaration (lines 5–37) contains two parts—the enum
constants and the other members of the enum
type. The first part (lines 8–13) declares six enum
constants. Each enum
constant is optionally followed by arguments which are passed to the enum
constructor (lines 20–24). Like the constructors you have seen in classes, an enum
constructor can specify any number of parameters and can be overloaded. In this example, the enum
constructor has two String
parameters, hence each enum
constant is followed by parentheses containing two String
arguments. The second part (lines 16–36) declares the other members of the enum
type—two instance variables (lines 16–17), a constructor (lines 20–24) and two methods (lines 27–30 and 33–36).
Fig. 8.10. Declaring enum
type with instance fields, constructor and methods.
Lines 16–17 declare the instance variables title
and copyrightYear
. Each enum
constant in Book
is actually an object of type Book
that has its own copy of instance variables title
and copyrightYear
. The constructor (lines 20–24) takes two String
parameters, one that specifies the book title and one that specifies the copyright year of the book. Lines 22–23 assign these parameters to the instance variables. Lines 27–36 declare two methods, which return the book title and copyright year, respectively.
Figure 8.11 tests the enum
type declared in Fig. 8.10 and illustrates how to iterate through a range of enum
constants. For every enum
, the compiler generates the static
method values
(called in line 12) that returns an array of the enum
’s constants in the order they were declared. Recall from Section 7.6 that the enhanced for
statement can be used to iterate through an array. Lines 12–14 use the enhanced for
statement to display all the constants declared in the enum Book
. Line 14 invokes the enum Book
’s getTitle
and getCopyrightYear
methods to get the title and copyright year associated with the constant. Note that when an enum
constant is converted to a String
(e.g., book
in line 13), the constant’s identifier is used as the String
representation (e.g., JHTP6
for the first enum
constant).
Fig. 8.11. Testing an enum
type.
Lines 19–21 use the static
method range
of class EnumSet
(declared in package java.util
) to display a range of the enum Book
’s constants. Method range
takes two parameters—the first and the last enum
constants in the range—and returns an EnumSet
that contains all the constants between these two constants, inclusive. For example, the expression EnumSet.range( Book.JHTP6
, Book.CPPHTP4 )
returns an EnumSet
containing Book.JHTP6
, Book.CHTP4
, Book.IW3HTP3
and Book.CPPHTP4
. The enhanced for
statement can be used with an EnumSet
just as it can with an array, so lines 19–21 use the enhanced for
statement to display the title and copyright year of every book in the EnumSet
. Class EnumSet
provides several other static
methods for creating sets of enum
constants from the same enum
type. For more details of class EnumSet
, visit java.sun.com/javase/6/docs/api/java/util/EnumSet.html.
Common Programming Error 8.6
In an enum
declaration, it is a syntax error to declare enum
constants after the enum
type’s constructors, fields and methods in the enum
declaration.
finalize
Every class in Java has the methods of class Object
(package java.lang
), one of which is the finalize
method. This method is rarely used. In fact, we searched over 6500 source-code files for the Java API classes and found fewer than 50 declarations of the finalize
method. Nevertheless, because finalize
is part of every class, we discuss it here to help you understand its intended purpose in case you encounter it in your studies or in industry. The complete details of the finalize
method are beyond the scope of this book, and most programmers should not use it—you’ll soon see why. You’ll learn more about class Object
in Chapter 9, Object-Oriented Programming: Inheritance.
Every object you create uses various system resources, such as memory. To avoid “resource leaks,” we need a disciplined way to give resources back to the system when they are no longer needed. The Java Virtual Machine (JVM) performs automatic garbage collection to reclaim the memory occupied by objects that are no longer in use. When there are no more references to an object, the object is marked for garbage collection by the JVM. The memory for such an object can be reclaimed when the JVM executes its garbage collector, which is responsible for retrieving the memory of objects that are no longer used so it can be used for other objects. Therefore, memory leaks that are common in other languages like C and C++ (because memory is not automatically reclaimed in those languages) are less likely in Java (but some can still happen in subtle ways). Other types of resource leaks can occur. For example, an application could open a file on disk to modify the file’s contents. If the application does not close the file, no other application can use it until the application that opened the file completes.
The finalize
method is called by the garbage collector to perform termination housekeeping on an object just before the garbage collector reclaims the object’s memory. Method finalize
does not take parameters and has return type void
. A problem with method finalize
is that the garbage collector is not guaranteed to execute at a specified time. In fact, the garbage collector may never execute before a program terminates. Thus, it is unclear if, or when, method finalize
will be called. For this reason, most programmers should avoid method finalize
. In Section 8.11, we demonstrate a situation in which method finalize
is called by the garbage collector.
Software Engineering Observation 8.10
A class that uses system resources, such as files on disk, should provide a method to eventually release the resources. Many Java API classes provide close
or dispose
methods for this purpose. For example, class Scanner
(java.sun.com/javase/6/docs/api/java/util/Scanner.html) has a close
method.
static
Class MembersEvery object has its own copy of all the instance variables of the class. In certain cases, only one copy of a particular variable should be shared by all objects of a class. A static
field—called a class variable—is used in such cases. A static
variable represents classwide information—all objects of the class share the same piece of data. The declaration of a static
variable begins with the keyword static
.
Let’s motivate static
data with an example. Suppose that we have a video game with Martian
s and other space creatures. Each Martian
tends to be brave and willing to attack other space creatures when the Martian
is aware that at least four other Martian
s are present. If fewer than five Martian
s are present, each of them becomes cowardly. Thus each Martian
needs to know the martianCount
. We could endow class Martian
with martianCount
as an instance variable. If we do this, then every Martian
will have a separate copy of the instance variable, and every time we create a new Martian
, we’ll have to update the instance variable martianCount
in every Martian
. This wastes space with the redundant copies, wastes time in updating the separate copies and is error prone. Instead, we declare martianCount
to be static
, making martianCount
classwide data. Every Martian
can see the martianCount
as if it were an instance variable of class Martian
, but only one copy of the static martianCount
is maintained. This saves space. We save time by having the Martian
constructor increment the static martianCount
—there is only one copy, so we do not have to increment separate copies of martianCount
for each Martian
object.
Software Engineering Observation 8.11
Use a static
variable when all objects of a class must use the same copy of the variable.
Static variables have class scope. A class’s public static
members can be accessed through a reference to any object of the class, or by qualifying the member name with the class name and a dot (.
), as in Math.random()
. A class’s private static
class members can be accessed only through methods of the class. Actually, static
class members exist even when no objects of the class exist—they are available as soon as the class is loaded into memory at execution time. To access a public static
member when no objects of the class exist (and even when they do), prefix the class name and a dot (.
) to the static
member, as in Math.PI
. To access a private static
member when no objects of the class exist, a public static
method must be provided and the method must be called by qualifying its name with the class name and a dot.
Software Engineering Observation 8.12
Static class variables and methods exist, and can be used, even if no objects of that class have been instantiated.
Our next program declares two classes—Employee
(Fig. 8.12) and EmployeeTest
(Fig. 8.13). Class Employee
declares private static
variable count
(Fig. 8.12, line 9), and public static
method getCount
(lines 46–49). The static
variable count
is initialized to zero in line 9. If a static
variable is not initialized, the compiler assigns a default value to the variable—in this case 0
, the default value for type int
. Variable count
maintains a count of the number of objects of class Employee
that currently reside in memory. This includes objects that have already been marked for garbage collection by the JVM but have not yet been reclaimed by the garbage collector.
Fig. 8.12. static
variable used to maintain a count of the number of Employee
objects in memory.
Fig. 8.13. static
member demonstration.
When Employee
objects exist, member count
can be used in any method of an Employee
object—this example increments count
in the constructor (line 18) and decrements it in the finalize
method (line 28). When no objects of class Employee
exist, member count
can still be referenced, but only through a call to public static
method getCount
(lines 46–49), as in Employee.getCount()
, which returns the number of Employee
objects currently in memory. When objects exist, method getCount
can also be called through any reference to an Employee
object, as in the call e1.getCount()
.
Good Programming Practice 8.1
Invoke every static
method by using the class name and a dot (.
) to emphasize that the method being called is a static
method.
Note that the Employee
class has a finalize
method (lines 26–31). This method is included only to show when the garbage collector executes in this program. Method finalize
is normally declared protected
, so it is not part of the public
services of a class. We’ll discuss the protected
member access modifier in detail in Chapter 9.
EmployeeTest
method main
(Fig. 8.13) instantiates two Employee
objects (lines 13–14). When each Employee
object’s constructor is invoked, lines 15–16 of Fig. 8.12 assign the Employee
’s first name and last name to instance variables firstName
and lastName
. Note that these two statements do not make copies of the original String
arguments. Actually, String
objects in Java are immutable—they cannot be modified after they are created. Therefore, it is safe to have many references to one String
object. This is not normally the case for objects of most other classes in Java. If String
objects are immutable, you might wonder why are we able to use operators +
and +=
to concatenate String
objects. String concatenation operations actually result in a new String
s object containing the concatenated values. The original String
objects are not modified.
When main
has finished using the two Employee
objects, the references e1
and e2
are set to null
at lines 31–32. At this point, references e1
and e2
no longer refer to the objects that were instantiated in lines 13–14. This “marks the objects for garbage collection” because there are no more references to the objects in the program.
Eventually, the garbage collector might reclaim the memory for these objects (or the operating system surely will reclaim the memory when the program terminates). The JVM does not guarantee when the garbage collector will execute (or even whether it will execute), so this program explicitly calls the garbage collector in line 34 (Fig. 8.13) using static
method gc
of class System
(package java.lang
) to indicate that the garbage collector should make a best-effort attempt to reclaim objects that are eligible for garbage collection. This is just a best effort—it is possible that no objects or only a subset of the eligible objects will be collected. In Fig. 8.13’s sample output, the garbage collector did execute before lines 39–40 displayed current Employee
count. The last output line indicates that the number of Employee
objects in memory is 0 after the call to System.gc()
. The third- and second-to-last lines of the output show that the Employee
object for Bob Blue
was finalized before the Employee
object for Susan Baker
. The output on your system may differ, because the garbage collector is not guaranteed to execute when System.gc()
is called, nor is it guaranteed to collect objects in a specific order.
[Note: A method declared static
cannot access non-static
class members, because a static
method can be called even when no objects of the class have been instantiated. For the same reason, the this
reference cannot be used in a static
method—it must refer to a specific object of the class, and when a static
method is called, there might not be any objects of its class in memory. The this
reference is required to allow a method of a class to access other non-static
members of the same class.]
Common Programming Error 8.7
A compilation error occurs if a static
method calls an instance (non-static
) method in the same class by using only the method name. Similarly, a compilation error occurs if a static
method attempts to access an instance variable in the same class by using only the variable name.
Common Programming Error 8.8
Referring to this
in a static
method is a syntax error.
static
ImportIn Section 6.3, you learned about the static
fields and methods of class Math
. We invoked class Math
’s static
fields and methods by preceding each with the class name Math
and a dot (.
). A static
import declaration enables you to refer to imported static
members as if they were declared in the class that uses them—the class name and a dot (.
) are not required to use an imported static
member.
A static
import declaration has two forms—one that imports a particular static
member (which is known as single static
import) and one that imports all static
members of a class (which is known as static
import on demand). The following syntax imports a particular static
member:
import static packageName.ClassName.staticMemberName;
where packageName is the package of the class (e.g., java.lang
), ClassName is the name of the class (e.g., Math
) and staticMemberName is the name of the static
field or method (e.g., PI
or abs
). The following syntax imports all static
members of a class:
import static packageName.ClassName.*;
where packageName is the package of the class (e.g., java.lang
) and ClassName is the name of the class (e.g., Math
). The asterisk (*
) indicates that all static
members of the specified class should be available for use in the class(es) declared in the file. Note that static
import declarations import only static
class members. Regular import
statements should be used to specify the classes used in a program.
Figure 8.14 demonstrates a static
import. Line 3 is a static
import declaration, which imports all static
fields and methods of class Math
from package java.lang
. Lines 9–12 access the Math
class’s static
field E
(line 11) and the static
methods sqrt
(line 9), ceil
(line 10), log
(line 11) and cos
(line 12) without preceding the field name or method names with class name Math
and a dot.
Fig. 8.14. Static import Math
methods.
Common Programming Error 8.9
A compilation error occurs if a program attempts to import static
methods that have the same signature or static
fields that have the same name from two or more classes.
final
Instance VariablesThe principle of least privilege is fundamental to good software engineering. In the context of an application, the principle states that code should be granted only the amount of privilege and access that it needs to accomplish its designated task, but no more. Let us see how this principle applies to instance variables.
Some instance variables need to be modifiable and some do not. You can use the keyword final
to specify that a variable is not modifiable (i.e., it is a constant) and that any attempt to modify it is an error. For example,
private final int INCREMENT;
declares a final
(constant) instance variable INCREMENT
of type int
. Although constants can be initialized when they are declared, this is not required. Constants can be initialized by each of the class’s constructors.
Software Engineering Observation 8.13
Declaring an instance variable as final
helps enforce the principle of least privilege. If an instance variable should not be modified, declare it to be final
to prevent modification.
Our next example contains two classes—class Increment
(Fig. 8.15) and class IncrementTest
(Fig. 8.16). Class Increment
contains a final
instance variable of type int
named INCREMENT
(Fig. 8.15, line 7). Note that the final
variable is not initialized in its declaration, so it must be initialized by the class’s constructor (lines 9–13). If the class provided multiple constructors, every constructor would be required to initialize the final
variable. The constructor receives int
parameter incrementValue
and assigns its value to INCREMENT
(line 12). A final
variable cannot be modified by assignment after it is initialized. Application class IncrementTest
creates an object of class Increment
(Fig. 8.16, line 8) and provides as the argument to the constructor the value 5
to be assigned to the constant INCREMENT
.
Fig. 8.15. final
instance variable in a class.
Fig. 8.16. final
variable initialized with a constructor argument.
Common Programming Error 8.10
Attempting to modify a final
instance variable after it is initialized is a compilation error.
Attempts to modify a final
instance variable are caught at compilation time rather than causing execution-time errors. It is always preferable to get bugs out at compilation time, if possible, rather than allow them to slip through to execution time (where studies have found that repair is often many times more expensive).
Software Engineering Observation 8.14
A final
field should also be declared static
if it is initialized in its declaration. Once a final
field is initialized in its declaration, its value can never change. Therefore, it is not necessary to have a separate copy of the field for every object of the class. Making the field static
enables all objects of the class to share the final
field.
If a final
variable is not initialized, a compilation error occurs. To demonstrate this, we placed line 12 of Fig. 8.15 in a comment and recompiled the class. Figure 8.17 shows the error message produced by the compiler.
Fig. 8.17. final
variable INCREMENT
must be initialized.
Not initializing a final
instance variable in its declaration or in every constructor of the class yields a compilation error indicating that the variable might not have been initialized. The same error occurs if the class initializes the variable in some, but not all, of the class’s constructors.
Java programmers concentrate on crafting new classes and reusing existing classes. Many class libraries exist, and others are being developed worldwide. Software is then constructed from existing, well-defined, carefully tested, well-documented, portable, widely available components. This kind of software reusability speeds the development of powerful, high-quality software. Rapid application development (RAD) is of great interest today.
There are thousands of classes in the Java API from which to choose to help you implement Java programs. Indeed, Java is not just a programming language. It is a framework in which Java developers can work to achieve true reusability and rapid application development. Java programmers can focus on the task at hand when developing their programs and leave the lower-level details to the classes of the Java API. For example, to write a program that draws graphics, a Java programmer does not require knowledge of graphics on every computer platform where the program will execute. Instead, the programmer can concentrate on learning Java’s graphics capabilities (which are quite substantial and growing) and write a Java program that draws the graphics, using Java’s API classes, such as Graphics
. When the program executes on a given computer, it is the job of the JVM to translate Java commands into commands that the local computer can understand.
The Java API classes enable Java programmers to bring new applications to market faster by using preexisting, tested components. Not only does this reduce development time, it also improves the programmer’s ability to debug and maintain applications. To take advantage of Java’s many capabilities, it is essential that you familiarize yourself with the variety of packages and classes in the Java API. There are many web-based resources at java.sun.com to help you with this task. The primary resource for learning about the Java API is the Java API documentation, which can be found at
java.sun.com/javase/6/docs/api/
We overview how to use the documentation in Appendix G. You can download the API documentation from
java.sun.com/javase/downloads/ea.jsp
In addition, java.sun.com provides many other resources, including tutorials, articles and sites specific to individual Java topics.
Good Programming Practice 8.2
Avoid reinventing the wheel. Study the capabilities of the Java API. If the API contains a class that meets your program’s requirements, use that class rather than create your own.
To realize the full potential of software reusability, we need to improve cataloging schemes, licensing schemes, protection mechanisms which ensure that master copies of classes are not corrupted, description schemes that system designers use to determine whether existing objects meet their needs, browsing mechanisms that determine what classes are available and how closely they meet software developer requirements, and the like. Many interesting research and development problems have been solved and many more need to be solved. These problems will likely be solved because the potential value of increased software reuse is enormous.
Classes normally hide the details of their implementation from their clients. This is called information hiding. As an example, let us consider the stack data structure introduced in Section 6.6. Recall that a stack is a last-in, first-out (LIFO) data structure—the last item pushed (inserted) on the stack is the first item popped (removed) from the stack.
Stacks can be implemented with arrays and with other data structures, such as linked lists. A client of a stack class need not be concerned with the stack’s implementation. The client knows only that when data items are placed in the stack, they will be recalled in last-in, first-out order. The client cares about what functionality a stack offers, not about how that functionality is implemented. This concept is referred to as data abstraction. Although programmers might know the details of a class’s implementation, they should not write code that depends on these details. This enables a particular class (such as one that implements a stack and its operations, push and pop) to be replaced with another version without affecting the rest of the system. As long as the public
services of the class do not change (i.e., every original method still has the same name, return type and parameter list in the new class declaration), the rest of the system is not affected.
Most programming languages emphasize actions. In these languages, data exists to support the actions that programs must take. Data is “less interesting” than actions. Data is “crude.” Only a few primitive types exist, and it is difficult for programmers to create their own types. Java and the object-oriented style of programming elevate the importance of data. The primary activities of object-oriented programming in Java are the creation of types (e.g., classes) and the expression of the interactions among objects of those types. To create languages that emphasize data, the programming-languages community needed to formalize some notions about data. The formalization we consider here is the notion of abstract data types (ADTs), which improve the program-development process.
Consider primitive type int
, which most people would associate with an integer in mathematics. Rather, an int
is an abstract representation of an integer. Unlike mathematical integers, computer int
s are fixed in size. For example, type int
in Java is limited to the range –2,147,483,648 to +2,147,483,647. If the result of a calculation falls outside this range, an error occurs, and the computer responds in some machine-dependent manner. It might, for example, “quietly” produce an incorrect result, such as a value too large to fit in an int
variable (commonly called arithmetic overflow). Mathematical integers do not have this problem. Therefore, the notion of a computer int
is only an approximation of the notion of a real-world integer. The same is true of float
and other built-in types.
We have taken the notion of int
for granted until this point, but we now consider it from a new perspective. Types like int
, float
, and char
are all examples of abstract data types. They are representations of real-world notions to some satisfactory level of precision within a computer system.
An ADT actually captures two notions: a data representation and the operations that can be performed on that data. For example, in Java, an int
contains an integer value (data) and provides addition, subtraction, multiplication, division and remainder operations—division by zero is undefined. Java programmers use classes to implement abstract data types.
Software Engineering Observation 8.15
Programmers create types through the class mechanism. New types can be designed to be convenient to use as the built-in types. This marks Java as an extensible language. Although the language is easy to extend via new types, you cannot alter the base language itself.
Another abstract data type we discuss is a queue, which is similar to a “waiting line.” Computer systems use many queues internally. A queue offers well-understood behavior to its clients: Clients place items in a queue one at a time via an enqueue operation, then get them back one at a time via a dequeue operation. A queue returns items in first-in, first-out (FIFO) order, which means that the first item inserted in a queue is the first item removed from the queue. Conceptually, a queue can become infinitely long, but real queues are finite.
The queue hides an internal data representation that keeps track of the items currently waiting in line, and it offers operations to its clients (enqueue and dequeue). The clients are not concerned about the implementation of the queue—they simply depend on the queue to operate “as advertised.” When a client enqueues an item, the queue should accept that item and place it in some kind of internal FIFO data structure. Similarly, when the client wants the next item from the front of the queue, the queue should remove the item from its internal representation and deliver it in FIFO order (i.e., the item that has been in the queue the longest should be the next one returned by the next dequeue operation).
The queue ADT guarantees the integrity of its internal data structure. Clients cannot manipulate this data structure directly—only the queue ADT has access to its internal data. Clients are able to perform only allowable operations on the data representation—the ADT rejects operations that its public interface does not provide.
Time
Class Case Study: Creating PackagesWe have seen in almost every example in the text that classes from preexisting libraries, such as the Java API, can be imported into a Java program. Each class in the Java API belongs to a package that contains a group of related classes. As applications become more complex, packages help programmers manage the complexity of application components. Packages also facilitate software reuse by enabling programs to import classes from other packages (as we have done in most examples). Another benefit of packages is that they provide a convention for unique class names, which helps prevent class-name conflicts (discussed later in this section). This section introduces how to create your own packages.
Before a class can be imported into multiple applications, it must be placed in a package to make it reusable. Figure 8.18 shows how to specify the package in which a class should be placed. Figure 8.19 shows how to import our packaged class so that it can be used in an application. The steps for creating a reusable class are:
1. Declare a public
class. If the class is not public
, it can be used only by other classes in the same package.
2. Choose a unique package name and add a package
declaration to the source-code file for the reusable class declaration. There can be only one package
declaration in each Java source-code file, and it must precede all other declarations and statements in the file. Note that comments are not statements, so comments can be placed before a package
statement in a file.
3. Compile the class so that it is placed in the appropriate package directory structure.
4. Import the reusable class into a program and use the class.
Fig. 8.18. Packaging class Time1
for reuse.
Fig. 8.19. Time1
object used in an application.
public
Class and Adding the package
StatementFor Step 1, we modify the public
class Time1
declared in Fig. 8.1. The new version is shown in Fig. 8.18. No modifications have been made to the implementation of the class, so we’ll not discuss its implementation details again here.
For Step 2, we add a package
declaration (line 3) that declares a package
named com.deitel.javafp.ch08
. Placing a package
declaration at the beginning of a Java source file indicates that the class declared in the file is part of the specified package. Only package
declarations, import
declarations and comments can appear outside the braces of a class declaration. A Java source-code file must have the following order:
1. a package
declaration (if any),
2. import
declarations (if any), then
3. class declarations.
Only one of the class declarations in a particular file can be public
. Other classes in the file are placed in the package and can be used only by the other classes in the package. Non-public
classes are in a package to support the reusable classes in the package.
In an effort to provide unique names for every package, Sun Microsystems specifies a convention for package naming that all Java programmers should follow. Every package name should start with your Internet domain name in reverse order. For example, our domain name is deitel.com
, so our package names begin with com.deitel
. For the domain name yourcollege.edu
, the package name should begin with edu.
yourcollege. After the domain name is reversed, you can choose any other names you want for your package. If you are part of a company with many divisions or a university with many schools, you may want to use the name of your division or school as the next name in the package. We chose to use javafp
as the next name in our package name to indicate that this class is from Java for Programmers. The last name in our package name specifies that this package is for Chapter 8 (ch08
).
Step 3 is to compile the class so that it is stored in the appropriate package. When a Java file containing a package
declaration is compiled, the resulting class file is placed in the directory specified by the package
declaration. The package
declaration in Fig. 8.18 indicates that class Time1
should be placed in the directory
The directory names in the package
declaration specify the exact location of the classes in the package.
When compiling a class in a package, the javac
command-line option -d
causes the javac
compiler to create appropriate directories based on the class’s package
declaration. The option also specifies where the directories should be stored. For example, in a command window, we used the compilation command
javac -d . Time1.java
to specify that the first directory in our package name should be placed in the current directory. The period (.
) after -d
in the preceding command represents the current directory on the Windows, UNIX and Linux operating systems (and several others as well). After executing the compilation command, the current directory contains a directory called com
, com
contains a directory called deitel
, deitel
contains a directory called javafp
and javafp
contains a directory called ch08
. In the ch08
directory, you can find the file Time1.class
. [Note: If you do not use the -d
option, then you must copy or move the class file to the appropriate package directory after compiling it.]
The package
name is part of the fully qualified class name, so the name of class Time1
is actually com.deitel.javafp.ch08.Time1
. You can use this fully qualified name in your programs, or you can import
the class and use its simple name (the class name by itself—Time1
) in the program. If another package also contains a Time1
class, the fully qualified class names can be used to distinguish between the classes in the program and prevent a name conflict (also called a name collision).
Once the class is compiled and stored in its package, the class can be imported into programs (Step 4). In the Time1PackageTest
application of Fig. 8.19, line 3 specifies that class Time1
should be imported for use in class Time1PackageTest
. Class Time1PackageTest
is in the default package because the class’s .java
file does not contain a package
declaration. Since the two classes are in different packages, the import
at line 3 is required so that class Time1PackageTest
can use class Time1
.
Line 3 is known as a single-type-import declaration—that is, the import
declaration specifies one class to import. When your program uses multiple classes from the same package, you can import those classes with a single import
declaration. For example, the import declaration
import java.util.*; // import classes from package java.util
uses an asterisk (*
) at the end of the import
declaration to inform the compiler that all classes from the java.util
package are available for use in the program. This is known as a type-import-on-demand declaration. Only the classes from package java.util
that are used in the program are loaded by the JVM. The preceding import
allows you to use the simple name of any class from the java.util
package in the program. Throughout this book, we use single-type-import declarations for clarity.
Common Programming Error 8.12
Using the import
declaration import java.*;
causes a compilation error. You must specify the exact name of the package from which you want to import classes.
When compiling Time1PackageTest
, javac
must locate the .class
file for Time1
to ensure that class Time1PackageTest
uses class Time1
correctly. The compiler uses a special object called a class loader to locate the classes it needs. The class loader begins by searching the standard Java classes that are bundled with the JDK. Then it searches for optional packages. Java provides an extension mechanism that enables new (optional) packages to be added to Java for development and execution purposes. [Note: The extension mechanism is beyond the scope of this book. For more information, visit java.sun.com/javase/6/docs/technotes/guides/extensions/.] If the class is not found in the standard Java classes or in the extension classes, the class loader searches the classpath, which contains a list of locations in which classes are stored. The classpath consists of a list of directories or archive files, each separated by a directory separator—a semicolon (;
) on Windows or a colon (:
) on UNIX/Linux/Mac OS X. Archive files are individual files that contain directories of other files, typically in a compressed format. For example, the standard classes used by your programs are contained in the archive file rt.jar
, which is installed with the JDK. Archive files normally end with the .jar
or .zip
file-name extensions. The directories and archive files specified in the classpath contain the classes you wish to make available to the Java compiler and the JVM.
By default, the classpath consists only of the current directory. However, the classpath can be modified by
1. providing the -classpath
option to the javac
compiler or
2. setting the CLASSPATH
environment variable (a special variable that you define and the operating system maintains so that applications can search for classes in the specified locations).
For more information on the classpath, visit java.sun.com/javase/6/docs/technotes/tools/index.html. The section entitled “General Information” contains information on setting the classpath for UNIX/Linux and Windows.
Common Programming Error 8.13
Specifying an explicit classpath eliminates the current directory from the classpath. This prevents classes in the current directory (including packages in the current directory) from loading properly. If classes must be loaded from the current directory, include a dot (.
) in the classpath to specify the current directory.
Software Engineering Observation 8.16
In general, it is a better practice to use the -classpath
option of the compiler, rather than the CLASSPATH
environment variable, to specify the classpath for a program. This enables each application to have its own classpath.
Error-Prevention Tip 8.3
Specifying the classpath with the CLASSPATH
environment variable can cause subtle and difficult-to-locate errors in programs that use different versions of the same package.
For the example of Fig. 8.18 and Fig. 8.19, we did not specify an explicit classpath. Thus, to locate the classes in the com.deitel.javafp.ch08
package from this example, the class loader looks in the current directory for the first name in the package—com
. Next, the class loader navigates the directory structure. Directory com
contains the subdirectory deitel
. Directory deitel
contains the subdirectory javafp
. Finally, directory javafp
contains subdirectory ch08
. In the ch08
directory is the file Time1.class
, which is loaded by the class loader to ensure that the class is used properly in our program.
When you execute an application, the JVM must be able to locate the classes used in that application. Like the compiler, the java
command uses a class loader that searches the standard classes and extension classes first, then searches the classpath (the current directory by default). The classpath for the JVM can be specified explicitly by using either of the techniques discussed for the compiler. As with the compiler, it is better to specify an individual program’s classpath via command-line options to the JVM. You can specify the classpath in the java
command via the -classpath
or -cp
command-line options, followed by a list of directories or archive files separated by semicolons (;
) on Microsoft Windows or by colons (:
) on UNIX/Linux/Mac OS X. Again, if classes must be loaded from the current directory, be sure to include a dot (.
) in the classpath to specify the current directory.
If no access modifier (public
, protected
or private
—protected
is discussed in Chapter 9) is specified for a method or variable when it is declared in a class, the method or variable is considered to have package access. In a program that consists of one class declaration, this has no specific effect. However, if a program uses multiple classes from the same package (i.e., a group of related classes), these classes can access each other’s package-access members directly through references to objects of the appropriate classes.
The application in Fig. 8.20 demonstrates package access. The application contains two classes in one source-code file—the PackageDataTest
application class (lines 5–21) and the PackageData
class (lines 24–41). When you compile this program, the compiler produces two separate .class files—PackageDataTest.class
and PackageData.class
. The compiler places the two .class
files in the same directory, so the classes are considered to be part of the same package. Since they are part of the same package, class PackageDataTest
is allowed to modify the package-access data of PackageData
objects.
Fig. 8.20. Package-access members of a class are accessible by other classes in the same package.
In the PackageData
class declaration, lines 26–27 declare the instance variables number
and string
with no access modifiers—therefore, these are package-access instance variables. The PackageDataTest
application’s main
method creates an instance of the PackageData
class (line 9) to demonstrate the ability to modify the PackageData
instance variables directly (as shown in lines 15–16). The results of the modification can be seen in the output window.
In the Software Engineering Case Study sections in Chapters 1–7, we introduced the fundamentals of object orientation and developed an object-oriented design for our ATM system. Earlier in this chapter, we discussed many of the details of programming with classes. We now begin implementing our object-oriented design in Java. At the end of this section, we show how to convert class diagrams to Java code. In the final Software Engineering Case Study section (Section 10.8), we modify the code to incorporate the object-oriented concept of inheritance. We present the full Java code implementation in Appendix H.
We now apply access modifiers to the members of our classes. In Chapter 3, we introduced access modifiers public
and private
. Access modifiers determine the visibility or accessibility of an object’s attributes and methods to other objects. Before we can begin implementing our design, we must consider which attributes and methods of our classes should be public
and which should be private
.
In Chapter 3, we observed that attributes normally should be private
and that methods invoked by clients of a given class should be public
. Methods that are called only by other methods of the class as “utility methods,” however, normally should be private
. The UML employs visibility markers for modeling the visibility of attributes and operations. Public visibility is indicated by placing a plus sign (+) before an operation or an attribute, whereas a minus sign (–) indicates private visibility. Figure 8.21 shows our updated class diagram with visibility markers included. [Note: We do not include any operation parameters in Fig. 8.21—this is perfectly normal. Adding visibility markers does not affect the parameters already modeled in the class diagrams of Figs. 6.27–6.30.]
Fig. 8.21. Class diagram with visibility markers.
Before we begin implementing our design in Java, we introduce an additional UML notation. The class diagram in Fig. 8.22 further refines the relationships among classes in the ATM system by adding navigability arrows to the association lines. Navigability arrows (represented as arrows with stick arrowheads in the class diagram) indicate in which direction an association can be traversed. When implementing a system designed using the UML, programmers use navigability arrows to help determine which objects need references to other objects. For example, the navigability arrow pointing from class ATM
to class BankDatabase
indicates that we can navigate from the former to the latter, thereby enabling the ATM
to invoke the BankDatabase
’s operations. However, since Fig. 8.22 does not contain a navigability arrow pointing from class BankDatabase
to class ATM
, the BankDatabase
cannot access the ATM
’s operations. Note that associations in a class diagram that have navigability arrows at both ends or do not have navigability arrows at all indicate bidirectional navigability—navigation can proceed in either direction across the association.
Fig. 8.22. Class diagram with navigability arrows.
Like the class diagram of Fig. 3.21, the class diagram of Fig. 8.22 omits classes BalanceInquiry
and Deposit
to keep the diagram simple. The navigability of the associations in which these classes participate closely parallels the navigability of class Withdrawal
. Recall from Section 3.9 that BalanceInquiry
has an association with class Screen
. We can navigate from class BalanceInquiry
to class Screen
along this association, but we cannot navigate from class Screen
to class BalanceInquiry
. Thus, if we were to model class BalanceInquiry
in Fig. 8.22, we would place a navigability arrow at class Screen
’s end of this association. Also recall that class Deposit
associates with classes Screen
, Keypad
and DepositSlot
. We can navigate from class Deposit
to each of these classes, but not vice versa. We therefore would place navigability arrows at the Screen
, Keypad
and DepositSlot
ends of these associations. [Note: We model these additional classes and associations in our final class diagram in Section 10.8, after we have simplified the structure of our system by incorporating the object-oriented concept of inheritance.]
We are now ready to begin implementing the ATM system. We first convert the classes in the diagrams of Fig. 8.21 and Fig. 8.22 into Java code. The code will represent the “skeleton” of the system. In Chapter 10, we modify the code to incorporate the object-oriented concept of inheritance. In Appendix H, we present the complete working Java code for our model.
As an example, we develop the code from our design of class Withdrawal
in Fig. 8.21. We use this figure to determine the attributes and operations of the class. We use the UML model in Fig. 8.22 to determine the associations among classes. We follow the following four guidelines for each class:
1. Use the name located in the first compartment to declare the class as a public
class with an empty no-argument constructor. We include this constructor simply as a placeholder to remind us that most classes will indeed need constructors. In Appendix H, when we complete a working version of this class, we add any necessary arguments and code the body of the constructor as needed. For example, class Withdrawal
yields the code in Fig. 8.23. [Note: If we find that the class’s instance variables require only default initialization, then we remove the empty no-argument constructor because it is unnecessary.]
Fig. 8.23. Java code for class Withdrawal
based on Fig. 8.21 and Fig. 8.22.
2. Use the attributes located in the second compartment to declare the instance variables. For example, the private
attributes accountNumber
and amount
of class Withdrawal
yield the code in Fig. 8.24. [Note: The constructor of the complete working version of this class will assign values to these attributes.]
Fig. 8.24. Java code for class Withdrawal
based on Fig. 8.21 and Fig. 8.22.
3. Use the associations described in the class diagram to declare the references to other objects. For example, according to Fig. 8.22, Withdrawal
can access one object of class Screen
, one object of class Keypad
, one object of class CashDispenser
and one object of class BankDatabase
. This yields the code in Fig. 8.25.
Fig. 8.25. Java code for class Withdrawal
based on Fig. 8.21 and Fig. 8.22.
[Note: The constructor of the complete working version of this class will initialize these instance variables with references to actual objects.]
4. Use the operations located in the third compartment of Fig. 8.21 to declare the shells of the methods. If we have not yet specified a return type for an operation, we declare the method with return type void
. Refer to the class diagrams of Figs. 6.27–6.30 to declare any necessary parameters. For example, adding the public
operation execute
in class Withdrawal
, which has an empty parameter list, yields the code in Fig. 8.26. [Note: We code the bodies of methods when we implement the complete system in Appendix H.]
Fig. 8.26. Java code for class Withdrawal
based on Fig. 8.21 and Fig. 8.22.
This concludes our discussion of the basics of generating classes from UML diagrams.
8.1 State whether the following statement is true or false, and if false, explain why: If an attribute of a class is marked with a minus sign (-
) in a class diagram, the attribute is not directly accessible outside of the class.
8.2 In Fig. 8.22, the association between the ATM
and the Screen
indicates that:
a. we can navigate from the Screen
to the ATM
b. we can navigate from the ATM
to the Screen
c. Both (a) and (b); the association is bidirectional
d. None of the above
8.3 Write Java code to begin implementing the design for class Keypad
.
Fig. 8.27. Java code for class Keypad
based on Fig. 8.21 and Fig. 8.22.
8.1 True. The minus sign (–
) indicates private visibility.
8.2 b.
8.3 The design for class Keypad
yields the code in Fig. 8.27. Recall that class Keypad
has no attributes for the moment, but attributes may become apparent as we continue the implementation. Also note that if we were designing a real ATM, method getInput
would need to interact with the ATM’s keypad hardware. We’ll actually do input from the keyboard of a personal computer when we write the complete Java code in Appendix H.
In this chapter, we presented additional class concepts. The Time
class case study presented a complete class declaration consisting of private
data, overloaded public
constructors for initialization flexibility, set and get methods for manipulating the class’s data, and methods that returned String
representations of a Time
object in two different formats. You also learned that every class can declare a toString
method that returns a String
representation of an object of the class and that method toString
can be called implicitly whenever an object of a class appears in the code where a String
is expected.
You learned that the this
reference is used implicitly in a class’s non-static
methods to access the class’s instance variables and other non-static
methods. You also saw explicit uses of the this
reference to access the class’s members (including shadowed fields) and how to use keyword this
in a constructor to call another constructor of the class.
We discussed the differences between default constructors provided by the compiler and no-argument constructors provided by the programmer. You learned that a class can have references to objects of other classes as members—a concept known as composition. You saw the enum
class type and learned how it can be used to create a set of constants for use in a program. You learned about Java’s garbage collection capability and how it reclaims the memory of objects that are no longer used. The chapter explained the motivation for static
fields in a class and demonstrated how to declare and use static
fields and methods in your own classes. You also learned how to declare and initialize final
variables.
You learned how to package your own classes for reuse and how to import those classes into an application. Finally, you learned that fields declared without an access modifier are given package access by default. You saw the relationship between classes in the same package that allows each class in a package to access the package-access members of other classes in the package.
In the next chapter, you’ll learn about an important aspect of object-oriented programming in Java—inheritance. You’ll see that all classes in Java are related directly or indirectly to the class called Object
. You’ll also begin to understand how the relationships between classes enable you to build more powerful applications.