3.2. Passing Information to an ObjectOne 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. Effective Design: Accessor and Mutator 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 preceding 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: public void takeOne() { nSticks = nSticks - 1; player = 3 - player; } 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 a 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.
Formal parameter Java Language Rule: Formal Parameter
Consider the following definition for a takeSticks() method: public void takeSticks(int num) { nSticks = nSticks - num; player = 3 - player; } Note 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 either 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 that defines a set method to allow the starting number of sticks to be set for an instance of OneRowNim. For this, we could define: public void setSticks(int sticks) { nSticks = sticks; } // setSticks() 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 can set the values of an object's instance variables. If a method uses more than one parameter, separate the individual parameter declarations in the method header with commas. 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 as: public void setGame(int sticks, int starter) { nSticks = sticks; player = starter; } // setGame() The Scope of Parameters, Variables, and MethodsThe bodies of the mutator methods in the preceding section make use of both instance variables and parameters. There is an important 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.
Scope A parameter's scope is limited to the body of the method in which it is declared. Variables declared in the body of a method have scope that 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 declared in the parameter list of a method's header. They 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 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.
Local scope Java Language Rule: Scope
By contrast, instance variables, class variables, and all methods have scope that extends throughout the entire class. This is known as class scope. They can be used in the body of any method and in 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 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 defined in Chapter 2 is an example of such a class method. In that case, we tested the instance methods of OneRowNim by creating an instance of OneRowNim and using it to call its instance methods: OneRowNim game = new OneRowNim(); // Create instance game.report(); // Call an instance method
Class scope 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. Java Language Rule: Scope
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().
Simple vs. qualified names Java Language Rule: Qualified Names
Debugging Tip: Scope Error
3.2.1. Arguments and ParametersThe new class definition for OneRowNim is given in Figure 3.1. 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 Chapter 2, 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 did not use a parameter and we wanted to take away four sticks, or five, or more. If we did not have parameters, we would have to write a separate method for each case, which is clearly a bad idea. Using parameters in this way leads to a more generally useful method and thus is an example of the generality principle. Figure 3.1. The OneRowNim class definition with takeSticks() method.
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 three 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: OneRowNim game = new OneRowNim(); game.takeSticks(3); 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: int val = 7 - 5; game.takeSticks(val); 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 is not a int to takeSticks(). For example, each of the following invocations of takeSticks() results in a syntax error: game.takeSticks(); // no argument is supplied game.takeSticks("3"); // "3" is a String, not an int game.takeSticks(int); // int not is an int value As you will recall from Chapter 0, the value 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 parameter to refer to the formal parameter in the method definitionthe variable used to pass data to a method. We use argument to refer to the actual value supplied when the method is invoked.
Parameter vs. argument Debugging Tip: Type Error
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
Defining vs. calling a method public void printStr(String s) { System.out.println(s); } This definition defines a method that takes a single String parameter, s, 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
Invoking a method printStr("HelloWorld"); 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. 3.2.2. Passing an int Value to a OneRowNim MethodTo 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.
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. 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.2(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). Note that the value of nSticks has been reduced to 4. 3.2.3. 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 Chapter 2, 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: Number of sticks left: 7 Next turn by player 1 Input 1, 2, or 3 and hit enter: Figure 3.4. A main() method with keyboard input for OneRowNim.
If the user then inputs a 2 from the keyboard, that input will be read and the takeSticks() method will remove two sticks. The output in the console window will now look like: Number of sticks left: 7 Next turn by player 1 Input 1, 2, or 3 and hit enter:2 Number of sticks left: 5 Next turn by player 2 Self-Study Exercises
|