Section 3.2
Static Methods, Static Variables, and Constants
EVERY SUBROUTINE IN JAVA MUST BE DEFINED inside some class. This makes Java rather unusual among programming languages, since most languages allow free-floating, independent subroutines. One purpose of a class is to group together related subroutines and variables. Perhaps the designers of Java felt that everything must be related to something. As a less philosophical motivation, Java's designers wanted to place firm controls on the ways things are named, since a Java program potentially has access to a huge number of subroutines scattered all over the Internet. The fact that those subroutines are grouped into named classes (and, as we shall see later, classes are grouped into named "packages") helps control the confusion that might result from so many different names.
A subroutine that is a member of a class is often called a method, and "method" is the term that is preferred for subroutines in Java. I will start using the term "method"; however, I will also continue to use the term "subroutine" because it is a more general term that applies to all programming languages.
In Java, a method (that is, a subroutine) can be either static or non-static. Non-static methods are used in object-oriented programming, which will be covered in the next chapter. For the rest of this chapter, we will deal only with static methods. A static method plays the same role in Java that ordinary subroutines play in more traditional, non-object-oriented programming.
A method definition in Java takes the form:
modifiers return-type method-name ( parameter-list ) { statements }The statements between the braces, { and }, are the inside of the "black box", as discussed in the previous section. They are the instructions that the computer executes when the method is called. Subroutines can contain any of the statements discussed in Chapter 2. You've already seen some examples of methods, such as the main() subroutines of the sample programs you've looked at. Here are some more examples, with the "insides" omitted (but remember that definitions like these can only occur inside classes):
static void playGame() { // "static" is a modifier; "void" is the return-type; // "playGame" is the method-name; the parameter-list // is empty . . . // statements that define what method does go here } int getNextN(int N) { // there are no modifiers; "int" in the return-type // "getNextN" is the method-name; the parameter-list includes // one parameter whose name is "N" and whose type is "int" . . . // statements that define what method does go here } public static boolean lessThan(double x, double y) { // "public" and "static" are modifiers; "boolean" is the return-type; // "lessThan" is the method-name; the parameter-list includes // two parameters whose names are "x" and "y", and the type // of each of these parameters is "double" . . . // statements that define what method does go here }The modifiers that can occur at the beginning of a subroutine definition are words that set certain characteristics of the method, such as whether it is static or not. In the second example given here, getNextN is a non-static method, since its definition does not include the modifier "static" -- and so its not an example that we should be looking at in this chapter! The other modifier shown in the examples is "public". This modifier indicates that the method can be called from anywhere in a program, even from outside the class where the method is defined. There is another modifier, "private", which indicates that the method can be called only from inside the same class. The modifiers public and private are called access specifiers. If no access specifier is given for a method, then by default, that method can be called from anywhere in the "package" that contains the class but not from outside that package. You'll learn about packages in Section 5. In addition to "static," "public" and "private", there are a few other modifiers that we'll run across as the course proceeds.
Some subroutines are designed to compute and return a value. Such subroutines are called functions. You've already seen examples of mathematical functions such as Math.sqrt(x) and Math.random() in Section 2.6. If you write a method that is meant to be used as a function, then you have to specify the return-type of the method to be the type of value that is computed by your function. For example, the return-type of boolean in
public static boolean lessThan(double x, double y)
specifies that lessThan is a function that computes a boolean value. The return type of Math.sqrt() is double. I'll have more to say about functions in Section 4.
A method that is meant to be used as an ordinary subroutine, rather than one that returns a value, must specify void as its return-type. The term "void" is meant to indicate that the return value is empty or non-existent.
Finally, we come to the parameter-list of the method. Parameters are part of the interface of a subroutine. They represent information that is passed into the subroutine from outside, to be used by the subroutine's internal computations. For a concrete example, imagine a class named Television that includes a method named changeChannel(). The immediate question is: What channel should it change to? A parameter can be used to answer this question. Since the channel number is an integer, the type of the parameter would be int, and the declaration of the changeChannel() method might look like
public void changeChannel(int channelNum) {...}
This declaration specifies that changeChannel() has a parameter named channelNum of type int. However, channelNum does not yet have any particular value. A value for channelNum is provided when the subroutine is called; for example: changeChannel(17);
Parameters are covered in more detail in the next section.
When you define a subroutine, all you are doing is telling the computer that the subroutine exists and what it does. The subroutine doesn't actually get executed until it is called. (This is true even for the main() method in a class -- even though you don't call it, it is called by the system when the system runs your program.) For example, the playGame() method defined above could be called using the following subroutine call statement:
playGame();
This statement could occur anywhere in the same class that includes the definition of playGame(), whether in a main() method or in some other method. Since playGame() is a public method, it can also be called from other classes, but in that case, you have to tell the computer which class it comes from. Let's say, for example, that playGame() is defined in a class named Poker. Then to call playGame() from outside the Poker class, you would have to say
Poker.playGame();
The use of the class name here tells the computer which class to look in to find the method. It also lets you distinguish between Poker.playGame() and other potential playGame() methods defined in other classes, such as Roulette.playGame() or Blackjack.playGame().
More generally, a subroutine call statement takes the form
method-name(parameters);
if the method that is being called is in the same class, or
class-name.method-name(parameters);
if the method is a static method defined elsewhere, in a different class. (Non-static methods defined in other classes are just a bit different; we'll get to that later.) Note that the parameter list can be empty, as in the playGame() example, but the parentheses must be there even if there is nothing between them.
It's time to give an example of what a complete program might look like, if it includes another method in addition to the main() method. Let's write a program that plays a guessing game with the user. The computer will choose a random number between 1 and 100, and the user will try to guess it. If the user gets the number after six guesses or fewer, the user wins the game. After each game, the user has the option of continuing with another game.
Since playing one game can be thought of as a single, coherent task, it makes sense to write a subroutine that will play one guessing game with the user. The main() routine will use a loop to call the playGame() method over and over, as many times as the user wants to play. As a beginning programmer, you might have to think a bit about how to write the playGame() subroutine. You would probably come up with something similar to this:
static void playGame() { int computersNumber = (int)(100 * Math.random()) + 1; // The value assigned to computersNumber is a randomly // chosen integer between 1 and 100, inclusive int usersGuess; // this will be a number entered by user int guessCount = 0; // number of guesses the user has made TextIO.putln(); TextIO.put("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // get the user's guess guessCount++; if (usersGuess == computersNumber) { TextIO.putln("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // the game is over; the user has won } if (guessCount == 6) { TextIO.putln("Sorry, you didn't get the number in 6 guesses."); TextIO.putln("You lose. My number was " + computersNumber); break; // the game is over; the user has lost } // If we get to this point, the game continues. // Tell the user if the guess was too high or too low. if (usersGuess < computersNumber) TextIO.put("That's too low. Try again: "); else if (usersGuess > computersNumber) TextIO.put("That's too high. Try again: "); } TextIO.putln(); } // end of playGame()Now, where exactly should you put this? It should be part of the same class as the main() routine, but not inside the main routine. It is not legal to have one subroutine physically nested inside another. The main() routine will call playGame(), but not contain it physically. You can put the definition of playGame() either before or after the main() routine. Java is not very picky about having the members of a class in any particular order.
Here's what the complete program looks like (except that a serious program needs more comments than I've included here). Note, by the way, the neat way it uses a boolean variable to get the user's answer to a yes/no question.
public class GuessingGame { public static void main(String[] args) { TextIO.putln("Let's play a game. I'll pick a number between"); TextIO.putln("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // call subroutine to play one game TextIO.put("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); TextIO.putln("Thanks for playing. Goodbye."); } // end of main() static void playGame() { int computersNumber = (int)(100 * Math.random()) + 1; // The value assigned to computersNumber is a randomly // chosen integer between 1 and 100, inclusive int usersGuess; // this will be a number entered by user int guessCount = 0; // number of guesses the user has made TextIO.putln(); TextIO.put("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // get the user's guess guessCount++; if (usersGuess == computersNumber) { TextIO.putln("You got it in " + guessCount + " guesses! My number was " + computersNumber); break; // the game is over; the user has won } if (guessCount == 6) { TextIO.putln("Sorry, you didn't get the number in 6 guesses."); TextIO.putln("You lose. My number was " + computersNumber); break; // the game is over; the user has lost } // If we get to this point, the game continues. // Tell the user if the guess was too high or too low. if (usersGuess < computersNumber) TextIO.put("That's too low. Try again: "); else if (usersGuess > computersNumber) TextIO.put("That's too high. Try again: "); } TextIO.putln(); } // end of playGame() } // end of class GuessingGameTake some time to read the program carefully and figure out how it works. And try to convince yourself that even in this relatively simple case, breaking up the program into two methods makes the program easier to understand and probably made it easier to write each piece.
You can try out a simulation of this program here:
A class can include other things besides subroutines. In particular, it can also include variable declarations. Of course, you can have variable declarations inside subroutines. Those are called local variables. However, you can also have variables that are not part of any subroutine. To distinguish such variables from local variables, we call them member variables, since they are members of a class.
Just as with methods, member variables can be either static or non-static. In this chapter, we'll stick to static variables. Member variables can optionally be declared to be public or private. A static member variable can be used in any subroutine defined in the same class. Assuming that it's not private, it can be accessed from outside the class using a compound name of the form class-name.variable-name. (Modifiers such as static and private can only be used with member variables, not with local variables.)
For example, if you want to define a variable named gamesWon and want to be sure that it can only be used inside a single class, then you could declare it with the statement
private static int gamesWon;
You could add this variable to the GuessingGame class given above and use it to keep track of how many games the user wins. In the playGame() routine, you would add 1 to gamesWon if the user wins. At the end of the main() routine, you could print out the value of gamesWon. It would be impossible to do this with local variables, since you need access to the same variable from both routines.
When you declare a local variable in a subroutine, you have to assign a value to that variable before you can do anything with it. Member variables, on the other hand are automatically initialized with a default value if you fail to provide an initial value. For numeric variables, the default value is zero. So the variable gamesWon, as defined above would automatically have an initial value of zero. This happens to be the correct initial value for a variable that is being used as a counter. If you want a different initial value, you can include it in the declaration. For example:
static double pi = 3.141592654;
The example of pi raises an interesting point. 3.141592654 is a well-known mathematical constant. This variable declaration gives the symbolic name "pi" to this constant. However, this pi is a variable! There is nothing to stop you from changing the value of pi! Fortunately, Java provides a way of defining a named constant whose value cannot be changed. If you declare a variable to be "final", its initial value cannot be changed elsewhere in the program. For example, if you declare
static final double PI = 3.141592654;
you can be sure that the value of PI will not be changed elsewhere in the program to a different value. A variable that is declared to be final is called a constant or, to be more specific, a named constant. (It is customary in Java to write the name of a constant in all uppercase letters. However, this is not required.)
There are several good reasons to use named constants. For one thing, you can use a meaningful name, such as MONTHS_IN_YEAR, which will tell a reader of the program a lot more about your intentions than they could learn from a literal number, such as 12. And as strange as it might sound, one of the major reasons for using named constants is because they are easy to change. The computer can't change the value of the constant while the program is running, but you can change the value in the program's source code and recompile it. Consider a typical example:
static final double INTEREST_RATE = 0.045;
Suppose that you use this in a large program that refers to the INTEREST_RATE dozens of times. Now, suppose that you are told that the interest rate has to be changed to 0.043. Since you were clever enough to use a named constant, you only have to change the one line in your program to:
static final double INTEREST_RATE = 0.043;
If, instead, you had uses the literal number 0.045 throughout your program, you would have to search through the whole program looking for occurrences of 0.045 that need to be changed to 0.043.
The GuessingGame program from earlier in this chapter can be improved by using a constant. In the game as written, the computer chooses a number between 1 and 100. The upper limit, 100, is a natural candidate to be a named constant. The statement
static final int MAX = 100;
declares the constant and gives it a name. To get the real benefit of the change, every occurrence of the literal number 100 in the old program has to be replaced with the name MAX. (If you're not obsessive about using the name in the program instead of the number, you'll have problems if you ever want to change the value because you'll still have to search through the program to change any occurrences of the literal number to the new value.)
Here's a revised version of GuessingGame.java that implements the two changes discussed above: the constant MAX and a variable to keep track of the number of games won by the user. The changes from the above version are shown in red:
public class GuessingGame2 { static final int MAX = 100; // The upper limit on the number // that the computer chooses. static int gamesWon = 0; // The number of games won by // the user. public static void main(String[] args) { TextIO.putln("Let's play a game. I'll pick a number between"); TextIO.putln("1 and " + MAX + ", and you try to guess it."); boolean playAgain; do { playGame(); // call subroutine to play one game TextIO.put("Would you like to play again? "); playAgain = TextIO.getlnBoolean(); } while (playAgain); TextIO.putln(); TextIO.putln("You won " + gamesWon + " games."); TextIO.putln("Thanks for playing. Goodbye."); } // end of main() static void playGame() { int computersNumber = (int)(MAX * Math.random()) + 1; // The value assigned to computersNumber is a randomly // chosen integer between 1 and MAX, inclusive int usersGuess; // this will be a number entered by user int guessCount = 0; // number of guesses the user has made TextIO.putln(); TextIO.put("What is your first guess? "); while (true) { usersGuess = TextIO.getInt(); // get the user's guess guessCount++; if (usersGuess == computersNumber) { TextIO.putln("You got it in " + guessCount + " guesses! My number was " + computersNumber); gamesWon++; // Count this game by incrementing gamesWon. break; // the game is over; the user has won } if (guessCount == 6) { TextIO.putln("Sorry, you didn't get the number in 6 guesses."); TextIO.putln("You lose. My number was " + computersNumber); break; // the game is over; the user has lost } // If we get to this point, the game continues. // Tell the user if the guess was too high or too low. if (usersGuess < computersNumber) TextIO.put("That's too low. Try again: "); else if (usersGuess > computersNumber) TextIO.put("That's too high. Try again: "); } TextIO.putln(); } // end of playGame() } // end of class GuessingGame
[ Next Section | Previous Section | Chapter Index | Main Index ]