Objectives
In this appendix you’ll learn:
• Encapsulation and data hiding.
• 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.
F.3 Controlling Access to Members
F.4 Referring to the Current Object’s Members with the this
Reference
F.5 Time
Class Case Study: Overloaded Constructors
F.6 Default and No-Argument Constructors
Self-Review Exercise | Answers to Self-Review Exercise | Exercises
We now 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. Recall that Section D.10 introduced the basic enum
type to declare a set of constants. In this appendix, 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 appendix also discusses static
class members and final
instance variables in detail. 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.
Time
Class Case StudyOur first example consists of two classes—Time1
(Fig. F.1) and Time1Test
(Fig. F.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’re both public
classes. The output of this program appears in Fig. F.2.
1 // Fig. F.1: Time1.java
2 // Time1 class declaration maintains the time in 24-hour format.
3
4 public class Time1
5 {
6 private int hour; // 0 - 23
7 private int minute; // 0 - 59
8 private int second; // 0 - 59
9
10 // set a new time value using universal time; throw an
11 // exception if the hour, minute or second is invalid
12 public void setTime( int h, int m, int s )
13 {
14 // validate hour, minute and second
15 if ( ( h >= 0 && h < 24 ) && ( m >= 0 && m < 60 ) &&
16 ( s >= 0 && s < 60 ) )
17 {
18 hour = h;
19 minute = m;
20 second = s;
21 } // end if
22 else
23 throw new IllegalArgumentException(
24 "hour, minute and/or second was out of range" );
25 } // end method setTime
26
27 // convert to String in universal-time format (HH:MM:SS)
28 public String toUniversalString()
29 {
30 return String.format( "%02d:%02d:%02d", hour, minute, second );
31 } // end method toUniversalString
32
33 // convert to String in standard-time format (H:MM:SS AM or PM)
34 public String toString()
35 {
36 return String.format( "%d:%02d:%02d %s",
37 ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 ),
38 minute, second, ( hour < 12 ? "AM" : "PM" ) );
39 } // end method toString
40 } // end class Time1
1 // Fig. F.2: Time1Test.java
2 // Time1 object used in an application.
3
4 public class Time1Test
5 {
6 public static void main( String[] args )
7 {
8 // create and initialize a Time1 object
9 Time1 time = new Time1(); // invokes Time1 constructor
10
11 // output string representations of the time
12 System.out.print( "The initial universal time is: " );
13 System.out.println( time.toUniversalString() );
14 System.out.print( "The initial standard time is: " );
15 System.out.println( time.toString() );
16 System.out.println(); // output a blank line
17
18 // change time and output updated time
19 time.setTime( 13, 27, 6 );
20 System.out.print( "Universal time after setTime is: " );
21 System.out.println( time.toUniversalString() );
22 System.out.print( "Standard time after setTime is: ");
23 System.out.println( time.toString() );
24 System.out.println(); // output a blank line
25
26 // attempt to set time with invalid values
27 try
28 {
29 time.setTime( 99, 99, 99 ); // all values out of range
30 } // end try
31 catch ( IllegalArgumentException e )
32 {
33 System.out.printf( "Exception: %s
", e.getMessage() );
34 } // end catch
35
36 // display time after attempt to set invalid values
37 System.out.println( "After attempting invalid settings:" );
38 System.out.print( "Universal time: " );
39 System.out.println( time.toUniversalString() );
40 System.out.print( "Standard time: " );
41 System.out.println( time.toString() );
42 } // end main
43 } // end class Time1Test
The initial universal time is: 00:00:00
The initial standard time is: 12:00:00 AM
Universal time after setTime is: 13:27:06
Standard time after setTime is: 1:27:06 PM
Exception: hour, minute and/or second was out of range
After attempting invalid settings:
Universal time: 13:27:06
Standard time: 1:27:06 PM
Time1
Class DeclarationClass Time1
’s private int
instance variables hour
, minute
and second
(Fig. F.1, lines 6–8) 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–25), toUniversalString
(lines 28–31) and toString
(lines 34–39). 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’s supplied by the compiler. Each instance variable implicitly receives the default value 0
for an int
. Instance variables also can be initialized when they’re declared in the class body, using the same initialization syntax as with a local variable.
setTime
and Throwing ExceptionsMethod setTime
(lines 12–25) is a public
method that declares three int
parameters and uses them to set the time. Lines 15–16 test each argument to determine whether the value is in the proper range, and, if so, lines 18–20 assign the values to the hour
, minute
and second
instance variables. The hour
value 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 must be greater than or equal to 0
and less than 60
. For values outside these ranges, SetTime
throws an exception of type IllegalArgumentException
(lines 23–24), which notifies the client code that an invalid argument was passed to the method. As you learned in Appendix E, you can use try
...catch
to catch exceptions and attempt to recover from them, which we’ll do in Fig. F.2. The throw
statement (line 23) creates a new object of type IllegalArgumentException
. The parentheses following the class name indicate a call to the IllegalArgumentException
constructor. In this case, we call the constructor that allows us to specify a custom error message. After the exception object is created, the throw
statement immediately terminates method setTime
and the exception is returned to the code that attempted to set the time.
toUniversalString
Method toUniversalString
(lines 28–31) takes no arguments and returns a String
in universal-time format, consisting of two digits each for the hour, minute and second. For example, if the time were 1:30:07 PM, the method would return 13:30:07
. Line 30 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
.
toString
Method toString
(lines 34–39) 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 AM or PM (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 37 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, it appears as a value from 1 to 11. The conditional operator in line 38 determines whether AM or PM will be returned as part of the String
.
Recall from Section D.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
is 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 Appendix B, 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. F.2) uses class Time1
. Line 9 declares and creates a Time1
object and assigns it to local variable time
. Operator 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. Next, 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 it was set correctly.
Time1
Method setTime
with Invalid ValuesTo illustrate that method setTime
validates its arguments, line 29 calls method setTime
with invalid arguments of 99
for the hour
, minute
and second
. This statement is placed in a try
block (lines 27–30) in case setTime
throws an IllegalArgumentException
, which it will do since the arguments are all invalid. When this occurs, the exception is caught at lines 31–34, and line 33 displays the exception’s error message by calling its getMessage
method. Lines 37–41 output the time again in both formats to confirm that setTime
did not change the time when invalid arguments were supplied.
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.
The access modifiers public
and private
control access to a class’s variables and methods. In Appendix G, we’ll introduce the access modifier protected
. As you know, 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 need not be concerned with how the class accomplishes its tasks. For this reason, the class’s private
variables and private
methods (i.e., its implementation details) are not accessible to its clients.
Figure F.3 demonstrates that private
class members are not 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 that these private
members are not accessible. This program assumes that the Time1
class from Fig. F.1 is used.
1 // Fig. F.3: MemberAccessTest.java
2 // Private members of class Time1 are not accessible.
3 public class MemberAccessTest
4 {
5 public static void main( String[] args )
6 {
7 Time1 time = new Time1(); // create and initialize Time1 object
8
9 time.hour = 7; // error: hour has private access in Time1
10 time.minute = 15; // error: minute has private access in Time1
11 time.second = 30; // error: second has private access in Time1
12 } // end main
13 } // end class MemberAccessTest
MemberAccessTest.java:9: hour has private access in Time1
time.hour = 7; // error: hour has private access in Time1
^
MemberAccessTest.java:10: minute has private access in Time1
time.minute = 15; // error: minute has private access in Time1
^
MemberAccessTest.java:11: second has private access in Time1
time.second = 30; // error: second has private access in Time1
^
3 errors
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. This enables the class’s code to know which object should be manipulated. As you’ll see in Fig. F.4, you can also use keyword this
explicitly in a non-static
method’s body. Section F.5 shows another interesting use of keyword this
. Section F.10 explains why keyword this
cannot be used in a static
method.
1 // Fig. F.4: ThisTest.java
2 // this used implicitly and explicitly to refer to members of an object.
3
4 public class ThisTest
5 {
6 public static void main( String[] args )
7 {
8 SimpleTime time = new SimpleTime( 15, 30, 19 );
9 System.out.println( time.buildString() );
10 } // end main
11 } // end class ThisTest
12
13 // class SimpleTime demonstrates the "this" reference
14 class SimpleTime
15 {
16 private int hour; // 0-23
17 private int minute; // 0-59
18 private int second; // 0-59
19
20 // if the constructor uses parameter names identical to
21 // instance variable names the "this" reference is
22 // required to distinguish between the names
23 public SimpleTime( int hour, int minute, int second )
24 {
25 this.hour = hour; // set "this" object's hour
26 this.minute = minute; // set "this" object's minute
27 this.second = second; // set "this" object's second
28 } // end SimpleTime constructor
29
30 // use explicit and implicit "this" to call toUniversalString
31 public String buildString()
32 {
33 return String.format( "%24s: %s
%24s: %s",
34 "this.toUniversalString()", this.toUniversalString(),
35 "toUniversalString()", toUniversalString() );
36 } // end method buildString
37
38 // convert to String in universal-time format (HH:MM:SS)
39 public String toUniversalString()
40 {
41 // "this" is not required here to access instance variables,
42 // because method does not have local variables with same
43 // names as instance variables
44 return String.format( "%02d:%02d:%02d",
45 this.hour, this.minute, this.second );
46 } // end method toUniversalString
47 } // end class SimpleTime
this.toUniversalString(): 15:30:19
toUniversalString(): 15:30:19
We now demonstrate implicit and explicit use of the this
reference (Fig. F.4). 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
in lines 14–47. We do this to demonstrate that when you compile a .java
file containing 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 compiler places both class files for those classes in the same directory. Note also in Fig. F.4 that only class ThisTest
is declared public
. A source-code file can contain only one public
class—otherwise, a compilation error occurs. Non-public
classes can be used only by other classes in the same package. So, in this example, class SimpleTime
can be used only by class ThisTest
.
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. 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 a case in which explicit use of the this
reference is required. 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 on the left sides of the assignments 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 it explicitly to call method toUniversalString
. Line 35 uses it implicitly to call the same method. Both lines perform the same task. You typically will not use this
explicitly to reference other methods within the current object. Also, 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.
It’s 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.
Avoid method-parameter names or local-variable names that conflict with field names. This helps prevent subtle, hard-to-locate bugs.
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.
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.
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.
Time2
with Overloaded ConstructorsThe default constructor for class Time1
(Fig. F.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. F.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. 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. Class Time2
also provides set and get methods for each instance variable.
1 // Fig. F.5: Time2.java
2 // Time2 class with overloaded constructors.
3
4 public class Time2
5 {
6 private int hour; // 0 - 23
7 private int minute; // 0 - 59
8 private int second; // 0 - 59
9
10 // Time2 no-argument constructor:
11 // initializes each instance variable to zero
12 public Time2()
13 {
14 this( 0, 0, 0 ); // invoke Time2 constructor with three arguments
15 } // end Time2 no-argument constructor
16
17 // Time2 constructor: hour supplied, minute and second defaulted to 0
18 public Time2( int h )
19 {
20 this( h, 0, 0 ); // invoke Time2 constructor with three arguments
21 } // end Time2 one-argument constructor
22
23 // Time2 constructor: hour and minute supplied, second defaulted to 0
24 public Time2( int h, int m )
25 {
26 this( h, m, 0 ); // invoke Time2 constructor with three arguments
27 } // end Time2 two-argument constructor
28
29 // Time2 constructor: hour, minute and second supplied
30 public Time2( int h, int m, int s )
31 {
32 setTime( h, m, s ); // invoke setTime to validate time
33 } // end Time2 three-argument constructor
34
35 // Time2 constructor: another Time2 object supplied
36 public Time2( Time2 time )
37 {
38 // invoke Time2 three-argument constructor
39 this( time.getHour(), time.getMinute(), time.getSecond() );
40 } // end Time2 constructor with a Time2 object argument
41
42 // Set Methods
43 // set a new time value using universal time;
44 // validate the data
45 public void setTime( int h, int m, int s )
46 {
47 setHour( h ); // set the hour
48 setMinute( m ); // set the minute
49 setSecond( s ); // set the second
50 } // end method setTime
51
52 // validate and set hour
53 public void setHour( int h )
54 {
55 if ( h >= 0 && h < 24 )
56 hour = h;
57 else
58 throw new IllegalArgumentException( "hour must be 0-23" );
59 } // end method setHour
60
61 // validate and set minute
62 public void setMinute( int m )
63 {
64 if ( m >= 0 && m < 60 )
65 minute = m;
66 else
67 throw new IllegalArgumentException( "minute must be 0-59" );
68 } // end method setMinute
69
70 // validate and set second
71 public void setSecond( int s )
72 {
73 if ( s >= 0 && s < 60 )
74 second = ( ( s >= 0 && s < 60 ) ? s : 0 );
75 else
76 throw new IllegalArgumentException( "second must be 0-59" );
77 } // end method setSecond
78
79 // Get Methods
80 // get hour value
81 public int getHour()
82 {
83 return hour;
84 } // end method getHour
85
86 // get minute value
87 public int getMinute()
88 {
89 return minute;
90 } // end method getMinute
91
92 // get second value
93 public int getSecond()
94 {
95 return second;
96 } // end method getSecond
97
98 // convert to String in universal-time format (HH:MM:SS)
99 public String toUniversalString()
100 {
101 return String.format(
102 "%02d:%02d:%02d", getHour(), getMinute(), getSecond() );
103 } // end method toUniversalString
104
105 // convert to String in standard-time format (H:MM:SS AM or PM)
106 public String toString()
107 {
108 return String.format( "%d:%02d:%02d %s",
109 ( (getHour() == 0 || getHour() == 12) ? 12 : getHour() % 12 ),
110 getMinute(), getSecond(), ( getHour() < 12 ? "AM" : "PM" ) );
111 } // end method toString
112 } // end class Time2
Lines 12–15 declare a so-called no-argument constructor that’s invoked without arguments. Once you declare any constructors in a class, the compiler will not provide a default constructor. This no-argument constructor ensures that class Time2
’s clients can create Time2
objects with default values. 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’s allowed only as the first statement in a constructor’s body. Line 14 uses this
in methodcall syntax to invoke the Time2
constructor that takes three parameters (lines 30–33) with values of 0 for the hour
, minute
and second
. 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’s possible that the changes the class might require would be localized to the set methods.
It’s a compilation 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’s also a compilation error when a method attempts to invoke a constructor directly via this
.
A constructor can call methods of the class. Be aware that the instance variables might not yet be initialized, because the constructor is in the process of initializing the object. Using instance variables before they’ve been initialized properly is a logic error.
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.
Lines 36–40 declare a Time2
constructor that receives a 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
. 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 F.1
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 setTime
MethodMethod setTime
(lines 45–50) invokes the setHour
(lines 53–59), setMinute
(lines 62–68) and setSecond
(lines 71–77) methods, which 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, each of these methods throws an IllegalArgumentException
(lines 58, 67 and 76) indicating which value was out of range.
Time2
’s set and get Methods and ConstructorsTime2
’s set and get methods are called throughout 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 102 and lines 109–110, 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 include a copy of the appropriate statements from methods setHour
, setMinute
and setSecond
. Doing so may be slightly more efficient, because the extra calls to the constructor and 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 that any changes to the implementation of setTime
be made only once. Also, the compiler can optimize programs by removing calls to simple methods and replacing them with the expanded code of their declarations—a technique known as inlining the code, which improves program performance.
Software Engineering Observation F.2
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. F.6) invokes the overloaded Time2
constructors (lines 8–12 and 40). Line 8 invokes the no-argument constructor (Fig. F.5, lines 12–15). Lines 9–13 of the program demonstrate passing arguments to the other Time2
constructors. Line 9 invokes the single-argument constructor that receives an int
at lines 18–21 of Fig. F.5. Line 10 invokes the two-argument constructor at lines 24–27 of Fig. F.5. Line 11 invokes the three-argument constructor at lines 30–33 of Fig. F.5. Line 12 invokes the single-argument constructor that takes a Time2
at lines 36–40 of Fig. F.5. Next, the application displays the String
representations of each Time2
object to confirm that it was initialized properly. Line 40 attempts to intialize t6
by creating a new Time2
object and passing three invalid values to the constructor. When the constructor attempts to use the invalid hour value to initialize the object’s hour
, an IllegalArgumentException
occurs. We catch this exception at line 42 and display its error message, which results in the last line of the output.
1 // Fig. F.6: Time2Test.java
2 // Overloaded constructors used to initialize Time2 objects.
3
4 public class Time2Test
5 {
6 public static void main( String[] args )
7 {
8 Time2 t1 = new Time2(); // 00:00:00
9 Time2 t2 = new Time2( 2 ); // 02:00:00
10 Time2 t3 = new Time2( 21, 34 ); // 21:34:00
11 Time2 t4 = new Time2( 12, 25, 42 ); // 12:25:42
12 Time2 t5 = new Time2( t4 ); // 12:25:42
13
14 System.out.println( "Constructed with:" );
15 System.out.println( "t1: all arguments defaulted" );
16 System.out.printf( " %s
", t1.toUniversalString() );
17 System.out.printf( " %s
", t1.toString() );
18
19 System.out.println(
20 "t2: hour specified; minute and second defaulted" );
21 System.out.printf( " %s
", t2.toUniversalString() );
22 System.out.printf( " %s
", t2.toString() );
23
24 System.out.println(
25 "t3: hour and minute specified; second defaulted" );
26 System.out.printf( " %s
", t3.toUniversalString() );
27 System.out.printf( " %s
", t3.toString() );
28
29 System.out.println( "t4: hour, minute and second specified" );
30 System.out.printf( " %s
", t4.toUniversalString() );
31 System.out.printf( " %s
", t4.toString() );
32
33 System.out.println( "t5: Time2 object t4 specified" );
34 System.out.printf( " %s
", t5.toUniversalString() );
35 System.out.printf( " %s
", t5.toString() );
36
37 // attempt to initialize t6 with invalid values
38 try
39 {
40 Time2 t6 = new Time2( 27, 74, 99 ); // invalid values
41 } // end try
42 catch ( IllegalArgumentException e )
43 {
44 System.out.printf( "
Exception while initializing t6: %s
",
45 e.getMessage() );
46 } // end catch
47 } // end main
48 } // end class Time2Test
Constructed with:
t1: all arguments defaulted
00:00:00
12:00:00 AM
t2: hour specified; minute and second defaulted
02:00:00
2:00:00 AM
t3: hour and minute specified; second defaulted
21:34:00
9:34:00 PM
t4: hour, minute and second specified
12:25:42
12:25:42 PM
t5: Time2 object t4 specified
12:25:42
12:25:42 PM
Exception while initializing t6: hour must be 0-23
Every class must have at least one constructor. If you do not provide any in a class’s declaration, the compiler creates a default constructor that takes no arguments when it’s 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 G.4.1, you’ll learn that the default constructor performs another task also.
If your class declares constructors, the compiler will not create a default constructor. In this case, you must declare a no-argument constructor if default initialization is required. Like a default constructor, a no-argument constructor is invoked with empty parentheses. The Time2
no-argument constructor (lines 12–15 of Fig. F.5) 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()
.
A class can have references to objects of other classes as members. This is called composition and is sometimes referred to as a has-a relationship. For example, an AlarmClock
object needs to know the current time and the time when it’s supposed to sound its alarm, so it’s reasonable to include two references to Time
objects in an AlarmClock
object.
Date
This composition example contains classes Date
(Fig. F.7), Employee
(Fig. F.8) and EmployeeTest
(Fig. F.9). Class Date
(Fig. F.7) declares instance variables month, day and year (lines 6–8) to represent a date. The constructor receives three int
parameters. Line 17 invokes utility method checkMonth
(lines 26–32) to validate the month—if the value is out of range the method throws an exception. Line 15 assumes that the value for year
is correct and doesn’t validate it. Line 19 invokes utility method checkDay
(lines 35–48) to validate the day
based on the current month
and year
. Line 38 determines whether the day is correct based on the number of days in the particular month
. If the day is not correct, lines 42–43 determine whether the month
is February, the day is 29
and the year
is a leap year. If the day
is still invalid, the method throws an exception. Lines 21–22 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 51–54) is called implicitly to obtain the object’s String
representation.
1 // Fig. F.7: Date.java
2 // Date class declaration.
3
4 public class Date
5 {
6 private int month; // 1-12
7 private int day; // 1-31 based on month
8 private int year; // any year
9
10 private static final int[] daysPerMonth = // days in each month
11 { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
12
13 // constructor: call checkMonth to confirm proper value for month;
14 // call checkDay to confirm proper value for day
15 public Date( int theMonth, int theDay, int theYear )
16 {
17 month = checkMonth( theMonth ); // validate month
18 year = theYear; // could validate year
19 day = checkDay( theDay ); // validate day
20
21 System.out.printf(
22 "Date object constructor for date %s
", this );
23 } // end Date constructor
24
25 // utility method to confirm proper month value
26 private int checkMonth( int testMonth )
27 {
28 if ( testMonth > 0 && testMonth <= 12 ) // validate month
29 return testMonth;
30 else // month is invalid
31 throw new IllegalArgumentException( "month must be 1-12" );
32 } // end method checkMonth
33
34 // utility method to confirm proper day value based on month and year
35 private int checkDay( int testDay )
36 {
37 // check if day in range for month
38 if ( testDay > 0 && testDay <= daysPerMonth[ month ] )
39 return testDay;
40
41 // check for leap year
42 if ( month == 2 && testDay == 29 && ( year % 400 == 0 ||
43 ( year % 4 == 0 && year % 100 != 0 ) ) )
44 return testDay;
45
46 throw new IllegalArgumentException(
47 "day out-of-range for the specified month and year" );
48 } // end method checkDay
49
50 // return a String of the form month/day/year
51 public String toString()
52 {
53 return String.format( "%d/%d/%d", month, day, year );
54 } // end method toString
55 } // end class Date
1 // Fig. F.8: Employee.java
2 // Employee class with references to other objects.
3
4 public class Employee
5 {
6 private String firstName;
7 private String lastName;
8 private Date birthDate;
9 private Date hireDate;
10
11 // constructor to initialize name, birth date and hire date
12 public Employee( String first, String last, Date dateOfBirth,
13 Date dateOfHire )
14 {
15 firstName = first;
16 lastName = last;
17 birthDate = dateOfBirth;
18 hireDate = dateOfHire;
19 } // end Employee constructor
20
21 // convert Employee to String format
22 public String toString()
23 {
24 return String.format( "%s, %s Hired: %s Birthday: %s",
25 lastName, firstName, hireDate, birthDate );
26 } // end method toString
27 } // end class Employee
1 // Fig. F.9: EmployeeTest.java
2 // Composition demonstration.
3
4 public class EmployeeTest
5 {
6 public static void main( String[] args )
7 {
8 Date birth = new Date( 7, 24, 1949 );
9 Date hire = new Date( 3, 12, 1988 );
10 Employee employee = new Employee( "Bob", "Blue", birth, hire );
11
12 System.out.println( employee );
13 } // end main
14 } // end class EmployeeTest
Date object constructor for date 7/24/1949
Date object constructor for date 3/12/1988
Blue, Bob Hired: 3/12/1988 Birthday: 7/24/1949
Employee
Class Employee
(Fig. F.8) has instance variables firstName
, lastName
, birthDate
and hireDate
. Members firstName
and lastName
(lines 6–7) are references to String
objects. 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 are assigned to the Employee
object’s instance variables. When class Employee
’s toString
method is called, it returns a String
containing the employee’s name and 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.
EmployeeTest
Class EmployeeTest
(Fig. F.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. D.5, we introduced the basic enum
type, which defines a set of constants 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
constants are implicitly final
, because they declare constants that shouldn’t 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 F.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 is optionally followed by arguments which are passed to the enum
constructor (lines 20–24). Like the constructors you’ve seen in classes, an enum
constructor can specify any number of parameters and can be overloaded. In this example, the enum
constructor requires two String
parameters. To properly initialize each enum
constant, we follow it with parentheses containing two String
arguments, which are passed to the enum
’s constructor. 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).
1 // Fig. F.10: Book.java
2 // Declaring an enum type with constructor and explicit instance fields
3 // and accessors for these fields
4
5 public enum Book
6 {
7 // declare constants of enum type
8 JHTP( "Java How to Program", "2012" ),
9 CHTP( "C How to Program", "2007" ),
10 IW3HTP( "Internet & World Wide Web How to Program", "2008" ),
11 CPPHTP( "C++ How to Program", "2012" ),
12 VBHTP( "Visual Basic 2010 How to Program", "2011" ),
13 CSHARPHTP( "Visual C# 2010 How to Program", "2011" );
14
15 // instance fields
16 private final String title; // book title
17 private final String copyrightYear; // copyright year
18
19 // enum constructor
20 Book( String bookTitle, String year )
21 {
22 title = bookTitle;
23 copyrightYear = year;
24 } // end enum Book constructor
25
26 // accessor for field title
27 public String getTitle()
28 {
29 return title;
30 } // end method getTitle
31
32 // accessor for field copyrightYear
33 public String getCopyrightYear()
34 {
35 return copyrightYear;
36 } // end method getCopyrightYear
37 } // end enum Book
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’s title and one that specifies its copyright year. 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 F.11 tests the enum
type Book
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. 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. 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., JHTP
for the first enum
constant).
1 // Fig. F.11: EnumTest.java
2 // Testing enum type Book.
3 import java.util.EnumSet;
4
5 public class EnumTest
6 {
7 public static void main( String[] args )
8 {
9 System.out.println( "All books:
" );
10
11 // print all books in enum Book
12 for ( Book book : Book.values() )
13 System.out.printf( "%-10s%-45s%s
", book,
14 book.getTitle(), book.getCopyrightYear() );
15
16 System.out.println( "
Display a range of enum constants:
" );
17
18 // print first four books
19 for ( Book book : EnumSet.range( Book.JHTP, Book.CPPHTP ) )
20 System.out.printf( "%-10s%-45s%s
", book,
21 book.getTitle(), book.getCopyrightYear() );
22 } // end main
23 } // end class EnumTest
All books:
JHTP Java How to Program 2012
CHTP C How to Program 2007
IW3HTP Internet & World Wide Web How to Program 2008
CPPHTP C++ How to Program 2012
VBHTP Visual Basic 2010 How to Program 2011
CSHARPHTP Visual C# 2010 How to Program 2011
Display a range of enum constants:
JHTP Java How to Program 2012
CHTP C How to Program 2007
IW3HTP Internet & World Wide Web How to Program 2008
CPPHTP C++ How to Program 2012
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.JHTP
, Book.CPPHTP )
returns an EnumSet
containing Book.JHTP
, Book.CHTP
, Book.IW3HTP
and Book.CPPHTP
. The enhanced for
statement can be used with an EnumSet
just as it can with an array, so lines 12–14 use it 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.
In an enum
declaration, it’s a syntax error to declare enum
constants after the enum
type’s constructors, fields and methods.
Every object uses system resources, such as memory. We need a disciplined way to give resources back to the system when they’re no longer needed; otherwise, “resource leaks” might occur that would prevent them from being reused by your program or possibly by other programs. The JVM performs automatic garbage collection to reclaim the memory occupied by objects that are no longer used. When there are no more references to an object, the object is eligible to be collected. This typically occurs when the JVM executes its garbage collector. So, 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 may open a file on disk to modify its contents. If it does not close the file, the application must terminate before any other application can use it.
Software Engineering Observation F.3
A class that uses system resources, such as files on disk, should provide a method that programmers can call to release resources when they’re no longer needed in a program. Many Java API classes provide close
or dispose
methods for this purpose. For example, class Scanner
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
object. 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’s only one copy, so we do not have to increment separate copies for each Martian
object.
Software Engineering Observation F.4
Use a static
variable when all objects of a class must use the same copy of the variable.
Static variables have class scope. We can access a class’s public static
members 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 by client code only through methods of the class. Actually, static
class members exist even when no objects of the class exist—they’re 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, provide a public static
method and call it by qualifying its name with the class name and a dot.
Software Engineering Observation F.5
Static class variables and methods exist, and can be used, even if no objects of that class have been instantiated.
A static
method 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. The this
reference 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.
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.
Our next program declares two classes—Employee
(Fig. F.12) and EmployeeTest
(Fig. F.13). Class Employee
declares private static
variable count
(Fig. F.12, line 9) and public static
method getCount
(lines 36–39). The static
variable count
is initialized to zero in line 9. If a static
variable is not initialized, the compiler assigns it a default value—in this case 0
, the default value for type int
. Variable count
maintains a count of the number of objects of class Employee
that have been created so far.
1 // Fig. F.12: Employee.java
2 // Static variable used to maintain a count of the number of
3 // Employee objects in memory.
4
5 public class Employee
6 {
7 private String firstName;
8 private String lastName;
9 private static int count = 0; // number of Employees created
10
11 // initialize Employee, add 1 to static count and
12 // output String indicating that constructor was called
13 public Employee( String first, String last )
14 {
15 firstName = first;
16 lastName = last;
17
18 ++count; // increment static count of employees
19 System.out.printf( "Employee constructor: %s %s; count = %d
",
20 firstName, lastName, count );
21 } // end Employee constructor
22
23 // get first name
24 public String getFirstName()
25 {
26 return firstName;
27 } // end method getFirstName
28
29 // get last name
30 public String getLastName()
31 {
32 return lastName;
33 } // end method getLastName
34
35 // static method to get static count value
36 public static int getCount()
37 {
38 return count;
39 } // end method getCount
40 } // end class Employee
1 // Fig. F.13: EmployeeTest.java
2 // static member demonstration.
3
4 public class EmployeeTest
5 {
6 public static void main( String[] args )
7 {
8 // show that count is 0 before creating Employees
9 System.out.printf( "Employees before instantiation: %d
",
10 Employee.getCount() );
11
12 // create two Employees; count should be 2
13 Employee e1 = new Employee( "Susan", "Baker" );
14 Employee e2 = new Employee( "Bob", "Blue" );
15
16 // show that count is 2 after creating two Employees
17 System.out.println( "
Employees after instantiation: " );
18 System.out.printf( "via e1.getCount(): %d
", e1.getCount() );
19 System.out.printf( "via e2.getCount(): %d
", e2.getCount() );
20 System.out.printf( "via Employee.getCount(): %d
",
21 Employee.getCount() );
22
23 // get names of Employees
24 System.out.printf( "
Employee 1: %s %s
Employee 2: %s %s
",
25 e1.getFirstName(), e1.getLastName(),
26 e2.getFirstName(), e2.getLastName() );
27
28 // in this example, there is only one reference to each Employee,
29 // so the following two statements indicate that these objects
30 // are eligible for garbage collection
31 e1 = null;
32 e2 = null;
33 } // end main
34 } // end class EmployeeTest
Employees before instantiation: 0
Employee constructor: Susan Baker; count = 1
Employee constructor: Bob Blue; count = 2
Employees after instantiation:
via e1.getCount(): 2
via e2.getCount(): 2
via Employee.getCount(): 2
Employee 1: Susan Baker
Employee 2: Bob Blue
When Employee
objects exist, variable count
can be used in any method of an Employee
object—this example increments count
in the constructor (line 18). The public static
method getCount
(lines 36–39) returns the number of Employee
objects that have been created so far. When no objects of class Employee
exist, client code can access variable count
by calling method getCount
via the class name, as in Employee.getCount()
. When objects exist, method getCount
can also be called via any reference to an Employee
object.
Invoke every static
method by using the class name and a dot (.
) to emphasize that the method being called is a static
method.
EmployeeTest
method main
(Fig. F.13) instantiates two Employee
objects (lines 13–14). When each Employee
object’s constructor is invoked, lines 15–16 of Fig. F.12 assign the Employee
’s first name and last name to instance variables firstName
and lastName
. 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’re created. Therefore, it’s 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 we’re 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 (Fig. F.13). At this point, references e1
and e2
no longer refer to the objects that were instantiated in lines 13–14. The objects become “eligible for garbage collection” because there are no more references to them in the program.
Eventually, the garbage collector might reclaim the memory for these objects (or the operating system will reclaim the memory when the program terminates). The JVM does not guarantee when, or even whether, the garbage collector will execute. When it does, it’s possible that no objects or only a subset of the eligible objects will be collected.
final
Instance VariablesThe principle of least privilege is fundamental to good software engineering. In the context of an application, it states that code should be granted only the amount of privilege and access that it needs to accomplish its designated task, but no more. This makes your programs more robust by preventing code from accidentally (or maliciously) modifying variable values and calling methods that should not be accessible.
Let’s see how this principle applies to instance variables. Some of them 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’s 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
. Such variables can be initialized when they’re declared. If they are not, they must be initialized in every constructor of the class. Initializing constants in constructors enables each object of the class to have a different value for the constant. If a final
variable is not initialized in its declaration or in every constructor, a compilation error occurs.
Software Engineering Observation F.6
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.
Attempting to modify a final
instance variable after it’s initialized is a compilation error.
Attempts to modify a final
instance variable are caught at compilation time rather than causing execution-time errors. It’s always preferable to get bugs out at compilation time, if possible, rather than allow them to slip through to execution time (where experience has found that repair is often many times more expensive).
Software Engineering Observation F.7
A final
field should also be declared static
if it’s initialized in its declaration to a value that’s the same for all objects of the class. After this initialization, its value can never change. Therefore, we don’t need 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.
We’ve 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. These packages are defined once, but can be imported into many programs. As applications become more complex, packages help you manage the complexity of application components. Packages also facilitate software reuse by enabling programs to import classes from other packages (as we’ve done in most examples), rather than copying the classes into each program that uses them. Another benefit of packages is that they provide a convention for unique class names, which helps prevent class-name conflicts.
If no access modifier (public
, protected
or private
) is specified for a method or variable when it’s declared in a class, the method or variable has 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, or in the case of static
members through the class name. Package access is rarely used.
In this appendix, 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 (unpredictably) reclaims the memory of objects that are no longer used. We 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 that fields declared without an access modifier are given package access by default and that classes in the same package can access the package-access members of other classes in the package.
In the next appendix, you’ll learn about two important aspects of object-oriented programming in Java—inheritance and polymorphism. 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.
F.1 Fill in the blanks in each of the following statements:
a. The public
methods of a class are also known as the class’s __________ or __________.
b. String
class static
method __________ is similar to method System.out.printf
, but returns a formatted String
rather than displaying a String
in a command window.
c. If a method contains a local variable with the same name as one of its class’s fields, the local variable __________ the field in that method’s scope.
d. Keyword __________ specifies that a variable is not modifiable.
e. The __________ states that code should be granted only the amount of privilege and access that it needs to accomplish its designated task.
f. If a class declares constructors, the compiler will not create a(n) __________.
g. An object’s __________ method is called implicitly when an object appears in code where a String
is needed.
h. For every enum
, the compiler generates a static
method called __________ that returns an array of the enum
’s constants in the order in which they were declared.
i. Composition is sometimes referred to as a(n) __________ relationship.
j. A(n) __________ declaration contains a comma-separated list of constants.
k. A(n) __________ variable represents classwide information that’s shared by all the objects of the class.
a. public
services, public
interface.
b. format
.
c. shadows.
d. final
.
e. principle of least privilege.
f. default constructor.
g. toString
.
h. values
.
i. has-a.
j. enum
.
k. static
.
F.2 (Rectangle Class) Create a class Rectangle
with attributes length
and width
, each of which defaults to 1. Provide methods that calculate the rectangle’s perimeter and area. Provide set and get methods for both length
and width
. The set methods should verify that length
and width
are each floating-point numbers larger than 0.0 and less than 20.0. Write a program to test class Rectangle
.
F.3 (Savings Account Class) Create class SavingsAccount
. Use a static
variable annualInterestRate
to store the annual interest rate for all account holders. Each object of the class should contain a private
instance variable savingsBalance
indicating the amount the saver currently has on deposit. Provide method calculateMonthlyInterest
to calculate the monthly interest by multiplying the savingsBalance
by annualInterestRate
divided by 12—this interest should be added to savingsBalance
. Provide a static
method modifyInterestRate
that sets the annualInterestRate
to a new value. Write a program to test class SavingsAccount
. Instantiate two savingsAccount
objects, saver1
and saver2
, with balances of $2000.00 and $3000.00, respectively. Set annualInterestRate
to 4%, then calculate the monthly interest for each of 12 months and print the new balances for both savers. Next, set the annualInterestRate
to 5%, calculate the next month’s interest and print the new balances for both savers.
F.4 (Enhancing Class Time2) Modify class Time2
of Fig. F.5 to include a tick
method that increments the time stored in a Time2
object by one second. Provide method incrementMinute
to increment the minute by one and method incrementHour
to increment the hour by one. Write a program that tests the tick
method, the incrementMinute
method and the incrementHour
method to ensure that they work correctly. Be sure to test the following cases:
a. incrementing into the next minute,
b. incrementing into the next hour and
c. incrementing into the next day (i.e., 11:59:59 PM to 12:00:00 AM).
F.5 Write an enum
type TrafficLight
, whose constants (RED
, GREEN
, YELLOW
) take one parameter—the duration of the light. Write a program to test the TrafficLight enum
so that it displays the enum
constants and their durations.
F.6 (Date Class) Create class Date
with the following capabilities:
a. Output the date in multiple formats, such as
MM/DD/YYYY
June 14, 1992
DDD YYYY
b. Use overloaded constructors to create Date
objects initialized with dates of the formats in part (a). In the first case the constructor should receive three integer values. In the second case it should receive a String
and two integer values. In the third case it should receive two integer values, the first of which represents the day number in the year. [Hint: To convert the String
representation of the month to a numeric value, compare String
s using the equals
method. For example, if s1
and s2
are String
s, the method call s1.equals( s2 )
returns true
if the String
s are identical and otherwise returns false
.]
F.7 (Huge Integer Class) Create a class HugeInteger
which uses a 40-element array of digits to store integers as large as 40 digits each. Provide methods parse
, toString
, add
and subtract
. Method parse
should receive a String
, extract each digit using method charAt
and place the integer equivalent of each digit into the integer array. For comparing HugeInteger
objects, provide the following methods: isEqualTo
, isNotEqualTo
, isGreaterThan
, isLessThan
, isGreaterThanOrEqualTo
and isLessThanOrEqualTo
. Each of these so-called predicate methods (that is, methods that test a condition and return true
or false
) returns true
if the relationship holds between the two HugeInteger
objects and returns false
if the relationship does not hold. Provide a predicate method isZero
. If you feel ambitious, also provide methods multiply
, divide
and remainder
. [Note: Primitive boolean
values can be output as the word “true” or the word “false” with format specifier %b
.]
F.8 (Tic-Tac-Toe) Create a class TicTacToe
that will enable you to write a program to play Tic-Tac-Toe. The class contains a private 3-by-3 two-dimensional array. Use an enumeration to represent the value in each cell of the array. The enumeration’s constants should be named X
, O
and EMPTY
(for a position that does not contain an X
or an O
). The constructor should initialize the board elements to EMPTY
. Allow two human players. Wherever the first player moves, place an X
in the specified square, and place an O
wherever the second player moves. Each move must be to an empty square. After each move, determine whether the game has been won and whether it’s a draw. If you feel ambitious, modify your program so that the computer makes the moves for one of the players. Also, allow the player to specify whether he or she wants to go first or second. If you feel exceptionally ambitious, develop a program that will play three-dimensional Tic-Tac-Toe on a 4-by-4-by-4 board [Note: This is an extremely challenging project!].