Section 3.2
Static Methods and Static Variables
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 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, namely 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 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. Another modifier, "private", 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 -- you'll learn about packages in Section 5.) There are a few other modifiers that we'll run across as the course proceeds.
(By the way, you might be wondering why access specifiers should exist at all. Why not just let everything be public? Well, think of the whole class itself as a kind of black box. The public methods are part of the interface of that black box, while private methods are part of the hidden implementation inside the box. The Rules of Black Boxes imply that you should only declare a method to be public if it is really meant to be part of the interface that is visible from outside the box. Do you see why this is true?)
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. 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 just indicates that changeChannel() has a parameter of type int. When the subroutine is called, an actual channel number must be provided; 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 that 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.)
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. Here's a program that plays a guessing game with the user -- except that I've left out the inside of the playGame() method:
public class GuessingGame { public static void main(String[] args) { Console console = new Console(); console.putln("Let's play a game. I'll pick a number between"); console.putln("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // call subroutine to play one game console.put("Would you like to play again? "); playAgain = console.getlnBoolean(); } while (playAgain); console.close(); } // end of main() static void playGame() { . . . // programming for playing one game goes here } // end of playGame() } // end of class GuessingGameThis does show a possible outline for the program, but as soon as I try to finish the definition of playGame, I run into trouble! The problem is that I need to ask the user questions and read the user's responses, and for that, I need to use the console. However, the console is a local variable defined inside the subroutine main(). The rule is that a variable defined locally, inside a subroutine, is part of the black box and is completely inaccessible from outside the subroutine. There is no way for me to use the console from inside playGame()! (In case you are wondering, if I were to give playGame() its own Console variable, it would be a completely different variable from the console in main() -- with its own separate window on the screen!)
There is an easy solution: Instead of being a local variable in a subroutine, console should be a static member variable of the class GuessingGame. Variables can be declared in a class outside of any subroutine. Such variables can be either static or non-static, but -- as with methods -- we'll stick to the static case in this chapter. 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. If it is public, then it can be accessed from outside the class using a compound name of the form
class-name.variable-name
Whereas a local variable in a subroutine only exists while a subroutine is being executed, a static member variable exists as long as the program as a whole is running. By moving the declaration of console outside the subroutine main(), we make it into a static member variable that can be accessed in both main() and in playGame(). This allows us to complete the sample program:
public class GuessingGame { static Console console; // declare console as a static variable public static void main(String[] args) { console = new Console(); console.putln("Let's play a game. I'll pick a number between"); console.putln("1 and 100, and you try to guess it."); boolean playAgain; do { playGame(); // call subroutine to play one game console.put("Would you like to play again? "); playAgain = console.getlnBoolean(); } while (playAgain); console.close(); } // end of main() static void playGame() { int computersNumber = (int)(100 * Math.random()) + 1; // The value assigned to computerNumber is a randomly // chosen integer between 1 and 100, inclusive int usersGuess; // this will be a number entered by user console.putln(); console.put("What is your first guess? "); do { usersGuess = console.getInt(); if (usersGuess == computersNumber) console.putln("You got it! My number was " + computersNumber); else if (usersGuess < computersNumber) console.put("That's too low. Try again: "); else if (usersGuess > computersNumber) console.put("That's too high. Try again: "); } while (usersGuess != computersNumber); console.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 subroutines makes it easier to understand and probably made it easier to write each piece.
You can try out a simulation of this program here:
[ Next Section | Previous Section | Chapter Index | Main Index ]