Java Static Methods Revision

In the previous set of notes you saw a simple program that read a list of numbers, stored them in an array, and then counted the number of times a particular number occurred in the list. You were also reminded that for the purpose of this course, the important part of the code was the bit that went through the array counting the occurrences of the number given.

Please note that everything in this set of notes is really just revision of material you have already covered in your first programming course. There is no need to bother with this set of notes if you are happy with that course, have understood the material, done well in the tests, and managed to do all the coursework and exercises on your own. These notes cover material which it is necessary to be familiar with for the Algorithms and Data Structures course, but past experience suggests some students still have difficulty with.

Dividing a program into static methods

A nicer way of writing the program we considered previously, might have been:
import java.util.*;
import java.io.*;

class CountNumber2
// A program to read a list of numbers then read one number and
// count the number of times it occurs in the list.
// Code broken up into separate static methods.
{
public static void main(String[] args) throws Exception
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int[] a = readNumbers(in);
int n = readANumber(in);
int count = count(n,a);
System.out.print("The number "+n);
System.out.println(" occurs "+count+" times in the list");
System.out.println("That's all");
}

public static int[] readNumbers(BufferedReader in) throws Exception
{
System.out.println("Enter a list of numbers (all on one line):");
String line = in.readLine();
StringTokenizer toks = new StringTokenizer(line);
int[] a = new int[toks.countTokens()];
for(int i=0; i<a.length; i++)
a[i] = Integer.parseInt(toks.nextToken());
return a;
}

public static int readANumber(BufferedReader in) throws Exception
{
System.out.print("Enter a number: ");
String line = in.readLine();
int n = Integer.parseInt(line);
return n;
}

public static int count(int n,int[] a)
{
int count=0;
for(int j=0; j<a.length; j++)
if(a[j]==n)
count++;
return count;
}
}
This program has a neater appearance, because the code is broken up into several static methods. You were told previously that the important bit of code was the bit which went through the array, and the rest was just supporting input/output stuff which you needn't consider in any detail. Now the important bit is separated out into its own method called count. We also separated out two bits of code which prompt for values and return them. The first, which we have called readNumbers, prompts for a list of numbers and returns them stored in an array. The second, which we have called readANumber, prompts for a single number and returns it. The main method, which is where execution of a Java program always starts off, consists mainly of calls to those methods

A static method can be considered a piece of code that is parameterised. It's slightly more complex than that in Java, but for now this will do as an explanation. If we have a method call meth(a,b) then we need a static method in the same class called meth which has a header which includes meth(atype c, btype d). Here atype and btype are in italics because they can be any types, but they must match the type of the variables a and b respectively. What happens then is that execution of the code where the call meth(a,b) occurs is halted. Instead the code following the { in the method is executed, but with c replaced by a and d replaced by b. This is what is meant by "parameterisation" - c and d are the parameters which are replaced by the arguments (a and b in this case) when the method is executed.

All methods must have a return type. When a statement written return followed by some value, which must be of the return type of the method, is executed, execution returns to the point where the method was called. You can think of the method call as being replaced by the value that was returned. In the above example, you can see that the return type for the method readNumbers is int[], that is, an array of integers, whereas the return type for the methods readANumber and coun is int, that is, a single integer.

A special return type written void is used for methods that don't return anything. In this case, the method will execute until it reaches the final closing }, although you can use return; on its own to cause it to halt.

In the above code, note that as in is just another variable in the main method, if you want to use it in some other method, you have to pass it as an argument to that method. Its type is BufferedReader, so that has to be given to the parameter of the method. As a Java input/output thing, note that if buf is a BufferedReader, every time you call buf.readLine() there is a chance you might "throw a checked exception". That is why the methods readNumbers and readANumber have to have the words throws Exception after their headers, because they have a call to readLine() attached to a BufferedReader variable inside them. Exceptions are an aspect of Java you won't have covered yet, so don't worry about this, it isn't important to this course.

Parameter Passing

In the code above, the parameter names of the methods were the same as the names of the argument passed when they are called. This makes it easier to understand how they work. To better illustrate the mechanism of parameter passing, however, let us consider a modified version of the program where the methods have deliberately been given parameter names which are different from the names used as the arguments when the methods are called:
import java.util.*;
import java.io.*;

class CountNumber3
// A program to read a list of numbers then read one number and
// count the number of times it occurs in the list.
// Code broken up into separate static methods.
// Shows use of different parameter names to arguments.
{
public static void main(String[] args) throws Exception
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int[] a = readNumbers(in);
int n = readANumber(in);
int count = count(n,a);
display(n,count);
System.out.println("That's all");
}

public static void display(int num, int c)
{
System.out.print("The number "+num);
System.out.println(" occurs "+c+" times in the list");
}

public static int[] readNumbers(BufferedReader reader) throws Exception
{
System.out.println("Enter a list of numbers (all on one line):");
String line = reader.readLine();
StringTokenizer toks = new StringTokenizer(line);
int[] a = new int[toks.countTokens()];
for(int i=0; i<a.length; i++)
a[i] = Integer.parseInt(toks.nextToken());
return a;
}

public static int readANumber(BufferedReader reader) throws Exception
{
System.out.print("Enter a number: ");
String line = reader.readLine();
int n = Integer.parseInt(line);
return n;
}

public static int count(int m,int[] b)
{
int occurrences=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
occurrences++;
return occurrences;
}
}
There is also a separate method called display which prints out the results of the count. This is given to illustrate a void-returning method.

To illustrate how this code works, consider the line in the main method:

int count = count(n,a);
This is a call to the method whose signature is:
public static int count(int m,int[] b)
In the call, the argument n is matched against the parameter name m, and the argument a is matched against the parameter name b. You can think of this as executing the code for the method count, which is:
  int occurrences=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
occurrences++;
return occurrences;
with m replaced by n and b replaced by a. However, a better way of thinking of it is that the code is executed with m assigned the value of n and b assigned the value of a. That means the statement
int count = count(n,a);
is equivalent to:
  int m=n;
int[] b=a;
int occurrences=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
occurrences++;
int count = occurrences;
Note how the value which is returned replaces the original method call.

The reason for this is that parameters can be used just like variables in the code of a method, and even be assigned new values. However, assigning them a new value does not change the value of the argument they were matched with. To demonstrate this, suppose we had the method:

  int square(int m)
{
m=m*m;
return m;
}
If we had the code:
  int h=5;
int k=square(h);
it wouldn't have the effect of changing the value of a. The effect is as if:
  int h=5;
int m=h;
m=m*m;
int k=m;
is executed, and not:
  int h=5;
h=h*h;
int k=h;
Note that when two arrays are assigned, for example, above we had
  int[] b=a;
for passing the array a as an argument to a method to match parameter b, the effect is that b becomes another name for the array a. This is something we will discuss in more detail in the course later on, since it has quite a big practical effect on methods that deal with data structures.

Printing and returning

In the second example program above, we had one method that took some arguments and didn't return a value. Instead it printed something. Real Java programs more usually work with graphical user interfaces, so such a method might display something in some window. The important thing is that it doesn't return anything. To indicate this it is given return type void. Here is that method again:
 public static void display(int num, int c)
{
System.out.print("The number "+num);
System.out.println(" occurs "+c+" times in the list");
}
Many people when learning to program confuse printing a value with returning it. It is important to realise that the two are completely different. Printing a value causes something external to the program to happen: the value is displayed in some way, or left on a file. Returning a method means that it is passed around internally in the execution of a program. We would not have any useful programs if there was never any method that did something externally, since we would never then view the results of any computation. However, a method that calculates and returns a value is in general much more useful.

One thing you should always avoid doing is writing methods that calculate and display a result. That is to try and make a method do two things that should be separate, and that is always a mistake. For example, I sometimes see people who are asked to write a method to do a task like "take an integer and an array of integers and return the number of times the integer occurs in the array" write something like:

 void count(int m,int[] b)
// Deliberately silly method - never write one like this
{
int count=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
count++;
System.out.print("The number "+m);
System.out.println(" occurs "+count+" times in the list");
}
I call this a "useless" method. Why is it useless when it seems to do the job - calculating the number of times an integer occurs in an array? If we call it with, say:
  count(n,a);
it will count and print the number of times the value in variable n occurs in the array referred to by a.

The reason it is useless is that although it solves the problem for the purpose of the programming exercise, it could not be used any further. Once we get beyond tiny examples given as exercises for beginning programmers, when we write methods it's because we want to use the value they return to do something else with it. So if we want to count the number of times an integer occurs in an array of integers, it's because we want to save that value in a variable and use that variable elsewhere. If the value is just printed, it is cannot be used further in the program. We want to be able to write something like

  num = count(n,a);
and go on to use num. It's quite likely we will want to use a method more than once with different arguments, for example:
  num1 = count(n,a);
num2 = count(m,b);
so another advantage of methods is that they enable us to reuse portions of code.

Some people, realising this, write methods like:

 int count(int m,int[] b)
// Deliberately silly method - never write one like this
{
int count=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
count++;
System.out.print("The number "+m);
System.out.println(" occurs "+count+" times in the list");
return count;
}
Again, this is wrong. If you use a method as part of a larger program, the chances are you don't want some silly little message being printed whenever it is used.

Another variation I have seen, is people writing methods which return the silly little message:

 String count(int m,int[] b)
// Deliberately silly method - never write one like this
{
int count=0;
for(int j=0; j<b.length; j++)
if(b[j]==m)
count++;
return "The number "+m+" occurs "+count+" times in the list";
}
Again, this is useless. We will always want a method which returns the number of times an integer is in an array, or whatever else it is we are calculating, as an actual value. We cannot do anything further with it if it is turned into a string.

Finally, if you are asked to write a method to do something, a method is required, not a whole program. For the purpose of a programming exercise set for beginning programmers that we actually want to execute, we will need the program with its main method, and simple interface to read values and pass them to the method. However, in a test or exam where you are asked to write a method that would never be required, you would waste time writing it, and lose marks for not realising it isn't needed.

So, to summarise, if you are asked in a test or exam:

Write a method which takes an integer and an array of integers and returns the number of times the integer occurs in the array.

what is required is:

 public static int count(int n,int[] a)
{
int count=0;
for(int j=0; j<a.length; j++)
if(a[j]==n)
count++;
return count;
}
It would be wrong to write code, in answer to the request above, which either reads something or writes something. When doing exercises in the lab however, code to read/write things may be provided or may have to be written in order to be able to run the more important code that provides the algorithms and data structures.

Prompts and parameters

As a minor point, our code above had methods which printed a prompt an read a number and a list of numbers. Even this is really to mix two separate things - printing the prompts and doing the reading, which makes the methods less flexible. There may be circumstances under which we would not want to print a prompt (for example, if we were reading from a file rather than user-type input), or we may want to vary the prompt used. So a better program would take the prompts out of the reading methods, giving:
import java.util.*;
import java.io.*;

class CountNumber4
// A program to read a list of numbers then read one number and
// count the number of times it occurs in the list.
// Code broken up into separate static methods.
// Prompts outside reading methods
{
public static void main(String[] args) throws Exception
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter a list of numbers (all on one line):");
int[] a = readNumbers(in);
System.out.print("Enter a number: ");
int n = readANumber(in);
int count = count(n,a);
System.out.print("The number "+n);
System.out.println(" occurs "+count+" times in the list");
System.out.println("That's all");
}

public static int[] readNumbers(BufferedReader in) throws Exception
{
String line = in.readLine();
StringTokenizer toks = new StringTokenizer(line);
int[] a = new int[toks.countTokens()];
for(int i=0; i<a.length; i++)
a[i] = Integer.parseInt(toks.nextToken());
return a;
}

public static int readANumber(BufferedReader in) throws Exception
{
String line = in.readLine();
int n = Integer.parseInt(line);
return n;
}

public static int count(int n,int[] a)
{
int count=0;
for(int j=0; j<a.length; j++)
if(a[j]==n)
count++;
return count;
}
}
Another possibility would be to remember that the prompts are themselves just strings, and generalise the reading methods by making the prompts parameters to them. This gives:
import java.util.*;
import java.io.*;

class CountNumber5
// A program to read a list of numbers then read one number and
// count the number of times it occurs in the list.
// Code broken up into separate static methods.
// Prompts as parameters to reading methods
{
public static void main(String[] args) throws Exception
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
int[] a = readNumbers(in,"Enter a list of numbers (all on one line):");
int n = readANumber(in,"Enter a number: ");
int count = count(n,a);
System.out.print("The number "+n);
System.out.println(" occurs "+count+" times in the list");
System.out.println("That's all");
}

public static int[] readNumbers(BufferedReader in,String prompt)
throws Exception
{
System.out.println(prompt);
String line = in.readLine();
StringTokenizer toks = new StringTokenizer(line);
int[] a = new int[toks.countTokens()];
for(int i=0; i<a.length; i++)
a[i] = Integer.parseInt(toks.nextToken());
return a;
}

public static int readANumber(BufferedReader in,String prompt)
throws Exception
{
System.out.print(prompt);
String line = in.readLine();
int n = Integer.parseInt(line);
return n;
}

public static int count(int n,int[] a)
{
int count=0;
for(int j=0; j<a.length; j++)
if(a[j]==n)
count++;
return count;
}
}

Matthew Huntbach

Last modified: 14 December 2004