Chapter 7

Adding Some Methods to Your Madness

IN THIS CHAPTER

check Introducing static methods

check Seeing some good reasons to use methods in your programs

check Creating methods that return values

check Creating methods that accept parameters

In Java, a method is a block of statements that has a name and can be executed by calling (also called invoking) it from some other place in your program. You may not realize it, but you’re already very experienced in using methods. To print text to the console, for example, you use the println or print method. To get an integer from the user, you use the nextInt method. To compare string values, you use the equals or equalsIgnore Case method. Finally, the granddaddy of all methods — main — contains the statements that are executed when you run your program.

All the methods you’ve used so far (with the exception of main ) have been defined by the Java API and belong to a particular Java class. The nextInt method belongs to the Scanner class, for example, and the equalsIgnoreCase method belongs to the String class. By contrast, the main method belongs to the class defined by your application.

In this chapter, you find out how to create additional methods that are part of your application’s class. Then you can call these methods from your main method. As you’ll see, this technique turns out to be very useful for all but the shortest Java programs.

The Joy of Methods

The use of methods can dramatically improve the quality of your programming life. Suppose that the problem your program is supposed to solve is complicated, and you need at least 1,000 Java statements to get ’er done. You could put all those 1,000 statements in the main method, but it would go on for pages and pages. It’s better to break your program into a few well-defined sections of code and place each of those sections in a separate method. Then your main method can simply call the other methods in the right sequence.

Or suppose that your program needs to perform some calculation, such as how long to let the main rockets burn to make a midcourse correction on a moon flight, and the program needs to perform this calculation in several places. Without methods, you’d have to duplicate the statements that do this calculation. That approach is not only error-prone, but also makes your programs more difficult to test and debug. But if you put the calculation in a method, you can simply call the method whenever you need to perform the calculation. Thus methods help you cut down on repetitive code.

Another good use for methods is to simplify the structure of your code that uses long loops. Suppose you have a while loop that has 500 statements in its body. That structure makes it pretty hard to track down the brace that marks the end of the body. By the time you find it, you probably will have forgotten what the while loop does. You can simplify this while loop by placing the code from its body in a separate method. Then all the while loop has to do is call the new method.

technicalstuff At this point, the object-oriented programming zealots in the audience are starting to boo and hiss. A few of them have already left the auditorium. They’re upset because I’m describing methods in traditional procedural-programming terms instead of modern object-oriented programming terms.

Well, phooey. They’re right, but so what? I get to the object-oriented uses for methods in Book 3 . There, you find out that methods have a far greater purpose than simply breaking a long main method into smaller pieces. Even so, some of the most object-oriented programs I know use methods just to prevent repetitive code or to slice a large method into a couple of smaller ones. So there.

The Basics of Making Methods

All methods — including the main method — must begin with a method declaration. Here’s the basic form of a method declaration, at least for the types of methods I talk about in this chapter:

public static return-type method-name (parameter-list)
{
statements
}

The following paragraphs describe the method declaration piece by piece:

An example

Okay, all that was a little abstract. Now, for a concrete example, I offer a version of the Hello, World! program in which the message is displayed not by the main method, but by a method named sayHello that’s called by the main method:

public class HelloWorldMethod
{
public static void main(String[] args)
{
sayHello();
}
public static void sayHello()
{
System.out.println("Hello, World!");
}
}

This program is admittedly trivial, but it illustrates the basics of creating and using methods in Java. Here, the statement in the main method calls the sayHello method, which in turn displays a message on the console.

tip The order in which methods appear in your Java source file doesn’t matter. The only rule is that all the methods must be declared within the body of the class — that is, between the first left brace and the last right brace. Here’s a version of the HelloWorldMethod program in which I reverse the order of the methods:

public class HelloWorldMethod
{
public static void sayHello()
{
System.out.println("Hello, World!");
}
public static void main(String[] args)
{
sayHello();
}
}

This version of the program works exactly like the preceding version.

Another example

Okay, the last example was kind of dumb. No one in his (or her) right mind would create a method that has just one line of code and then call it from another method that also has just one line of code. The Hello, World! program is too trivial to illustrate anything remotely realistic.

A program in Book 2, Chapter 5 , plays a guessing game. Most of this program’s main method is a large while loop that repeats the game as long as the user wants to keep playing. This loop has 41 statements in its body. That’s not so bad, but what if the game were 100 times more complicated, so that the while loop needed 4,100 statements to play a single cycle of the game? Do you really want a while loop that has 4,100 statements in its body? I should think not.

Listing 7-1 shows how you can simplify this game a bit just by placing the body of the main while loop in a separate method. I called this method playARound , because its job is to play one round of the guessing game. Now, instead of actually playing a round of the game, the main method of this program delegates that task to the playARound method.

LISTING 7-1 A Version of the Guessing-Game Program That Uses a playARound Method

import java.util.Scanner;

public class GuessingGameMethod
{
static Scanner sc = new Scanner(System.in);
static boolean keepPlaying = true; →7
public static void main(String[] args)
{
System.out.println("Let's play a guessing game!");

while (keepPlaying) →12
{
playARound(); →14
}
System.out.println("\nThank you for playing!");
}
public static void playARound() →19
{
boolean validInput;
int number, guess;
String answer;

// Pick a random number
number = (int)(Math.random() * 10) + 1;
System.out.println("\nI'm thinking of a number "
+ "between 1 and 10.");
// Get the guess

System.out.print("What do you think it is? ");
do
{
guess = sc.nextInt();
validInput = true;
if ((guess < 1) || (guess > 10))
{
System.out.print("I said, between 1 "
+ "and 10. Try again: ");
validInput = false;
}
} while (!validInput);

// Check the guess
if (guess == number)
System.out.println("You're right!");
else
System.out.println("You're wrong!"
+ " The number was " + number);
// Play again?
do
{
System.out.print("\nPlay again? (Y or N)");
answer = sc.next();
validInput = true;
if (answer.equalsIgnoreCase("Y"));
else if (answer.equalsIgnoreCase("N"))
keepPlaying = false; →60
else
validInput = false;
} while (!validInput);
}
}

Here are a few important details to notice about this method:

  1. 7 Because both the main method (in line 12) and the playARound method (in line 60) must access the keepPlaying variable, I declare it as a class variable rather than as a local variable in the main method.

    remember Class variables must be static if you intend to access them from static methods.

  2. 14 The body of the while loop in the main method is just one line: a call to the playARound method. Thus, each time the loop repeats, the program plays one round of the game with the user.
  3. 19 The declaration for the playARound method marks the method as static so that the static main method can call it.

tip The body of the playARound method is identical to the body of the while loop used in the single-method version of this program shown in Book 2, Chapter 5 . If you want a refresher on how this code works, I politely refer you to Listing 5-1 , near the end of that chapter.

Methods That Return Values

Methods that just do work without returning any data are useful only in limited situations. The real utility of methods comes when they can perform some mundane task such as a calculation and then return the value of that calculation to the calling method so that the calling method can do something with the value. You find out how to do that in the following sections.

Declaring the method’s return type

To create a method that returns a value, you simply indicate the type of the value returned by the method on the method declaration in place of the void keyword. Here’s a method declaration that creates a method that returns an int value:

public static int getRandomNumber()

Here the getRandomNumber method calculates a random number and then returns the number to the caller.

remember The return type of a method can be any of Java’s primitive return types (described in Book 2, Chapter 2 ):

int
long
float
char
short
byte
double
boolean

Alternatively, the return type can be a reference type , including a class defined by the API such as String or a class you create yourself.

Using the return statement to return the value

When you specify a return type other than void in a method declaration, the body of the method must include a return statement that specifies the value to be returned. The return statement has this form:

return expression ;

The expression must evaluate to a value that’s the same type as the type listed in the method declaration. In other words, if the method returns an int , the expression in the return statement must evaluate to an int .

Here’s a program that uses a method that determines a random number between 1 and 10:

public class RandomNumber
{
public static void main(String[] args)
{
int number = getRandomNumber();
System.out.println("The number is " + number);
}
public static int getRandomNumber()
{
int num = (int)(Math.random() * 10) + 1;
return num;
}
}

In this program, the getRandomNumber method uses the Math.random method to calculate a random number from 1 to 10. (For more information about the Math.random method, see Book 2, Chapter 3 .) The return statement returns the random number that was calculated.

Because the return statement can specify an expression as well as a simple variable, I could just as easily have written the getRandomNumber method like this:

public static int getRandomNumber()
{
return (int)(Math.random() * 10) + 1;
}

Here the return statement includes the expression that calculates the random number.

Using a method that returns a type

You can use a method that returns a value in an assignment statement, like this:

int number = getRandomNumber();

Here the getRandomNumber method is called, and the value it returns is assigned to the variable number .

You can also use methods that return values in expressions — such as

number = getRandomNumber() * 10;

Here the value returned by the getRandomNumber method is multiplied by 10, and the result is assigned to number .

You gotta have a proper return statement

If a method declares a return type other than void, it must use a return statement to return a value. The compiler doesn’t let you get away with a method that doesn’t have a correct return statement.

Things can get complicated if your return statements are inside if statements. Sometimes, the compiler gets fooled and refuses to compile your program. To explain this situation, I offer the following tale of multiple attempts to solve what should be a simple programming problem.

Suppose that you want to create a random-number method that returns random numbers between 1 and 20 but never returns 12 (because you have the condition known as dodecaphobia, which — as Lucy from Peanuts would tell you — is the fear of the number 12). Your first thought is to just ignore the 12s, like this:

public static int getRandomNumber()
{
int num = (int)(Math.random() * 20) + 1;
if (num != 12)
return num;
}

The compiler isn’t fooled by your trickery here, however. It knows that if the number is 12, the return statement won’t get executed, so it issues the message missing return statement and refuses to compile your program.

Your next thought is to simply substitute 11 whenever 12 comes up:

public static int getRandomNumber()

{
int num = (int)(Math.random() * 20) + 1;
if (num != 12)
return num;
else
return 11;
}

Later that day, you realize that this solution isn’t a good one because the number isn’t really random anymore. One of the requirements of a good random-number generator is that any number should be as likely as any other number to come up next. But because you’re changing all 12s to 11s, you’ve made 11 twice as likely to come up as any other number.

To fix this error, you decide to put the random-number generator in a loop that ends only when the random number is not 12:

public static int getRandomNumber()
{
int num;
do
{
num = (int)(Math.random() * 20) + 1;
if (num != 12)
return num;
} while (num == 12);
}

But the compiler refuses to compile the method again. It turns out that the compiler is smart, but not very smart. It doesn’t catch the fact that the condition in the do-while loop is the opposite of the condition in the if statement, meaning that the only way out of this loop is through the return statement in the if statement. So the compiler whines missing return statement again.

After thinking about it for a while, you come up with this solution:

public static int getRandomNumber()
{
int num;
while (true)
{
num = (int)(Math.random() * 20) + 1;
if (num != 12)
return num;
}
}

Now everyone’s happy. The compiler knows that the only way out of the loop is through the return statement, your dodecaphobic user doesn’t have to worry about seeing the number 12, and you know that the random number isn’t twice as likely to be 11 as any other number. Life is good, and you can move on to the next topic.

Trying another version of the guessing-game program

To illustrate the benefits of using methods that return values, Listing 7-2 presents another version of the guessing-game program that uses four methods in addition to main :

LISTING 7-2 Another Version of the Guessing-Game Program

import java.util.Scanner;

public class GuessingGameMethod2
{
static Scanner sc = new Scanner(System.in);

public static void main(String[] args)
{
System.out.println("Let's play a guessing game!");
do →11
{
playARound(); →13
} while (askForAnotherRound()); →14
System.out.println("\nThank you for playing!");
}

public static void playARound() →18
{
boolean validInput;
int number, guess;
String answer;

// Pick a random number
number = getRandomNumber(); →25

// Get the guess
System.out.println("\nI'm thinking of a number "
+ "between 1 and 10.");
System.out.print("What do you think it is? ");
guess = getGuess(); →31

// Check the guess
if (guess == number)
System.out.println("You're right!");
else
System.out.println("You're wrong!"
+ " The number was " + number);
}
public static int getRandomNumber() →41
{
return (int)(Math.random() * 10) + 1; →43
}
public static int getGuess() →46
{
while (true) →48
{
int guess = sc.nextInt();
if ((guess < 1) || (guess > 10))
{
System.out.print("I said, between 1 and 10. "
+ "Try again: ");
}
else
return guess; →57
}
}

public static boolean askForAnotherRound() →61
{
while (true) →63
{
String answer;
System.out.print("\nPlay again? (Y or N) ");
answer = sc.next();
if (answer.equalsIgnoreCase("Y"))

return true; →69
else if (answer.equalsIgnoreCase("N"))
return false; →71
}
}
}

The following paragraphs point out the key lines of this program:

  1. 11 The start of the do loop in the main method. Each cycle of this do loop plays one round of the game. The do loop continues until the user indicates that he or she wants to stop playing.
  2. 13 Calls the playARound method to play one round of the game.
  3. 14 Calls the askForAnotherRound method to determine whether the user wants to play another round. The boolean return value from this method is used as the expression for the do loop. Thus, the do loop repeats if the askForAnotherRound method returns true .
  4. 18 The start of the playARound method.
  5. 25 Calls the getRandomNumber method to get a random number between 1 and 10. The value returned by this method is stored in the number variable.
  6. 31 Calls the getGuess method to get the user’s guess. This method returns a number between 1 and 10, which is stored in the guess variable.
  7. 41 The start of the getRandomNumber method, which indicates that this method returns an int value.
  8. 43 The return statement for the getRandomNumber method. The random number is calculated using the Math.random method, and the result of this calculation is returned as the value of the getRandomNumber method.
  9. 46 The start of the getGuess method, which indicates that this method returns an int value.
  10. 48 The getGuess method uses a while loop, which exits only when the user enters a number between 1 and 10.
  11. 57 The return statement for the getGuess method. Note that this return statement is in the else part of an if statement that checks whether the number is less than 1 or greater than 10. If the number is outside the acceptable range, the return statement isn’t executed. Instead, the program displays an error message, and the while loop repeats.
  12. 61 The start of the askForAnotherRound method, which returns a boolean value.
  13. 63 The askForAnotherRound method, which uses a while loop that exits only when the user enters a valid Y or N response.
  14. 69 The askForAnotherRound method, which returns true if the user enters Y or y .
  15. 71 The askForAnotherRound method, which returns false if the user enters N or n .

Methods That Take Parameters

A parameter is a value that you can pass to a method. Then the method can use the parameter as though it were a local variable initialized with the value of the variable passed to it by the calling method.

The guessing-game application shown in Listing 7-2 has a method named getRandomNumber that returns a random number between 1 and 10:

public static int getRandomNumber()
{
return (int)(Math.random() * 10) + 1;
}

This method is useful, but it would be even more useful if you could tell it the range of numbers you want the random number to fall in. It would be nice to call the method like this to get a random number between 1 and 10:

int number = getRandomNumber(1, 10);

Then, if your program needs to roll dice, you could call the same method:

int number = getRandomNumber(1, 6);

Or, to pick a random card from a deck of 52 cards, you could call it like this:

int number = getRandomNumber(1, 52);

You wouldn’t have to start with 1, either. To get a random number between 50 and 100, you’d call the method like this:

int number = getRandomNumber(50, 100);

In the following sections, you write methods that accept parameters.

Declaring parameters

A method that accepts parameters must list the parameters in the method declaration. The parameters are placed in a parameter list inside the parentheses that follow the method name. For each parameter used by the method, you list the parameter type followed by the parameter name. If you need more than one parameter, you separate the parameters with commas.

Here’s a version of the getRandomNumber method that accepts parameters:

public static int getRandomNumber(int min, int max)
{
return (int)(Math.random()
* (max - min + 1)) + min;
}

Here the method uses two parameters, both of type int , named min and max . Then, within the body of the method, these parameters can be used as though they were local variables.

tip The names you use for parameters can be the same as the names you use for the variables you pass to the method when you call it, but they don’t have to be. You could call the getRandomNumber method like this:

int min = 1;
int max = 10;
int number = getRandomNumber(min, max);

Or you could call it like this:

int low = 1;
int high = 10;
int number = getRandomNumber(low, high);

Or you could dispense with the variables altogether and just pass literal values to the method:

int number = getRandomNumber(1, 10);

You can also specify expressions as the parameter values:

int min = 1;
int max = 10;
int number = getRandomNumber(min * 10, max * 10);

Here number is assigned a value between 10 and 100.

Scoping out parameters

The scope of a parameter is the method for which the parameter is declared. As a result, a parameter can have the same name as local variables used in other methods without causing any conflict. Consider this program:

public class ParameterScope
{
public static void main(String[] args)
{
int min = 1;
int max = 10;
int number = getRandomNumber(min, max);
System.out.println(number);
}

public static int getRandomNumber(int min, int max)
{
return (int)(Math.random()
* (max - min + 1)) + min;
}
}

Here the main method declares variables named min and max , and the getRandomNumber method uses min and max for its parameter names. This doesn’t cause any conflict, because in each case the scope is limited to a single method.

Understanding pass-by-value

When Java passes a variable to a method via a parameter, the method itself receives a copy of the variable’s value, not the variable itself. This copy is called a pass-by-value, and it has an important consequence: If a method changes the value it receives as a parameter, that change is not reflected in the original variable that was passed to the method. The following program can help clear this up:

public class ChangeParameters
{
public static void main(String[] args)
{
int number = 1;
tryToChangeNumber(number);
System.out.println(number);
}

public static void tryToChangeNumber(int i)
{
i = 2;
}
}

Here a variable named number is set to 1 and then passed to the method named tryToChangeNumber . This method receives the variable as a parameter named i and then sets the value of i to 2 . Meanwhile, back in the main method, println is used to print the value of number after the tryToChangeNumber method returns.

Because tryToChangeNumber gets only a copy of number , not the number variable itself, this program displays the following on the console (drum roll, please …): 1 .

The key point is this: Even though the tryToChangeNumber method changes the value of its parameter, that change has no effect on the original variable that was passed to the method.

Trying yet another version of the guessing-game program

To show off the benefits of methods that accept parameters, Listing 7-3 shows one more version of the guessing-game program. This version uses the following methods in addition to main :

LISTING 7-3 Yet Another Version of the Guessing-Game Program

import java.util.Scanner;

public class GuessingGameMethod3
{

static Scanner sc = new Scanner(System.in);
public static void main(String[] args)
{
System.out.println("Let's play a guessing game!");
do
{
playARound(1, getRandomNumber(7, 12)); →13
} while (askForAnotherRound("Try again?"));
System.out.println("\nThank you for playing!");
}
public static void playARound(int min, int max)
{
boolean validInput;
int number, guess;
String answer;

// Pick a random number
number = getRandomNumber(min, max); →25
// Get the guess
System.out.println("\nI'm thinking of a number "
+ "between " + min + " and " + max + "."); →29
System.out.print("What do you think it is? ");
guess = getGuess(min, max); →31

// Check the guess
if (guess == number)
System.out.println("You're right!");
else
System.out.println("You're wrong!"
+ " The number was " + number);
}
public static int getRandomNumber(int min, int max) →41
{
return (int)(Math.random() →43
* (max - min + 1)) + min;
}
public static int getGuess(int min, int max) →47
{
while (true)
{
int guess = sc.nextInt();
if ( (guess < min) || (guess > max) ) →52
{
System.out.print("I said, between "
+ min + " and " + max
+ ". Try again: ");
}
else
return guess; →59
}
}
public static boolean askForAnotherRound(String prompt) →63
{
while (true)
{
String answer;
System.out.print("\n" + prompt + " (Y or N) ");
answer = sc.next();
if (answer.equalsIgnoreCase("Y"))
return true;
else if (answer.equalsIgnoreCase("N"))
return false;
}
}
}

The following paragraphs point out the key lines of this program:

  1. 13 This line calls the playARound method to play one round of the game. The values for min and max are passed as literals. To add a small amount of variety to the game, the getRandomNumber method is called here to set the value for the max to a random number from 7 to 12.
  2. 25 The call to the getRandomNumber method passes the values of min and max as parameters to set the range for the random numbers.
  3. 29 The message that announces to the user that the computer has chosen a random number uses the min and max parameters to indicate the range.
  4. 31 The call to the getGuess method now passes the range of acceptable guesses to the getGuess method.
  5. 41 The declaration for the getRandomNumber method specifies the min and max parameters.
  6. 43 The calculation for the random number is complicated a bit by the fact that min may not be 1 .
  7. 47 The declaration for the getGuess method accepts the min and max parameters.
  8. 52 The if statement in the getGuess method uses the min and max values to validate the user’s input.
  9. 59 This line is the return statement for the getGuess method. Note that this return statement is in the else part of an if statement that checks whether the number is less than 1 or greater than 10. If the number is outside the acceptable range, the return statement isn’t executed. Instead, the program displays an error message, and the while loop repeats.
  10. 63 The askForAnotherRound method accepts a string variable to use as a prompt.