Methods: Communicating with Objects

Site: Saylor Academy
Course: CS101: Introduction to Computer Science I
Book: Methods: Communicating with Objects
Printed by: Guest user
Date: Friday, January 27, 2023, 10:35 PM

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.

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
Creative Commons License 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.

Java effective design

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 method change the values of the instance variables nSticks and player. All three methods have very similar bodies. The definition of the takeOne() is:

Annotation 2020-03-23 203058

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 it is passed while the method is executing.

Annotation 2020-03-23 203248

Consider the following definition for a takeSticks() method:

Annotation 2020-03-23 203337

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 in which define a set method to allow the starting number of sticks to be set for an instance of OneRowNim. For this, we could define:

Annotation 2020-03-23 203518

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:

Annotation 2020-03-23 203722

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.

Annotation 2020-03-23 203910

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:

Annotation 2020-03-23 204111

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.

Annotation 2020-03-23 204154

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

Annotation 2020-03-23 204321

Annotation 2020-03-23 204401

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.

Annotation 2020-03-23 204621

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:

Annotation 2020-03-23 204724

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:

Annotation 2020-03-23 204917

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:

Annotation 2020-03-23 205103

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.

Annotation 2020-03-23 205247

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

Annotation 2020-03-23 205354

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

Annotation 2020-03-23 205458

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.

Annotation 2020-03-23 205752

Figure 3.2: A main() method to test the takeSticks() method.

Annotation 2020-03-23 212412Figure 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().

To get a clearer understanding of how a parameter works, it will be instructive to trace through the code in main(). Figure 3.3 displays how the states of the instance variables of game and the parameter of takeSticks() interact.

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:

Annotation 2020-03-23 212959

Annotation 2020-03-23 213055

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:

Annotation 2020-03-23 213155


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

Annotation 2020-03-23 213628

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:

Annotation 2020-03-23 213900

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.

Annotation 2020-03-23 214006

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:

Annotation 2020-03-23 214136

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.

Annotation 2020-03-23 214315

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:

Annotation 2020-03-23 214416

Now that we have this constructor we can use it when we create instances of OneRowNim:

Annotation 2020-03-23 214511

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?

Annotation 2020-03-23 214826

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.

Annotation 2020-03-23 215141

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:

Annotation 2020-03-23 215239

This explains why the following statement was valid when a class definition of OneRowNim contained no explicit definition of a constructor:

Annotation 2020-03-23 215339

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:

Annotation 2020-03-23 215533

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.

Annotation 2020-03-23 215707

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 have 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:

Annotation 2020-03-23 215905

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:

Annotation 2020-03-23 220002

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:

Annotation 2020-03-23 220118

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:

Annotation 2020-03-23 220317

The following constructor invocations are invalid because there are no matching constructor definitions:

Annotation 2020-03-23 220411

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.

Annotation 2020-03-23 220516

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:

Annotation 2020-03-23 220652

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:

Annotation 2020-03-23 220902

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

Annotation 2020-03-23 221106

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:

Annotation 2020-03-23 221250

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:

Annotation 2020-03-23 221354

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.

Annotation 2020-03-23 221616

Figure 3.5 provides a UML class diagram of the expanded OneRowNim class.

Annotation 2020-03-23 221732

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.

Annotation 2020-03-23 221856

The output to the console when this program is run will be:

Annotation 2020-03-23 222047

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?

Annotation 2020-03-23 222307

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

Annotation 2020-03-24 131729

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

Annotation 2020-03-24 131910

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

Annotation 2020-03-24 132030

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.

Annotation 2020-03-24 132150

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

Annotation 2020-03-24 132339

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

Annotation 2020-03-24 132533

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

Annotation 2020-03-24 132624

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.

Annotation 2020-03-24 132918

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():

Annotation 2020-03-24 133240

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 its Simple if statement equivalent.

Annotation 2020-03-24 133441

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:

Annotation 2020-03-24 133602

For readability, we usually write an if statement with its contained statement indented on the next line:

Annotation 2020-03-24 133651

The following are all examples of syntax errors involving the if statement:

Annotation 2020-03-24 133746

Annotation 2020-03-24 133927Semantically, 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:

Annotation 2020-03-24 134127

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.

Annotation 2020-03-24 134250

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:

Annotation 2020-03-24 134501

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:

Annotation 2020-03-24 134626

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:

Annotation 2020-03-24 134715

Annotation 2020-03-24 134753

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

Annotation 2020-03-24 135112

will print “Player One” if player == 1 is true. Otherwise, it will print “Player Two”.

Annotation 2020-03-24 135200

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 Structur

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. 

Annotation 2020-03-24 135721

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:

Annotation 2020-03-24 135856

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:

Annotation 2020-03-24 140105

to change the value of player from 1 to 2 or vice versa:

Annotation 2020-03-24 140206

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.

Annotation 2020-03-24 140349

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:

Annotation 2020-03-24 140600

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.
We will study several different repetition structures of Java in depth in Chapter 6. We will briefly consider the while statement here so as to be able to define methods that are more powerful and more interesting. Let us write a method that solves a slight generalization of the first problem above. We will use the while statement to sum the squares of integers from 1 to a number specified as a parameter of the method. Thus, the method call sumSquares(3) should return the value 14 since 1 ⇤ 1+2 ⇤ 2+3 ⇤ 3 = 1+4+9 = 14.

Annotation 2020-03-24 140907

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:

Annotation 2020-03-24 141029

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:

Annotation 2020-03-24 141246

In pseudocode, the while structure would take the following form:

Annotation 2020-03-24 141413

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.

Annotation 2020-03-24 141719

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:

Annotation 2020-03-24 142202

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

Annotation 2020-03-24 142352

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.

Annotation 2020-03-24 142554Annotation 2020-03-24 142717

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:

Annotation 2020-03-24 143059

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

Annotation 2020-03-24 143246

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.

Annotation 2020-03-24 143418

To reiterate a point made at the outset, object-oriented programming is a process of constructing objects that will interact with each other. Objectoriented 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:

Annotation 2020-03-24 143834

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:

Annotation 2020-03-24 144022

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:

Annotation 2020-03-24 144125

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:

Annotation 2020-03-24 144320

Let us describe the state of a oneRowNim instance very briefly in the string returned by the toString() method:

Annotation 2020-03-24 144500

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:

Annotation 2020-03-24 144610

Annotation 2020-03-24 144654

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

Annotation 2020-03-24 145008

Annotation 2020-03-24 145040

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 a the description of its state that we defined above. The following code segment illustrates this point:

Annotation 2020-03-24 145313

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:

Annotation 2020-03-24 145622

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 an 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:

Annotation 2020-03-24 145824

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

Annotation 2020-03-24 153126

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.

Annotation 2020-03-24 153324

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.

Annotation 2020-03-24 154043

Annotation 2020-03-24 153944

Annotation 2020-03-24 154139

Annotation 2020-03-24 154308

Annotation 2020-03-24 154436