Methods: Communicating with Objects
Site: | Saylor Academy |
Course: | CS101: Introduction to Computer Science I |
Book: | Methods: Communicating with Objects |
Printed by: | Guest user |
Date: | Wednesday, May 14, 2025, 5:00 AM |
Description
We communicate with objects using methods. Methods are executable code within each object, for which an interface has been established. Sometimes the interface is only for the object itself. Other times it is an interface accessible by other objects. This chapter discusses that topic in detail.
Table of contents
- 3.1 Introduction
- 3.2 Passing Information to an Object
- 3.3 Constructor Methods
- 3.4 Retrieving Information from an Object
- 3.5 Passing a Value and Passing a Reference
- 3.6 Flow of Control: Control Structures
- 3.7 Testing an Improved OneRowNim
- 3.8 From the Java Library java.lang.Object
- 3.9 Object-Oriented Design: Inheritance and Polymorphism
- 3.10 Drawing Lines and Defining Graphical Methods (Optional)
- Chapter Summary
3.1 Introduction
Methods: Communicating with Objects
OBJECTIVES
After studying this chapter, you will
• Understand the role that methods play in an object-oriented program.
• Know how to use parameters and arguments to pass data to an object.
• Understand how constructor methods are used to instantiate objects.
• Know the difference between passing a value and passing a reference to an object.
• Be able to design your own methods.
• Know how to use the if-else and while control structures.
OUTLINE
3.1 Introduction
3.2 Passing Information to an Object
3.3 Constructor Methods
3.4 Retrieving Information from an Object
3.5 Passing a Value and Passing a Reference
3.6 Flow of Control: Control Structures
3.7 Testing an Improved OneRowNim
Special Topic: Intelligent Agents
3.8 From the Java Library: java.lang.Object
3.9 Object-Oriented Design: Inheritance and Polymorphism
3.10 Drawing Lines and Defining Graphical Methods (Optional)
3.1 Introduction
In this chapter, we take a look at Java methods and parameters. Methods and parameters are the primary mechanisms for passing information into and out of an object. We will once again focus on the OneRowNim simulation that we designed in the previous chapter. That version was sufficient to introduce us to Java objects and classes, but it was limited in its ability to communicate with other objects. In this chapter, we want to expand OneRowNim to make our simulation more realistic. We begin by learning how to pass information to an object. That will enable us to specify the number of sticks to remove using a single method. We then consider special methods called constructors, which are used to initialize an object’s state when it is created. We also learn how to retrieve information from an object. That will enable us to request a OneRowNim object for several different bits of information. Then we consider the if-else and while control structures which allow us to define more useful methods and write more realistic test programs.
Source: R. Morelli and R. Walde, Trinity College
This work
is licensed under a Creative Commons Attribution 4.0 License.
3.2 Passing Information to an Object
One convention of object-oriented programming is to provide public
methods to set and get the values of some of its private instance variables. Methods that set or modify an object’s instance variables are called
mutator methods. Methods that get or retrieve the value of an instance
variable are called accessor methods.
It is up to the designer of the class to determine which private variables require accessor and mutator methods. If you were designing a
BankAccount class, you might want a public getAccountNumber()
method, so that clients could retrieve information about their bank accounts, but you would probably not want a public getAccountPassword()
method or a public setAccountBalance() method.
In the remainder of this section, we will be concerned with mutator methods. We defined three mutator methods named takeOne(),
takeTwo(), and takeThree as part of the OneRowNim class in the previous chapter. All three of these methods change the values of the instance variables nSticks and player. All three methods have very similar
bodies. The definition of the takeOne() is:
The only difference in the bodies of the other two methods is that they
subtract 2 and 3 from nSticks instead of 1. Instead of having three, virtually identical methods, It would be a more efficient design to define a
single method where the number to be subtracted from nSticks would
be supplied as an argument when the method is called. In order to be able
to handle such an argument, we must design a new method that uses a
parameter to handle the argument.
A formal parameter, or more simply, parameter, is a variable used to
pass information into a method when the method is invoked. The type and variable name of the formal parameter must appear in the formal parameter
list that follows the method’s name in the method header. The formal
parameter is used to hold a value that is passed while the method is
executing.
Consider the following definition for a takeSticks() method:
Notice that executing the body of takeSticks() when the parameter
num stores the value 1 accomplishes precisely the same task as executing
takeOne(). If, instead, a value of 2 or 3 is stored in num, then calling the
method acts like takeTwo() or takeThree() respectively. Thus, using
parameters enables us to design methods that are more general in what
they do, which is an important principle of good program design.
Another example of a mutator method is one which defines a set
method to allow the starting number of sticks to be set for an instance of
OneRowNim. For this, we could define:
As we will see in Section 3.3, we can also define a constructor method that
can be used, when the game is created, to set the initial value of nSticks. It is often desirable to have more than one method that sets the values of
an objects’ instance variables.
If a method uses more than one parameter, use a comma to separate the
individual parameter declarations in the method header. For example, if
we wanted a method for OneRowNim that specified both the number of
sticks for the start of a game and which player takes a turn first, it could
be defined:
The Scope of Parameters, Variables, and Methods
The bodies of the mutator methods in the previous section make use of
both instance variables and parameters. It is important to note that there
is a difference in where these two types of variables can be used. The scope of a variable or method refers to where it can be used in a program.
A parameter’s scope is limited to the body of the method in which it is
declared. Variables that are declared in the body of a method have scope
which extends from the point where they are declared to the end of the
block of code in which they are declared. Parameters are local variables
which are declared in the parameter list of a method’s header and which
have initial values specified by the arguments in a method call. The scope
of a parameter is the same as the scope of a variable declared at the very
Local scope beginning of the body of a method. Once the flow of execution leaves a
method, its parameters and other local variables cease to exist. The scope
of local variables is referred to as local scope.
By contrast, instance variables, class variables, and all methods have
scope that extends throughout the entire class, that is, class scope. They
can be used in the body of any method and in the expressions that assign initial values to class level variables. There are two restrictions to
remember. First, instance variables and instance methods cannot be used
in the body of a class method, one modified with static, unless an instance of the class is created there and then the dot notation of qualified
Class scope names must be used to refer to the variable or method. This is because
class methods are called without reference to a particular instance of the
class. The main() method of the OneRowNim class that we defined in the
previous chapter is an example of such a class method. In that case, to test the instance methods of OneRowNim we first created an instance of
OneRowNim and used it to call its instance methods:
The second restriction involved in class scope is that one class level variable can be used in the expression that initializes a second class level variable only if the first is declared before the second. There is no similar
restriction on methods.
Except for the restrictions noted above, methods and class level variables can be referred to within the same class by their simple names,
with just the method (or variable) name itself, rather than by their qualified names, with the dot operator. Thus, in OneRowNim, we can refer to
nSticks and report() in the bodies of other instance methods. In a
class method, such as main(), we would have to create an instance of
OneRowNim with a name like game and refer to game.report().
Arguments and Parameters
The new class definition for OneRowNim is given in Figure 3.1. Note that
now that we have a single method, takeSticks(), that can be used
to take away a variable number of sticks, we have removed the three
methods we wrote in the previous chapter, takeOne(), takeTwo(), and
takeThree(), from OneRowNim. Using a single method, with a parameter, is clearly a better design. To see this, just imagine what we would have
to do if we didn’t use a parameter and we wanted to be able to take away
four sticks, or five, or more. If we didn’t have parameters, we’d have to
write a separate method for each case, which is clearly a bad idea. Using
parameters in this way leads to a more general useful method and thus is
an example of the generality principle.
Now let’s consider how we would create a OneRowNim instance and
use the new method in the main() method or in a different class. If we
want to have an instance of OneRowNim object to remove 3 sticks on the
first move by using the takeSticks() method, we need to pass the int
value 3 to the method. In order to effect this action, we would use the
following statements:
Because the definition of takeSticks() includes a single int parameter, we must supply a single int value (such as 3), when we invoke it.
When the method is invoked, its formal parameter (num) will be set to the
value we supply (3). The value we supply does not have to be a literal
int value. We can supply any expression or variable that evaluates to an
int value. For example:
In this case, the value being passed to takeSticks() is 2, the value that val has at the time the method call is made.
It would be an error to try to pass a value that was not a int to takeSticks(). For example, each of the following invocations of takeSticks() results in a syntax error:
As you recall from Chapter 0, the value that is passed to a method when
it is invoked is called an argument. Even though the terms argument and parameter are sometimes used interchangeably, it will be useful to observe a distinction. We will use the term parameter to refer to the formal
parameter—the variable used to pass data to a method—that occurs in the
method definition. We use the term argument to refer to the actual value
that is supplied when the method is invoked.
The distinction between parameter and argument is related to the difference between defining a method and invoking a method. Defining a method
is a matter of writing a method definition, such as
This definition defines a method that takes a single String parameter, and simply prints the value of its parameter. On the other hand, invoking
a method is a matter of writing a method call statement, such as
This statement calls the printStr() method and passes it the string
“HelloWorld”. This notation assumes that the call to the instance method
printStr() is made within the body of another instance method of the
same class.
Passing an int value to a OneRowNim method.
To get a clearer picture of the interaction that takes place when we invoke takeSticks() and pass it an int value, let’s write a main() method to test our new version of OneRowNim.
Our first version of main() is shown in Figure 3.2. We will use it to trace how the parameter of takeSticks() interacts with the instance variables nSticks and player. The statements in the main() program simply create an instance of OneRowNim that is referenced by game and invoke the setSticks() method with an argument of 3.
Figure 3.2: A main() method to test the takeSticks() method.
Figure 3.3: Figure 3.3: Tracing the state
of game (a) Just before calling
takeSticks(3). (b) Just
before executing the body of
takeSticks(3). (c) Just
after executing the body of
takeSticks(3). (d) After flow
of control leaves takeSticks().
Executing the first two statements of main() creates the instance game of OneRowNim. Figure 3.3(a) shows the initial state of game. When the takeSticks(3) method call is made, a parameter (which is a local variable) named num is created and the value 3 is stored in it. The state of the instance variables and the parameter are shown in (b). Then the body of takeSticks() is executed. The new state of game is shown in (c). After the flow of control leaves the body of takeSticks() and returns to main(), the memory location which stored the value of the parameter num is released for other uses. The state of game at this point is shown in (d). Notice that the value of nSticks has been reduced to 4.
Passing keyboard input to takeSticks()
To complete this section, let’s modify our main() method in Figure 3.2
so that it prompts the user to input an integer from the keyboard and
then uses a Scanner object, introduced in the previous chapter, to read
the integer. That integer will then be used as the argument in a call to
takeSticks(). These modifications have been incorporated into the
revised version of the main() method shown in Figure 3.4. If we now
run this program the following output will be generated in the console
window before waiting for keyboard input:
If the user then inputs a 2 from the keyboard, that input will be read
and the takeSticks() method will remove 2 sticks. The output in the
console window will now look like:
SELF-STUDY EXERCISES
EXERCISE 3.1 Explain the difference between a method declaration and a method invocation.
EXERCISE 3.2 Explain the difference between a formal parameter and an argument.
EXERCISE 3.3 Modify the OneRowNim class of Figure 3.4 by adding two instance variables of type String to store names of the two players. Choose names for the instance variables that would be appropriate for storing names for player one and player two. Write a method named setNames() with two string parameters which assigns the first parameter to the instance variable that you created for the name of player one. The second parameter should be assigned to the other new instance variable.
EXERCISE 3.4 Write a statement that calls the setName() method of
the previous exercise and sets the name of player one of game to “Xena”
and sets the name of player two to “Yogi”.
3.3 Constructor Methods
In the previous section, we looked at several examples of mutator methods that change the values of private instance variables of an object. It is possible to define mutator methods to set the initial values of instance
variables after an object is created, but initial values can also be set by
constructors.
As you recall from Chapter 0, a constructor method is used to create
Constructor names for an instance (or object) of a class and to assign initial values to instance
variables. A constructor declaration looks just like a method definition
except it must have the same name as the class, and it cannot declare a
result type. Unlike the class level variables and methods of a class, constructors are not considered members of the class. Therefore, they are not
inherited by a class’s subclasses. Access to constructors is governed by the
access modifiers public and private. Here is a simple constructor for
our OneRowNim class:
This constructor merely sets the initial values of the instance variables,
nSticks and player. In our current version of OneRowNim these variables are given initial values by using initializer statements when they are
first declared:
So we now have two ways to initialize a class’s instance variables. In the
OneRowNim class it doesn’t really matter which way we do it. However,
the constructor provides more flexibility because it allows the state of the
object to be initialized at runtime. Of course, it would be somewhat redundant (though permissible) to initialize the same variable twice, once when
it is declared and again in the constructor, so we should choose one or
the other way to do this. For now, let’s stick with initializing the instance
variables when they are declared.
A constructor cannot return a value and, therefore, its declaration cannot
include a return type. Because they cannot return values, constructors
cannot be invoked by a regular method invocation. Instead, constructors
are invoked as part of an instance creation expression when instance objects
are created. An instance creation expression involves the keyword new
followed by the constructor invocation:
Note here that we have combined variable declaration and instantiation
into a single statement, whereas in some previous examples we used separate declaration and instantiation statements. Either way is acceptable.
Constructors should be used to perform the necessary initialization operations during object creation. In the case of a OneRowNim object, what
initializations could be performed? One initialization that would seem State initialization
appropriate is to initialize the initial number of sticks to a number specified. In order to do this, we would need a constructor with a single int
parameter:
Now that we have this constructor we can use it when we create instances
of OneRowNim:
The effect of these statements is the same as if we had used the
setSticks() method that was discussed briefly on page 103. The difference is that we can now set the number of sticks when we create the
object.
Should we keep the preceding constructor, or keep the setSticks()
method or keep both in our class definition? The constructor can only
be invoked as part of a new statement when the object is created but the
setSticks() method could be called anytime we want. In many cases,
having redundant methods for doing the same task in different ways
would be an asset, because it allows for more flexibility in how the class
could be used. However, for a game like One Row Nim, a major concern
is that the two instance variables get changed only in a manner consistent
with the rules for One Row Nim. The best way to guarantee this is to have
takeSticks() as the only method that changes the instance variables
nSticks and player. The only time that it should be possible to set the
number of sticks for a game is when a constructor is used to create a new
instance of OneRowNim.
SELF-STUDY EXERCISES
EXERCISE 3.5 What’s wrong with the following constructor definition?
EXERCISE 3.6 Change the OneRowNim(int sticks) constructor so
that it sets the number of sticks and also have it also set player two as the
player who takes the first turn.
Default Constructors
As we noted in Chapter 2, Java automatically provides a default constructor
when a class does not contain a constructor.
The default constructor’s role is simply to create an instance (an object) of
that class. It takes no parameters. In terms of what it does, the default
constructor for OneRowNim would be equivalent to a public constructor
method with an empty body:
This explains why the following statement was valid when a class definition of OneRowNim contained no explicit definition of a constructor:
The first is an explicit representation of the default constructor. The second is the constructor we defined earlier to initialize the number of sticks
in a OneRowNim object. Having multiple constructors lends flexibility to
the design of a class. In this case, the first constructor merely accepts
OneRowNim’s default initial state. The second enables the user to initialize
the number of sticks to something other than the default value.
In Java, as in some other programming languages, when two different
methods have the same name, it is known as method overloading. In overloading
this case, OneRowNim is used as the name for two distinct constructor
methods. What distinguishes one constructor from another is its signature, which consists of its name together with the number and types of
formal parameters it takes. Thus, our OneRowNim constructors have the
following distinct signatures:
Both have the same name, but the first takes no parameters, whereas the
second takes a single int parameter.
The same point applies to methods in general. Two methods can have Methods are known by their
the same name as long as they have distinct signatures. A method signature consists of its name, and the number, types, and order of its formal
parameters. A class may not contain two methods with the same signature, but it may contain several methods with the same name, provided
each has a distinct signature.
There is no limit to the amount of overloading that can be done in designing constructors and methods. The only restriction is that each method
has a distinct signature. For example, suppose in addition to the two
constructors we have already defined, we want a constructor that would
let us set both the number of sticks and the player who starts first. The
following constructor will do what we want:
When calling this constructor, we would have to take care to pass the number of sticks as the value of the first argument and either 1 or 2 as the value
of the second argument:
If we mistakenly reversed 14 and 2 in the first of these statements, we
would end up with a OneRowNim game that starts with 2 sticks and has
player 14 as the player with the first move.
We have now defined three constructor methods for the OneRowNim
class. Each constructor has the name OneRowNim, but each has a distinct
signature:
Constructor Invocation
A constructor method is invoked only as part of a new expression when an instance object is first created. Each of these is a valid invocation of a
ate an object OneRowNim constructor:
The following constructor invocations are invalid because there are no
matching constructor definitions:
In the first case, there is no constructor method that takes a String parameter, so there’s no matching constructor. In the second case, there is no
constructor that takes three int arguments. In both cases, the Java compiler would complain that there is no constructor method that matches the
invocation.
3.4 Retrieving Information from an Object
The modifications we’ve made to the OneRowNim class allow us to set the
instance variables of a OneRowNim object with a constructor, but there is
no way for us to retrieve their values other than to use the report()
method to write a message to the console. We will want to be able to
ask a OneRowNim object to provide us with the number of sticks remaining and who plays next when we develop a graphical user interface for
OneRowNim in the next chapter. We declared nSticks and player as
private variables, so we cannot access them directly. Therefore, we will need accessor methods to get the values of each of the instance variables.
Consider the following method definitions:
Recall that a method’s ResultType is specified just in front of the MethodName. We want the two methods to return int values that represent
OneRowNim’s instance variables. Therefore, their result types are both
declared int.
Before we discuss how the value that is returned by a method is used
when the method is called, let’s consider one more method definition.
Many methods that return a value do a computation rather than simply
returning the value of an instance variable. For example, suppose we wish
to define a method for the OneRowNim class that will notify the user of an
instance of the class whether the game is over. Thus we want a method
that, when called, returns a true or false depending on whether or
not all the sticks have been taken. gameOver() is a descriptive name
of such a method and the method should have a boolean result type.
This method should return true when the instance variable nSticks no
longer contains a positive int value. Thus we can define:
The expression (nSticks <= 0) evaluates to a false value if nSticks
stores a positive value and it evaluates to true otherwise. Thus the value
returned is precisely what is required.
Invoking a Method That Returns a Value
When we invoke a method that returns a value, the invocation expression takes on, or is replaced by, the value that is returned. For example, if we
execute the statements
the expression game1.getSticks() will take on the value 11 after the
getSticks() method is finished executing. At that point, the second
statement above can be treated as if expression game1.getSticks() is replaced with the value 11, which is assigned to sticksLeft. In effect,
the second statement is equivalent to the following statement:
We can use a value returned by a method call the same way we use a
literal value of the same type. It can be assigned to variables, be part of
a numerical expression, or be an argument of another method. All of the
following statements involve valid calls of methods that return values:
In each statement, the method call can be replaced with the value it returns. Notice that the last statement is valid but does nothing useful. In
Java and some other languages like C and C++, methods that return a
value can simply be called without making use of the value returned. This
may be useful to do if the method changes the state of instance variables
or sends a message to another object or an output device. The method
getSticks() does nothing but return the value of nSticks, so simply
calling the method accomplishes nothing.
An Expanded OneRowNim Class
Let’s add the new methods that return values to our OneRowNim class. We
might note that the report() method from the previous chapter displays
the values of nSticks and player in the console window which now
could be done by using the methods getSticks() and getPlayer()
with System.out.println(). However, calling report() is an easy
way to display the values of both instance variables but it cannot provide
Redundancy and flexibility access to either variable as an int value. So let’s keep all three methods
in our class definition. The inconvenience of a small amount of redundancy is outweighed by the added flexibility of being able to call all three
methods.
Figure 3.5 provides a UML class diagram of the expanded OneRowNim
class.
Let’s also consider a new main() method to test the new methods of
the class. A very short list of statements that call each of the three new
methods returning values is given in the main() method in Figure 3.6.
The output to the console when this program is run will be:
Note that the constructor sets player to 2, so player stores the value 1
after one turn.
SELF-STUDY EXERCISES
EXERCISE 3.7 What would these segments of Java code display on the
screen?
EXERCISE 3.8 Suppose that an int instance variable named nMoves
is added to the OneRowNim class that counts the number of moves taken
in a One Row Nim game. Write a Java method for the OneRowNim class
to get the value stored in nMoves.
EXERCISE 3.9 Write a method for the OneRowNim class called
playerOneGoesNext() that returns a boolean value. The value returned should be true if and only if player one has the next turn.
3.5 Passing a Value and Passing a Reference
The effect of passing arguments to a method differs depending on whether
you are passing a value of primitive type (such as 5 or true) or a value of
reference type (such as “Hello” or game1). When an argument of primiPassing a primitive value tive type is passed to a method, a copy of the argument is passed to the formal parameter. For example, consider the PrimitiveCall class shown
in Figure 3.7. Note that we have an int variable k, which initially stores
the value 5, and a method myMethod(), which takes an int parameter n.
In this case, when we invoke myMethod(k), k’s value (5) is copied into n
and stored there during the method.
One implication of passing a copy of a primitive value to a method is
that the original value of k in main() cannot be altered from inside the
method. Thus, the output generated by PrimitiveCall would be
Note that in main(), k’s value is printed both before and after
myMethod() is called, but that its value remains unaffected even though
n’s value is changed within the method. This is because myMethod()
contains just a copy of k’s value, not k itself. Any changes to the copy
within myMethod() leave k unaltered (See Fig. 3.8).
In contrast to this, when an argument of a reference type is passed to a
method, a copy of the reference to the object itself is assigned to the parameter. For example, in the case of a String parameter or a OneRowNim
parameter, the method would be given a reference to the object–that is,
the address of the object. The object itself is not passed, because it would
be too inefficient to copy the entire object with all its data and methods.
However, because the object’s reference gives the object’s location in memory, the method will have access to the object and can make changes to the
original object from within the method.
Figure 3.8: Tracing the state
of variables k and n in
PrimitiveCall (a) Just before calling myMethod(k) in
main. (b) Just before executing
the body of myMethod(). (c)
Just after executing the body of
myMethod(). (d) After flow of
control returns to main().
For example, consider the ReferenceCall class (Fig. 3.9). In this
case, myMethod() takes a parameter g of type OneRowNim. Because
a OneRowNim instance is an object, g is a reference variable. So when
myMethod(game) is invoked in main(), a reference to game is passed
to myMethod(). Note that in myMethod(), we use takeSticks(3) to
change the number of sticks of g from 10 to 7 and that this change persists
even after the method returns control to main(). The reason is that during the method’s execution, both game and g refer to the exact same object
(see Fig. 3.10). The output generated by ReferenceCall would be
This illustrates that when passing a reference variable to a method, it is
possible for the method to change the state of the object associated with
Figure 3.10: Tracing the
state of OneRowNim object in
ReferenceCall (a) Just before
calling myMethod(game). (b)
Just before executing the body of
myMethod(). (c) Just after executing the body of myMethod().
(d) After flow of control returns
to main().
the reference variable. In subsequent chapters we will see ways to make
use of this feature of reference parameters.
3.6 Flow of Control: Control Structures
We have been ignoring a couple of problems with the definition of the
OneRowNim class. One problem is that we would describe a One Row
Nim game as two players taking turns until there are no more sticks. An
object using OneRowNim would need a way to repeatedly execute a group
of statements. One command in Java that controls the repetition of a block
of statements is called a while loop. We will consider it later in this section.
A second problem is with the definition of takeSticks():
It is possible to call this method with an argument greater than 3 or less
than 1. The call game.takeSticks(5) will remove 5 sticks even though
the rules of One Row Nim say that you must remove 1, 2, or 3. While one
might assume that the user interface should prevent the user from breaking this rule, it is a far better design if it was dealt with in OneRowNim.
To do this we need a Java structure that executes different statements depending on whether the parameter is greater than 3, less than 1, or between 1 and 3. The Java if-else statement has this capability. A fuller treatment of control structures appears in Chapter 6, but in this section, we will
briefly introduce a couple of simple control structures. This will enable us
to write programs that take more interesting actions.
The Simple If Statement
A selection control structure, allows a program to select between two or more alternative paths of execution. The if statement is the most basic selection control structure in Java. Most programming languages have their own Simple if statement equivalent.
The statement contained in the if statement can be any valid Java statement, including a compound statement. (Recall from Chapter 1 that a compound statement is a set of statements contained within curly braces.) The boolean expression is an
expression that is either true
or false. We have seen examples of boolean expressions that involve
int variables, int values, and the inequality or equality operators. A method call to a method with a boolean result type is another example of a boolean expression. Given this description of if statement syntax, the
following are examples of valid if statements:
For readability, we usually write an if statement with its contained statement indented on the next line:
The following are all examples of syntax errors involving the if statement:
Semantically, the
if statement has the following interpretation: First, the boolean condition is evaluated. If it is true, then the contained statement is executed; if it is false, then the contained statement is not executed. This is shown in Figure 3.11. The
flowchart clearly shows that program flow will take one or the other of the alternative paths coming out of the diamond shaped boolean condition box. The branch through the rectangular statement box will be taken when the boolean condition is
true; otherwise the statement will be skipped.
As another example, consider the definition of a getPlayerString()
method for the OneRowNim class:
The flowchart in Figure 3.12 shows the program flow of the entire
getPlayerString() method. It is important to note that when a return statement is executed in a method, control is returned immediately to the calling method. Thus, if player == 1 is true,
the string “Player One” is returned to the calling method and the
getPlayerString() method exits at this point. If it is false, then
player == 2 should be true (if we have a consistent state) and the string
“Player Two” should be returned and the method exited. Thus, if we have
a consistent state —that is, if player has value 1 or 2—then the third
return statement should never be reached.
Figure 3.12: Flowchart of the
getPlayerString() method
The following example shows the more common case where the stateCompound statement ment contained in an if statement can be a compound statement:
If player == 1 is true, then all four statements in the contained comLocal scope pound statement will be executed. Note here that we are declaring the
local variable, s, in this block. Its scope would extend only to the end of
the block. Note also that when we use a compound statement, the compound statement itself is not followed by a semicolon because it is already
enclosed in braces.
A common programming error is to forget the braces around the compound statement. Merely indenting the statements following the if clause
doesn’t alter the logic of the if statement. For example, the following if
statement still has only one statement in its if clause:
This segment will always print “Two” because the second println() is
not part of the if statement. To include it in the if statement, you must
enclose both println() statements within braces:
The if-else Statement
A second version of the if statement incorporates an else clause into the
structure. This allows us to execute either of two separate statements (simple or compound) as the result of one boolean expression. For example,
the statement
will print “Player One” if player == 1 is true. Otherwise, it will print
“Player Two”.
As in the case of the simple if statement, the keyword if is followed by a parenthesized boolean expression, which is followed by statement1, which
may be either simple or compound. If statement1 is a simple statement,
then it is followed by a semicolon. The else clause follows immediately
after statement1. It begins with the keyword else, which is followed by
statement2, which can also be either a simple or compound statement.
Note that there is no boolean expression following the else keyword.
In an if-else statement, the boolean expression following the keyword if
goes with both the if and else clauses.
Semantically, the if-else statement has the following interpretation: If
the boolean expression is true, execute statement1; otherwise execute statement2. This interpretation is shown in Figure 3.13.
The Nested if/else Multiway Selection Structure
The statements that one inserts in place of statement1 and statement2 in
the if-else statement can be any executable statement, including another
if statement or if-else statement. In other words, it is possible to embed
one or more if-else statements inside another if-else statement, thereby
creating a nested control structure. As with most things, making a control
structure too complex isn’t a good idea, but there is a standard nested ifelse control structure that is very useful. It is known as multiway selection. As shown in Figure 3.14, the multiway structure is used when you
want to select one and only one option from several alternatives.
Suppose we have an int variable num that will contain one of the values 1, 2, or 3 unless there has been an error assigning a value to it. Suppose that we want to write code that will write out the English word for the value in num.
Figure 3.14: Flowchart of a nested
if-else statement.
In the example shown in Figure 3.14 there are three
alternatives plus an error state. Here is the Java code for this example:
Note that the multiway structure has a single entry point and that only one
Multiple alternatives of the four possible alternatives is executed. The code will print exactly
one of the strings.
We will have many occasions to use the if-else structure. Although it does not represent a significant change, we could rewrite our
takeStick() method to make use of the if-else instead of the somewhat
obscure statement:
to change the value of player from 1 to 2 or vice versa:
In some respects this version of takeSticks() involves four lines of
code instead of one but is simpler to understand. The if-statement tests
whether the value of player is 1. If it is, the value is changed to 2. If
the value of player is not 1, then the value must be 2 and so the value is
changed to 1. Both versions of the code will give precisely the same result,
a programmer could choose to write the code either way.
SELF-STUDY EXERCISES
EXERCISE 3.10 Consider the following method with boolean parameter.
Draw a flowchart for the if-else version of the getStatus() method, using the figures in this section as a guide. The if-else structure should be drawn exactly as shown in Figure 3.11. It should have a single entry point that leads directly to the top of a diamond-shaped box that contains a boolean condition. There should be two branches coming out of the condition box. The one going to the right is the true case, and the one going to the left is the false case. Each of these branches should contain one rectangular box, which contains the statements that would be executed in that case. The left and right branches should be connected by a circular symbol that is aligned directly under the diamond box whose conditions it connects. There should be a single exit arrow pointing directly down.
EXERCISE 3.11 Identify the error in the following statements:
EXERCISE 3.12 Suppose we have an int instance variable named player in some class describing a three person game. Write a method named getPlayerName() that returns a String. It should return “Ann”, “Bill”, “Cal”, or “Error” when the value of player is respectively 1, 2, 3, or any other value.
EXERCISE 3.13 How does a parameter for a primitive type differ from
a parameter for a reference type?
The While Structure
A repetition structure is a control structure that repeats a statement or
sequence of statements in a controlled way. Repetition structures are also
referred to as loop structures. Many types of programming tasks require
a repetition structure. Consider some examples.
- You want to add up the squares of the numbers from 1 to 100.
- You want to compute compound interest on an amount of money in a savings account with a fixed interest rate if it is kept there for 30 years.
- A computer security employee wants to try every possible password in order to break into an account of a suspected spy.
- You want to have players input moves for a turn in a game until the game is over. Our OneRowNim is such an example.
Note that in this example, the variable num gets assigned an initial value
of 1 before the while statement. Note also that the boolean expression
num < max in parentheses after while states the condition for which we
wish to continue summing squares. Finally note that the last statement
in the block following the boolean expression adds 1 to num–that is, this
variable is updated at the end of the block.
The while statement is a loop statement in which the loop entry condition occurs before the loop body. It has the following general form:
When the while statement is executed, the loop entry condition is evaluated and if this evaluates to false, execution continues at the statement
immediately after the loop body. If the loop entry condition evaluates to
true, the loop body is executed and then the entry condition is evaluated again. The loop body continues to be executed until the loop entry
condition evaluates to false.
To have a while statement accomplish a task, the variable or variables
in the loop entry condition must be initialized correctly before the while
statement and these variables must be correctly updated at the end of the
loop body. We can refer to the initializer statement followed by a while
statement as a while structure. We can restate the above guidelines as a
design principle:
In pseudocode, the while structure would take the following form:
As its form suggests, the while structure is designed so that on some conditions the loop body will never be executed. Because it tests for the loop
entry condition before the loop body, it is possible that the loop body is
never executed. We might say that it is designed to perform 0 or more
iterations.
For example, if the method call sumSquares(-3) is executed, the loop
body will be skipped, because the loop entry condition num <= max is
false to begin with. No iterations will be performed, and the algorithm
will simply return the value 0.
Note also that in the while statement the bound test is preceded by
initializer statements, and the loop body contains updater statements. The
semantics of the while structure are shown in Figure 3.15.
Figure 3.15: Flowchart of the
while statement and while structure.
SELF-STUDY EXERCISE
EXERCISE 3.14 Modify the definition of the sumSquares() method
to define a method named sumCubes() that sums the cubes of integers
from a minimum value up to a maximum value and returns that sum.
sumCubes() should have two parameters that will store the minimum
and maximum values. Thus the method call sumCubes(2,3) should
return 35 since 2 ⇤ 2 ⇤ 2+3 ⇤ 3 ⇤ 3 = 8+27 =
3.7 Testing an Improved OneRowNim
Let’s use the control structures that we have discussed to improve the definition of the takeSticks() method of OneRowNim. We noted earlier that our current definition allows 4 or more sticks to be removed from nSticks even though
the rules of One Row Nim indicate that a player must take one, two, or three sticks on a turn. We can use if-else statements to make certain that no more than 3 sticks get removed.
What should happen if the method takeSticks() is called with an argument that does not represent a legal number of sticks to remove? In this case, it would probably make sense to remove no sticks at all and to keep the value of player the
same so that the player whose turn it is does not change. In addition, it would be nice if the method could signal that an illegal move has been attempted. This can be accomplished if we redefine
takeSticks() to return a boolean value. Let’s have a return value of true represent the case that a valid number of sticks have been removed and the player to play next has been changed. A return of false will indicate that an
illegal move has been attempted. Making these changes to the takeSticks() method will yield a method definition that looks like:
Notice that the new definition of the takeSticks() method has a boolean return type. Also notice that the if/else multiway structure is used to handle the three cases of the parameter num being less than one, more than three, or a
valid number.
Let us add one more method to the OneRowNim class. Let’s define a method called getWinner() that will return the number of the winning player if the game is over. Recall that the player who takes the last stick loses, so after that
last play, the player whose turn it is to play next is the winner. However, we should be concerned about what value to return if the game is not over when the method is called. A common strategy is to have a method return a special value to indicate
that it is in a state in which it cannot return the value requested. Returning a 0 value is a good way to indicate that the game is not over so a winner cannot be identified. With this information, the if/else statement can be used in the definition
of getWinner().
We now have the final version (for this chapter) of the OneRowNim
class whose implementation is given in Figure 3.16. We have turned a very simple class into one that contains quite a few elements. Compared to our first version (in Chapter 1), this Chapter’s version of OneRowNim
presents an interface (to other objects) that is easy and convenient to use. The constructor methods with parameters provide an easy way to create a OneRowNim instance with any number of sticks. The use of private instance variables
and a single, carefully designed mutator method, takeSticks(), prevents other objects from tampering with the state of a OneRowNim object’s state. The other methods provide a flexible way to find out the state of a OneRowNim object.
The complete implementation of this OneRowNim is shown in Figure 3.16.
Let’s use a while statement to test the new methods of the class. A
pseudocode description of how a game is played might look like:
Translating this pseudocode into Java code in a main() method in a separate class gives us the class shown in Figure 3.17. We will use the Scanner
class introduced in the previous chapter to get moves from the keyboard
for both players. Before each move game.report() describes the state
of the game before the user is prompted to input a move for one of the
players. A reader interested in seeing the lengthy output to the console
when the TestOneRowNim class is run is encouraged to actually run the
program.
Note that the return value of the takeSticks() method is ignored
in this test program. We will make use of the return value in test programs in the next chapter when better user interfaces are developed for
OneRowNim. Note, however, that taken together, the public methods for OneRowNim provide other objects with an interface that they can use to
communicate with individual OneRowNim objects.
To reiterate a point made at the outset, object-oriented programming is a
process of constructing objects that will interact with each other. Object-oriented programs must ensure that the objects themselves are well designed in terms of their ability to carry out their designated functions.
Good design in this sense requires careful selection of instance variables
and careful design of methods to ensure that the object can carry out its
assigned tasks. However, equal care must be taken to ensure that the
interactions that take place among objects are constrained in ways that
make sense for that particular program. This aspect of designing objects comes into play in designing the methods—constructor, accessor, and
mutator—that make up the object’s interface.
Special Topic: Intelligent Agents
Wouldn’t it be nice if we had a computer program that could schedule
appointments for us, remind us of meetings and commitments, find information for us on the WWW, and manage our e-mail messages for us?
Wouldn’t it be nice to have a computerized personal assistant?
Actually, such programs are called intelligent agents, which are programs that are capable of acting autonomously to carry out certain tasks.
Intelligent agent technology is becoming an important research area in
computer science. Most agent programs incorporate some kind of machine learning capability, so that their performance improves over time.
As a typical agent activity, suppose I was able to tell my intelligent
agent to buy me a copy of a certain book that I just heard about. Given a
command like “buy me a copy of X,” the agent would perform a search
of online book sellers and come up with the best deal. Once it had found
the best buy, the agent would communicate with a computer-based agent
representing the book seller. My agent would make the order and pay
for it (assuming I gave it authority to do so), and the book seller’s agent
would process the order.
As far-fetched as the capability may now seem, this is the direction
that research in this area is headed. Researchers are developing agent
languages and describing protocols that agents can use to exchange information in a reliable and trustworthy environment. Obviously, you
wouldn’t want your agent to give your money to a fraudulent book seller,
so there are significant problems to solve in this area that go well beyond
the problem of simply exchanging information between two agents.
The best way to learn more about this research area is to do a Web
search using the search string “Intelligent Agent.” There are numerous research groups and companies that provide online descriptions and demos
of their products.
3.8 From the Java Library java.lang.Object
The most general class in Java’s class hierarchy is the java.lang.Object
class. It is the superclass of all classes that occur in Java programs. By default, it is the direct superclass of any class that does not explicitly specify
a pedigree in its class definition.
All subclasses of Object inherit the public and protected methods
contained in Object, so all such methods can be thought of as belonging
to the subclasses. This means that all classes inherit the methods of the
Object class, because every class is a subclass of it. In this section, let’s
look briefly at how we can use an inherited method and also at how we
can override it–that is, redefine the method–if it doesn’t exactly suit our
purposes.
One of the most useful methods in the Object class is the
toString() method:
The toString() method returns a String representation of its object.
For example, o1.toString() will return a String that in some sense
describes o1.
Because OneRowNim is a subclass of Object, it inherits the
toString() method. To illustrate the default behavior of toString(),
let’s use it with a OneRowNim instance:
This code segment creates two OneRowNim instances, one named g1 and
the other named g2. The inherited toString() method is then invoked
on each OneRowNim instance, which produces the following output:
What this experiment shows is that the default definition of toString()
returns some kind of internal representation of its object. It looks as if it
returns the name of the object’s class concatenated with its memory address. This may be useful for some applications. But for most objects
we will want to override the default definition to make the toString()
method return a string that is more appropriate for OneRowNim.
What String should the g1.toString() method return? Let’s have
it return a String that reports the OneRowNim instances’s current state,
which are the values stored in the two instance variables. To override
a method, you simply define a method with the same signature in the subclass. If you call toString() with an instance of the subclass, its
version of the method will be used. In this way, the subclass method overrides the superclass version. Thus, OneRowNim.toString() will have
the following signature:
Let us describe the state of a oneRowNim instance very briefly in the string
returned by the toString() method:
If we add the toString() method to the OneRowNim class and then run
the program shown in Figure 3.18, we get the following output:
Figure 3.18: An application to test the overridden toString() method.
While this new method may not play an important role in the OneRowNim
class, it does provide a very brief, understandable description of the state
of the object. This is the reason that the toString() method was included in the Object class.
3.9 Object-Oriented Design: Inheritance and Polymorphism
This use of Object’s toString() method provides our first look at
Java’s inheritance mechanism and how it promotes the generality and
extensibility of the object-oriented approach. As a subclass of Object,
our OneRowNim class automatically inherits toString() and any other
public or protected methods defined in Object. We can simply use
these methods as is, insofar as they are useful to us. As we saw in this
case, the default version of toString() wasn’t very useful. In that case, we can override the method by defining a method in our class with the
exact same method signature. The new version of toString() can be
customized to do exactly what is most appropriate for the subclass.
One of the great benefits of the object-oriented approach is the ability
to define a task, such as toString(), at a very high level in the class
hierarchy and let the inheritance mechanism spread that task throughout the rest of the hierarchy. Because toString() is defined in Object,
you can invoke this method for any Java object. Moreover, if you override toString() in the classes you define, you will be contributing to its
usefulness. Two important lessons from this example are
Obviously there is much more that needs to be explained about Java’s
inheritance mechanism. Therefore, we will be revisiting this topic on
numerous occasions in subsequent chapters.
Another important concept of object-oriented design is polymorphism.
The toString() method is an example of a polymorphic method. The
term polymorphism is from the Greek terms poly, which means “many,”
and morph, which means “form.” The toString() method is polymorphic because it has different behavior when invoked on different objects.
For example, suppose we design a class, Student, as a subclass of
Object and define its toString() method to return the student ID
number. Given this design, then obj.toString() will return a student
ID if obj is an instance of Student, but if it is an instance of OneRowNim,
it will return the description of its state that we defined above. The
following code segment illustrates this point:
In this case, the variable obj is used to refer to a Student and then to a OneRowNim instance. This is okay because both classes are subclasses of Object. When toString() is invoked on obj, Java will figure out what subclass of Object the instance belongs to and invoke the appropriate toString() method.
3.10 Drawing Lines and Defining Graphical Methods (Optional)
We used a Graphics object in the previous chapter to draw rectangles and ovals in a JFrame window. The Graphics class also possesses a method for drawing a line segment. Problems involving drawing pictures in an JFrame window using a series
of line segments can be a source of examples of defining useful methods and also of making good use of loops.
The Graphics class has a public instance method with the header:
The method call g.drawLine(x1, y1, x2, y2) draws a line from the point (x1, y1) to (x2, y2) where (x, y) refers to a point that is x pixels from the left edge of the area that g is drawing in and y pixels from the top edge. Thus g.drawLine(10, 10, 10, 60) draws a vertical line segment that is 50 pixels long and is 10 pixels from the left edge of the drawing area, that is, a line segment from the point (10,10) to the point (10,60).
Consider the problem of creating a Swing program with a method called drawSticks() to draw any specified number of vertical line segments. This method might be useful for an graphical user interface to the OneRowNim game to draw the number
of sticks at a given point in a game. Suppose that this method must have an int parameter to specify the number of vertical lines to draw and two int parameters to specify the location of the top endpoint of the left most line segment.
The
drawSticks() method will need to use a Graphics object connected to the JFrame window for drawing the line segment. The only such
Graphics object available is the parameter in the paint() method of the Canvas. Thus the method must have a Graphics parameter and it will be called in the paint() method using the Graphics object there as an argument.
Thus the header of the method should look like:
The length of the line segments and and the distance between them are not specified by parameters so we need to choose some fixed values for these quantities. Let us assume that the line segments are 10 pixels apart and 50 pixels long. We now have enough
information to complete the definition of an applet to solve this problem. Such a class definition is reproduced in Figure 3.19.
Note that the body of drawSticks() uses a while-loop to draw the lines and declares and initializes a local variable to zero to use for counting the number of lines drawn. The statement g.drawLine(x, y, x, y +
50); draws a vertical line which is 50 pixels long. Increasing the value of x by 10 each time through the loop moves the next line 10 pixels to the right.
The first call to drawSticks() in the paint() method draws 12 lines with (25,25) the top point of the left-most line. The second call to
Figure 3.19: A Swing Class with a method for drawing a set of sticks.
drawSticks() will draw 7 cyan sticks 100 pixels lower. Note that changing the color of g before passing it as an argument to drawSticks() changes the drawing color.
An image of the DrawSticksCanvas as it appears in a window is shown in Figure 3.20.
As we have seen in this example, defining methods with parameters to draw an object makes the code reusable and makes it possible to draw a complex scene by calling a collection of simpler methods. It is a typical use of the divide-and-conquer principle. The while-loop can be useful in drawing almost any geometrically symmetric object.
Chapter Summary
- accessor method
- class scope
- formal parameter
- if statement
- if/else statement
- inherit
- local scope
- loop structure
- method overloading
- method signature
- mutator method
- multiway selection
- override
- polymorphism
- repetition structure
- scope
- selection
- side effect
- while statement
- while structure
Summary of Important Points
- A formal parameter is a variable in a method declaration. It always consists of a type followed by a variable identifier. An argument is a value that is passed to a method via a formal parameter when the method is invoked. A method’s parameters constrain the type of information that can be passed to a method.
- When an argument of primitive type is passed to a method, it cannot be modified within the method. When an argument of reference type is passed to a method, the object it refers to can be modified within the method.
- Except for void methods, a method invocation or method call is an expression which has a value of a certain type. For example, nim.getSticks() returns a int value.
- The signature of a method consists of its name, and the number, types, and order of its formal parameters. A class may not contain more than one method with the same signature.
- A constructor is a method that is invoked when an object is created. If a class does not contain a constructor method, the Java compiler supplies a default constructor.
- Restricting access to certain portions of a class is a form of information hiding. Generally, instance variables are hidden by declaring them private. The class’s public methods make up its interface.
- The if statement executes a statement only if its boolean condition is true. The if-else statement executes one or the other of its statements depending on the value of its boolean condition. Multiway selection allows one and only one of several choices to be selected depending on the value of its boolean condition.
- The while statement is used for coding loop structures that repeatedly execute a block of code while a boolean condition is satisfied.
SOLUTIONS TO SELF-STUDY EXERCISES
SOLUTION 3.1 A method declaration defines the method by specifying its name, qualifiers, return type, formal parameters, and its algorithm, thereby associating a name with a segment of executable code. A method invocation calls or uses a defined method.
SOLUTION 3.2 A formal parameter is a variable in the method declaration, whose
purpose is to store a value while the method is running. An argument is a value
that is passed to a method in place of a formal parameter.