8. Classes and Objects: A Deeper Look

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

8.1 Introduction

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.

8.2 Time Class Case Study

Time1 Class Declaration

Our 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.

Image

Fig. 8.2. Time1 object used in an application.

Image

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

Image

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.

Using Class 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.

Notes on the Time1 Class Declaration

Consider 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

Image

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

Image

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.

8.3 Controlling Access to Members

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.

Image

Common Programming Error 8.1

Image

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.

8.4 Referring to the Current Object’s Members with the this Reference

Every 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.

Image

Image

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

Image

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

Image

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

Image

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.

8.5 Time Class Case Study: Overloaded Constructors

As 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.

Class Time2 with Overloaded Constructors

The 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.

Image

Image

Image

Image

Class Time2’s Constructors

Lines 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

Image

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

Image

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

Image

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

Notes Regarding Class Time2’s Set and Get Methods and Constructors

Note 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

Image

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.

Using Class Time2’s Overloaded Constructors

Class 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.

Image

Image

8.6 Default and No-Argument Constructors

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

Image

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

Image

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.

8.7 Notes on Set and Get Methods

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.

Set and Get Methods vs. public Data

It 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.

Validity Checking in Set Methods

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

Image

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

Image

Class designers need not provide set or get methods for each private field. These capabilities should be provided only when it makes sense.

Predicate Methods

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.

Using Set and Get Methods to Create a Class That Is Easier to Debug and Maintain

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.

8.8 Composition

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

Image

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.

Image

Image

Fig. 8.8. Employee class with references to other objects.

Image

Fig. 8.9. Composition demonstration.

Image

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 Strings 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 Strings (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.

8.9 Enumerations

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.

Image

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.

Image

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

Image

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.

8.10 Garbage Collection and Method 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

Image

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.

8.11 static Class Members

Every 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 Martians 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 Martians are present. If fewer than five Martians 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

Image

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

Image

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.

Image

Image

Fig. 8.13. static member demonstration.

Image

Image

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

Image

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 Strings 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

Image

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

Image

Referring to this in a static method is a syntax error.

8.12 static Import

In 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.

Image

Common Programming Error 8.9

Image

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.

8.13 final Instance Variables

The 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

Image

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.

Image

Fig. 8.16. final variable initialized with a constructor argument.

Image

Common Programming Error 8.10

Image

Attempting to modify a final instance variable after it is initialized is a compilation error.

Error-Prevention Tip 8.2

Image

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

Image

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.

Image

Common Programming Error 8.11

Image

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.

8.14 Software Reusability

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

Image

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.

8.15 Data Abstraction and Encapsulation

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 ints 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

Image

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.

8.16 Time Class Case Study: Creating Packages

We 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.

Steps for Declaring a Reusable Class

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.

Image

Fig. 8.19. Time1 object used in an application.

Image

Image

Steps 1 and 2: Creating a public Class and Adding the package Statement

For 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: Compiling the Packaged Class

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

Image

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

Step 4: Importing the Reusable Class

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

Image

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.

Specifying the Classpath During Compilation

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

Image

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

Image

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

Image

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.

Specifying the Classpath When Executing an Application

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.

8.17 Package Access

If no access modifier (public, protected or privateprotected 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.

Image

Image

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.

8.18 (Optional) Software Engineering Case Study: Starting to Program the Classes of the ATM System

In the Software Engineering Case Study sections in Chapters 17, 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.

Visibility

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.276.30.]

Fig. 8.21. Class diagram with visibility markers.

Image

Navigability

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.

Image

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.]

Implementing the ATM System from Its UML Design

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.

Image

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.

Image

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.

Image

[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.276.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.

Image

This concludes our discussion of the basics of generating classes from UML diagrams.

Software Engineering Case Study Self-Review Exercises

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.

Image

Answers to Software Engineering Case Study Self-Review Exercises

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.

8.19 Wrap-Up

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset