Espresso: A Concentrated Introduction to Java


Static Methods

Summary: We consider Java's static methods, one key mechanism for encapsulating code.

Prerequisites: Java basics. Input and Output. Numbers.

Contents:

Methods

Soon after beginning programming, most programmers discover that they often rewrite the same (or similar) code again and again and again. For example, beginning Java programmers regularly find that they have to write something like the following every time they want to read a number:

  pen.print("Enter a number: ");
  pen.flush();
  String userinput;
  userinput = eyes.readLine();
  int val = Integer.parseInt(userinput);

Of course, the code is not always identical, as they may use different prompts (or may not prompt at all) and may choose different names for the BufferedReader (eyes, in this case) and other variables.

In reaction to the need to repeat (and parameterize) similar code, most languages support subroutines, which are little more than parameterized blocks of code. In Java, we refer to subroutines as methods. Java supports two kinds of methods, object methods, which require an object to execute, and static methods (also called class methods), which do not require an object. For example, since the readLine method of the BufferedReader class requires a particular BufferedReader, it is an object method. In contrast, since the parseInt method does not require an individual object (we simply write Integer.parseInt), it is a static method.

Invoking Methods

You have already seen how to invoke (or call) a method in Java. For an object method, you write the name of the object, the name of the method, an open paren, a list of parameters separated by commas, and a close paren.

In generic form, an object method call looks like the following.

objName.methodName(param0,param1,...paramn)

For example,

pen.println("Hello");

You've also seen that methods can return values that you then assign or use in other ways. For example,

String combination = "** ".concat(name).concat(" **");

For a static method, you use a similar form, except that you use the name of the class in place of the name of the object. In the generic form, a static method call looks like the following.

ClassName.methodName(param0,param1,...paramn)

For example,

int val = Integer.parseInt(userinput);
double result = Math.sqrt(num);

Writing Static Methods

You know how to call methods, but how do you write one? For now, we will focus on writing static methods. In a future reading you will learn to write classes that can be used to create objects, and at that time you will also learn to write object methods.

What do we need to specify when writing a method?

Since Java likes to check types, we must also specify

Java also requires you to associate a protection level with each method. For now, we'll make each method public.

Java also requires programmers to explicity state whether or not a method can fail in specific ways. We have seen that (for now) you must include the phrase "throws Exception" in the header of methods that call methods from the BufferedReader class. In fact, the rule is really that you must include this phrase in the header of any method that calls a method which has the phrase "throws Exception" (or a variant thereof) in its header -- as several of BufferedReader's methods do. For us right now, this means that if you write a method readInt that uses a BufferedReader, and if you call your readInt method from main, then both readInt and main must say "throws Exception".

Java puts all of these components together into the following form.

protection staticVsObject returnType methodName(type0 name0, type1 name1 ... typen namen)
  optional_note_regarding_exceptions
{
  body
} 

For your first static methods, you should use the following form for methods that call methods which throw exceptions

   public static returnType methodName(type0 name0, type1 name1 ... typen namen)
     throws Exception
   {
     body
   }
and this form for methods that do not
   public static returnType methodName(type0 name0, type1 name1 ... typen namen)
   {
     body
   }

If your method does not return a value, use void as the return type.

For example, here is a simple method that prints and flushes a string, as we might do for a prompt. Such a method requires the PrintWriter for printing and the String to print.

   public static void prompt(PrintWriter pw, String str)
   {
     pw.print(str);
     pw.flush();
   } // prompt(PrintWriter, String)

Once we have defined such a method (say, in class Calculator), we can replace

  pen.print("Enter a number: ");
  pen.flush();

with

  Calculator.prompt(pen, "Enter a number: ");

When a method computes a result, we must use the command return to return the computed value. For example, here is a static method that reads and returns an integer.

   public static int readInt(BufferedReader br)
     throws Exception
   {
     String str = br.readLine();
     int i = Integer.parseInt(str);
     return i;
   } // readInt(BufferedReader)

We can also express this more concisely as

   public static int readInt(BufferedReader br)
     throws Exception
   {
     return Integer.parseInt(br.readLine());
   } // readInt(BufferedReader)

The verbose code from the beginning of the lab can now be phrased as

  Calculator.prompt(pen, "Enter a number: ");
  int val = Calculator.readInt(eyes);

In fact, we might even take the time to write a promptForInt method that does both

public static int promptForInteger(PrintWriter pw, BufferedReader br, String prompt)
     throws Exception
   {
     pw.print(prompt);
     pw.flush();
     return Integer.parseInt(br.readLine());
   } // promptForInteger(PrintWriter, BufferedReader, String)

The verbose code is now down to one line

  int val = Calculator.promptForInteger(pen, eyes, "Please enter a number: ");

Recursion in Static Methods

As you should have learned in a prior class, once you know how to write conditionals and subroutines, you can repeat code an arbitrary number of times using the technique of recursion. At its heart, recursion is simply the idea that a method can call itself. The body of a recursive method usually looks something like the following

public static type method(params)
{
  if (test_for_base_case) {
    return base_value;
  }
  else {
    return computation(params, method(modify(params)));
  }
} // method(params)

For example, the famous factorial function is typically defined as

For this function

Putting it all together, we get

public static int fact(int n)
{
  if (0 == n) {
    return 1;
  }
  else {
    return n * fact(n-1);
  }
} // fact(int)

We can also have more than one recursive call, and these calls can be different from one another. For example, one relatively efficient way of computing xn for non-negative integer n is to use the following rules

We can express those rules as an algorithm in Java as follows

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

We can even use recursion to check user input. For example, consider the problem of forcing the user to enter a positive number. One way to solve that problem is to read a number and, if the user fails to enter a positive number, try again. We can phrase that recursively in Java as

public static int readPositiveInteger(PrintWriter pr, BufferedReader br)
   throws Exception
{
  pr.print("Please enter a positive integer: "); pr.flush();
  int i = Integer.parseInt(br.readLine());
  if (i > 0) {
    return i;
  }
  else {
    br.print(i)
    br.println( " is not positive.  Try again.");
    return readPositiveInteger(pr, br);
  }
} // readPositiveInteger(PrintWriter, BufferedReader)

Grouping Static Methods in Utility Classes

At this point, you're probably saying to yourself something like

I certainly understand the importance of subroutines in clarifying and shortening my code, but it really seems like I'll want to use the same subroutines again and again in different programs. How can I do that?

Fortunately, the answer to that question is relatively simple. You've seen that we can call static methods by giving the class name and the method name, so we can group our utility methods in a single class (or, perhaps, a few classes) and refer to them using the name of the class. Since the purpose of that class is to group static methods, that class, unlike those you've written so far, will not need a main method. We call classes whose main purpose is to provide utility methods utility classes.

In the laboratory, you will create utility classes for input/output and for some calculations.

Overloading Method Names

In some languages, once you have chosen a name for a method, no other method can have the same name. In Java, that restriction is loosened in a number of ways. As you've already seen, it is perfectly acceptable to repeat a method name used in another class, since whenever you call a method, you specify the object or class that supplies the method.

But Java also provides another mechanism for repeating method names: Java permits you to create more than one method with the same name as long as the methods have different parameter types. This technique, called method overloading permits programmers to write variants of a method using the same method name. The value of this is that programmers can choose a single clear name and not worry about making variations of that name. You have already seen overloading in methods you used. For example, there are at least two versions of the indexOf method for strings, one that takes a string as a parameter and one that takes a string and a starting index as the parameter.

No special syntax is required to overload method names.


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