Espresso: A Concentrated Introduction to Java


Static Fields and Constants

Summary: We consider Java's static fields, a mechanism for sharing information between static methods within the same class. We also consider how to write constant values in java.

Prerequisites: Java basics. Input and Output. Numbers. Static Methods. Writing your own classes.

Contents:

Static Fields

You have seen that Java has both static methods and (non-static) object methods. We use object methods in classes which generate objects, and we call them using the objectname.methodname syntax. As such, an object method operates on a specific object in the class. In contrast, static methods are primarily used in main classes and utility classes, where they perform tasks that are not related to a particular instantiated object. Examples of this include IO.readInt and Math.sqrt. Even though we pass an argument to Math.sqrt and perform a computation on that specific argument, we do not call the method based on a specific object in the class.

In a very similar way, Java also supports static fields and object fields. As you have seen, object fields are relevant to classes that generate objects: each object stores its own copy of each object field. As you might guess, static fields are fields within a class that pertain to the class as a whole, not to individual objects. It is possible to have such a field in a template class, and we would use it to store some item of information that is shared between the objects in the class. However, it is more common to use static fields in utility classes from which no objects are generated.

Why might a utility class need a static field? Static fields provide a mechanism for the methods within a utility class to share information with one another, or across multiple invocations of the same method. An example will be given shortly.

Declaring a static field is very much like declaring an object field, except that we include the keyword static in the declaration.

protection static type name = expression;

This field declaration goes within the body of a class, but outside the body of any method within that class. The same protection levels exist for static fields as for object fields. Once you have declared a static field you use it as you do any other field or variable. That is, you can assign to it, change it, and, if it's an object, call its methods.

An Application: Counting Recursive Calls

There are, of course, a wide variety of reasons to use static fields. Here's a simple one. As you may recall, one important thing that computer scientists do is evaluate the running time of algorithms. They do so in a variety of ways, including through theoretical methods and through experimental methods.

Consider two mechanisms for computing xn. One is given by the following recursive definition:

The other is given by a more complex recursive definition:

We can encode the two definitions in Java as follows:

public class MyMath
{
  public static BigInteger expt1(BigInteger x, int n)
  {
    if (n == 0) {
      return BigInteger.ONE;
    }
    else {
      return x.multiply(expt1(x, n-1));
    } 
  } // expt1(BigInteger, int)

  public static BigInteger expt2(BigInteger x, int k)
  {
    if (0 == k) {
      return BigInteger.ONE;
    } // k is 0
    else if ((k % 2) == 0) { // k is even
      BigInteger tmp = expt2(x, k/2);
      return tmp.multiply(tmp);
    } // k is even
    else { // k is odd
      return x.multiply(expt2(x, k-1));
    } // k is odd
  } // expt2(BigInteger, int)
} // class MyMath

Which one makes fewer recursive calls? The latter, although more complicated, is significantly more efficient. How do we know? The first subtracts one from the exponent each time, the second cuts the exponent in half every other time, which takes us to 0 quite quickly. If you are not convinced by that informal argument, you might want us to run some experiments. To run the experiment, we'll add a static field, calls, that counts the recursive calls.

private static long calls = 0;

We'll extend each method to increment that count:

  public static BigInteger expt1(BigInteger x, int n)
  {
    calls++;
    ...
  }
  public static BigInteger expt2(BigInteger x, int n)
  {
    calls++;
    ...
  }

Finally, we'll add methods that let a client access the count.

  public static long getCalls()
  {
    return calls;
  } // getCalls()

  public static void resetCalls()
  {
    calls = 0;
  } // resetCalls()

We can then run an experiment:

  BigInteger x = new IO.readBigInteger(pen, eyes, "Enter x: ");
  int n = new IO.readBigInteger(pen. eyes, "Enter the exponent: ");
  MyMath.resetCalls();
  BigInteger result1 = MyMath.expt1(x, n);
  long calls1 = MyMath.getCalls();
  MyMath.resetCalls();
  BigInteger result2 = MyMath.expt2(x, n);
  long calls2 = MyMath.getCalls();
  pen.println("Strategy one returned: " + result1);
  pen.println("Strategy two returned: " + result2);
  pen.println("Strategy one used " + calls1 + " recursive calls.");
  pen.println("Strategy two used " + calls2 + " recursive calls.");

The experiment works because calls is a field within the class. Thus, its value is retained between the method calls, and it can be accessed and modified by each call. Note that this would not work if we declared calls as a variable within the methods. In that situation, the value stored in calls would be re-initialized each time the method was called.

Constants

Constants are what they sound like: their values are constant and therefore unchanging. We use constants to provide a name for constant values used within a program. Doing so allows us to refer to the constant value by name within methods, rather than repeatedly typing its numerical value.

This practice is helpful for a number of reasons. Consider a program that makes extensive use of the value PI. If we type the numerical value of PI in several places within our code, it is quite possible that we may not use the identical value in each place. For example, we may introduce a typographical error, or we may truncate PI differently in different places. Consider also what happens if we decide to change the exact value we have used. If we have defined a constant, we only need to change the definition of the constant. Otherwise, we need to search out and modify every line of code in which we inserted the numerical value of PI. Therefore, it is good practice to declare constants when the opportunity arises, rather than repeatedly typing literal constant values (sometimes called "magic numbers") throughout a program.

You declare constants much like you declare static fields, except that you include the keyword final.

protection static final type NAME = expression;

Like fields, constants are declared within a class but outside any method of the class. As such, they can be accessed by any method in the class. Custom is that you name constants with all capital letters.

To refer to a constant within the declaring class, use just the name of the constant. To refer to the constant from another class, preface the name of the constant with the name of the class. You've already used constants, such as Integer.MAX_VALUE and Math.PI.

An Application: Standard Input and Output

As you have surely noted, it becomes irritating to write the same code over and over again to declare the PrintWriter that prints to the screen and the BufferedReader that reads from the keyboard. We can put their declarations as constants within a utility class and then refer to them from that class.

Here are the declarations:

public class IO
{
  public static final PrintWriter SCREEN = 
    new PrintWriter(System.out, true);

  public static final BufferedReader KEYBOARD = 
    new BufferedReader(new InputStreamReader(System.in, true));

  ...

} // class IO

In our main method, presumably in another class, we can then write

  public static void main(String[] args)
  {
    PrintWriter pen = IO.SCREEN;
    BufferedReader eyes = IO.KEYBOARD;
    ...
  }

Written and revised by Samuel A. Rebelsky, 2006.
Revised further by Marge M. Coahran, 2006.
Samuel A. Rebelsky
rebelsky@grinnell.edu
--->