The Average Examples: Part 2

Breaking a loop

It was mentioned previously that there were ways to exit a loop without failing the loop condition. The simplest way involves the Java break command. This is well illustrated in an example which is a simple variation on the last average program, Average5.java which exited when a sentinel value was read. Here is the new version:
 1  import java.io.*;
 2
 3  class Average6
 4  {
 5  // A program that calculates the average of a series of integers.
 6  // It prompts for each one. A sentinel value is used to indicate the
 7  // end of the series.
 8
 9  static final int SENTINEL = -999;
10
11   public static void main(String[] args) throws IOException
12   {
13    BufferedReader in = Text.open(System.in);
14    int sum=0,count=1,n;
15    while(true)
16       {
17        System.out.print("Enter number "+count+" (or "+SENTINEL+" to finish): ");
18        n=Text.readInt(in);
19        if(n==SENTINEL)
20           break;
21        sum+=n;
22        count++;
23       }
24    System.out.println("Average is: "+Text.writeDouble((double)sum/(count-1),5,3));
25   }
26  }
As you can see, the condition in the while loop is simply the boolean constant true, which obviously always has the value true. As it can never become false the loop can never be exited in the normal way by failing the loop condition. It can however be exited by the break on line 20. A break simply means execution must immediately leave the loop (whether it is a while, for or do loop) which it is within. If it is in a loop within a loop, it only leaves the innermost one (there is a form of break known as a labelled break which can be used to make execution leave more than one loop in one go). A switch statement is treated like a loop for the purpose of exit by break, indeed it has to in order to avoid the follow-on between cases. However, an if statement is not treated as the statement to be exited, it is the switch or loop which encloses it which is exited. Again, that has to be the case, since if a break were not within an if statement within a loop it would always be encountered on every execution of the loop, and the loop would never be repeated.

In the Average6 example above, lines 19-20 make up the if statement, so the break statement within in is only executed if the value in variable n is equal to the sentinel value. In fact this gives us precisely the effect we want here - a loop where the real exit is in the middle. It ends the awkwardness of Average5 where we read the next integer at the end of the loop, and added it at the beginning, with an extra integer read before the loop to start off.

In general, break should be used with caution. It is easier to see where a loop exits if its exit point is always on failing the loop condition, rather than through a break which may occur at any point in its body. break may be used within non-infinite loops (i.e. ones which do not have true as their condition), but using them within an infinite loop does at least signal that exiting the loop must be done somewhere in its body, while a break inside a non-infinite loop is very easily missed as an alternative from of exit. Note that as an alternative to while(true), the loop heading for(;;) is often used to mean an infinite loop. It means the same, but for(;;) perhaps jumps out more to the eye, indicating immediately that we are dealing with an infinite loop, than does while(true).

Rarely seen, but a valid part of Java, inherited from C, is continue which works similarly to break, but instead of exiting the loop ignores any further execution of the body and starts again at the first statement of the loop.

Reading from a file by input redirection

Suppose the numbers we want to read and average come, not from the user typing them in the command window, but from a file which was previously prepared. Such a file, called nums1, is found in the /import/teaching/BSc/1st/ItP/averages directory. Note that it has to contain the sentinel value to indicate end-of-data. A simple way to redirect input to make it from a file is to do so at Unix level, using Unix's input redirection facility. Assuming file Average6.java has been compiled, if the Java interpreter is run with the Unix command:
java Average6 < nums1
it will take its input from the file nums1 (this file must be in the same directory, of course) rather than from what is typed in at the command window. In general, any Unix command can be made to take its input from some file by adding < filename to it, where filename is the name of the file in question. Output direction is similar - adding > filename to the end of a Unix command causes output to be redirected to the file named filename. Note that this output redirection will create a new file of the given name is one doesn't exist already, and it will overwrite i.e. destroy all the existing information in a file if one of that name exists already. However, if you want output redirection to append to an exiting file rather than overwrite it, all you do is use >> filename, i.e. with a double greater than sign. You may combine input redirection and output redirection in one Unix command.

Running Average6 with input redirection, however, has a silly effect. The prompts, that made sense when it was the user typing in the numbers in response to them, are still printed, even though it makes no sense to have them when the information is not being entered interactively. So if we are intending our program to be used for input redirected from a file, we should not have any prompts. The file Average7.java contains a program which is just like Average6, but lacks the prompt on line 17 of Average6.java (and is commented to indicate this is so).

Opening a file for reading inside a program

However, Unix input redirection is a very limited way to get input from a file. What if we want some input to come interactively from the user, and some from a file, or input to come from several different files? Java provides ways to do this, and the program below gives an example:
 1  import java.io.*;
 2
 3  class Average8
 4  {
 5  /*
 6  A program that calculates the average of a series of integers read
 7  from a file. A sentinel value is used to indicate the
 8  end of the series. 
 9  The program prompts for the name of the file the numbers are to be read
10  from.
11  */
12  
13   static final int SENTINEL = -999;
14
15   public static void main(String[] args) throws IOException
16   {
17    BufferedReader in = Text.open(System.in);
18    BufferedReader fileReader;
19    String filename;
20    int sum=0,count=0,n;
21    System.out.print("Enter file name to read from: ");
22    filename=Text.readString(in);
23    fileReader=Text.open(filename);
24    n=Text.readInt(fileReader);
25    while(n!=SENTINEL)
26       {
27        sum+=n;
28        count++;
29        n=Text.readInt(fileReader);
30       }
31    System.out.println("Average is: "+Text.writeDouble((double)sum/count,5,3));
32   }
33  }
This works as Average6, it shouldn't be used with input direction, but instead takes input from a file whose name is typed by the user, in response to a prompt, when the program is run. The way it works is to open an additional BufferedReader which is attached to the file rather than to the "standard input" (i.e. input from the command window or from command window redirection). In the examples you have seen so far, you have always had just one BufferedReader called in attached to the standard input using a declaration like that in line 17 in Average8. In Average8 the second BufferedReader is declared on line 18, where it is given the name fileReader, but it isn't given a value and hence attached to anything until line 23. As you can see from line 23, the open method from class Text can take the name of a file given in an ordinary string, and return a BufferedReader attached to the file with that name. Then, if that BufferedReader is given as the argument to the various reading methods in Text in the place of in which we have always used up till now, they will read from that file rather than from the standard input. So line 29 shows readInt with the variable fileReader as its argument, meaning that it will read an integer from the file to which fileReader was attached in line 23.

Exceptions

The program in Average8 will run fine if the file that is named at the prompt exists, and if it contains the sentinel value -999. It will stop reading the input when it comes across the first -999. A file called nums2 in the averages directory can be used to demonstrate that - the numbers after -999 are ignored. Given the behaviour of Text, it can cope with files that don't just contain integers, though it will print out the message Error in number, try again for each word it encounters - try it on file nums3. This message is really more appropriate for interactive input. However, if in response to the prompt for a file name you type in one that does not exist, say nums0, the program will stop and the following will be printed in the command screen:
java.io.FileNotFoundException: nums0
        at java.lang.Throwable.(Compiled Code)
        at java.lang.Exception.(Compiled Code)
        at java.io.IOException.(Compiled Code)
        at java.io.FileNotFoundException.(Compiled Code)
        at java.io.FileInputStream.(Compiled Code)
        at java.io.FileReader.(Compiled Code)
        at Text.open(Compiled Code)
        at Average8.main(Compiled Code
If you type in the name of a file that exists, but does not contain the sentinel value -999 (an example exists with name nums4 in the averages directory) the program will stop and the following will be printed in the command screen:
java.io.EOFException
        at java.lang.Throwable.(Compiled Code)
        at java.lang.Exception.(Compiled Code)
        at java.io.IOException.(Compiled Code)
        at java.io.EOFException.(Compiled Code)
        at Text.refresh(Compiled Code)
        at Text.readInt(Compiled Code)
        at Average8.main(Compiled Code
What we have here are two different examples of exceptions. As mentioned briefly previously, exceptions are when execution hits something it was not prepared for. In the first case above, that was trying to open a file that does not exist. In the second case above, that was trying to carry on reading from a file when the end of it had been reached (remember the loop makes it read till it reads -999, so that's what happens when there is no -999 in the file).

Java, however, has a way of constructing programs so they can "expect the unexpected". It is the try construct. The try construct takes the form

try { Java statements }

followed by at least one catch clauses. Note that the { and } are compulsory even if there is only one Java statement between them. A catch clause takes the form

catch(exception type exception name) { Java statements }

The exception type is the name of the particular type of exception the catch clause deals with. The type of exception where an attempt is being made to read past the end of a file is called a java.io.EOFException while the type of exception where an attempt is made to access a file that does not exist is called a java.io.FileNotFoundException. You can miss off the java.io. bit in any file which has the line import.java.io.*; in it. As you can see, these are the names that were in the run-time error messages. The exception name is the name of a variable which holds the details of the particular occurrence of the exception. It can be any name, at this point we won't make use of it. A try construct may also have an extra finally part after its catch clauses, but we won't consider that here.

The effect of a try construct is that the Java statements between the brackets of the try bit are executed as normal, but if an exception occurs anywhere in the execution, rather than the program just halting, the exception may be caught by one of the catch clauses. If an exception is of the type indicated in one of the catch clauses, execution of the code inside the try bit stops, but is followed by execution of the code in the relevant catch clause. Following this, program execution continues with the next statement after the whole try construct, and then as normal, rather than halting.

The following program is a variant of Average8 updated to deal with the two exceptions mentioned above:

 1  import java.io.*;
 2
 3  class Average9
 4  {
 5  /*
 6  A program that calculates the average of a series of integers read
 7  from a file. A sentinel value is used to indicate the
 8  end of the series.
 9  The program prompts for the name of the file the numbers are to be read
10  from.
11  */
12
13   static final int SENTINEL = -999;
14
15   public static void main(String[] args) throws IOException
16   {
17    BufferedReader in = Text.open(System.in);
18    BufferedReader fileReader;
19    String filename;
20    int sum=0,count=0,n;
21    System.out.print("Enter file name to read from: ");
22    filename=Text.readString(in);
23    try
24       {
25        fileReader=Text.open(filename);
26        n=Text.readInt(fileReader);
27        while(n!=SENTINEL)
28           {
29            sum+=n;
30            count++;
31            n=Text.readInt(fileReader);
32           }
33       }
34    catch(EOFException e)
35       {
36        System.out.println("No sentinel "+SENTINEL+" found in file");
37       }
38    catch(FileNotFoundException e)
39       {
40        System.out.println("File "+filename+" not found");
41       }
42    System.out.println("Average is: "+Text.writeDouble((double)sum/count,5,3));
43   }
44  }
The complete try construct extends over lines 23 to 41. Line 42 is the statement after the try construct. If during the execution of lines 25-32 an EOFException occurs, the execution of those lines is halted, and line 36 is executed, followed by line 42, and execution would continue as normal if there were any more statements after line 42. If during the execution of lines 25-32 a FileNotFoundException occurs, the execution of those lines is halted, and line 40 is executed, followed by line 42, and execution would continue as normal if there were any more statements after line 42. In both catch clauses the exception deatils are put in a variable named e, but the variable is not used further.

In this case the effect is that if no sentinel is found a message saying that is printed, but the average is calculated anyway since we have correctly counted how many numbers there are in the file and added them. The EOFException could occur (or to use correct Java terminology, be thrown) on attempting to execute line 26 or line 31 (it would only occur from line 26 if there were no numbers at all in the file) and coming against the end of the file being read.

The FileNotFoundException could only be thrown from line 25. It would result in line 40 being executed, printing out the message there which includes the name that was typed in but found not to refer to an openable file. Following this, line 42 would still be executed, so given that count would hold zero, the question mark indicating an attempt to use writeDouble with the result of a division by zero would be printed.

Note that if an exception is thrown in a method which was called by another but there is nothing to catch it there, it could be caught in the method which called it, and if there is a chain of methods which called each other, the exception would be thrown from each one until it was caught or until it was thrown by main. Only when main throws an exception does the program halt and print an error message in the command window.

Now you've seen exceptions, you can see the relevance of the throws part of a method header. Exceptions actually come in two sorts: run-time exceptions and checked exceptions. Run-time exceptions can be dealt with if the programmer wishes or the possibility of them occurring can be ignored. Checked exceptions however, have to be dealt with somehow. If there is no try construct to catch one in a place where it might occur, the method where it might occur has to indicate that it can throw the exception. For example, any input can cause an IOException and an IOException is a checked exception, so a method that does input but does not catch IOExceptions must have throws IOException attached to its header.

Using exceptions for control flow

You can see that in Average9 there is really no need for a sentinel. If we can correctly add up the numbers when the end of file is reached, why not write the program so this is the standard: read the numbers until the end of file is reached? This is done in the program below:
 1  import java.io.*;
 2
 3  class Average10
 2  {
 4  /*
 5  A program that calculates the average of a series of integers read
 6  from a file. The integers are read until the end of file is encountered.
 7  The program prompts for the name of the file the numbers are to be read
 8  from.
 9  */
10
11   public static void main(String[] args) throws IOException
12   {
13    BufferedReader in = Text.open(System.in);
14    BufferedReader fileReader;
15    String filename="";
16    int sum=0,count=0,n;
17    for(;;)
18      {
19       try
20         {
21          System.out.print("Enter file name to read from: ");
22          filename=Text.readString(in);
23          fileReader=Text.open(filename);
24          break;
25         }
26       catch(FileNotFoundException e)
27         {
28          System.out.println("File "+filename+" not found");
29         }
30      }
31    try
32      {
33       for(;;)
34         {
35          n=Text.readInt(fileReader);
36          sum+=n;
37          count++;
38         }
39      }
40    catch(EOFException e)
41      {
42       System.out.print("Average is: "+Text.writeDouble((double)sum/count,5,3));
43       System.out.println();
44      }
45   }
46  }
In this program, lines 33-38 is an "infinite loop", but it always exits as line 35 will eventually try to read past the end of file, cause an EOFException, and cause execution to go to line 42 after the EOFException is caught on line 40. So we have here an infinite loop inside a try construct.

This program also shows an example of a try construct inside an infinite loop. The loop takes up lines 17-30. This is used to keep on prompting for a file name and attempting to open a file of the name typed in until a file is successfully opened. The loop is exited by the break on line 24. Note that break exits from the loop and not just from the try construct. Execution of the body of the loop on lines 17-30 starts with line 21. If a file is successfully opened on line 23, it goes on to line 24, executes the break and then exits the loop, going on to line 31 where it starts reading from the file. If, however, line 23 does not successfully open a file, a FileNotFoundException is thrown, and is caught on line 26. This then causes line 28 to be executed. Since the break on line 24 was never executed, execution continues with the start of the loop again.


Matthew Huntbach

These notes were produced as part of the course Introduction to Programming as it was given in the Department of Computer Science at Queen Mary, University of London during the academic years 1998-2001.

Last modified: 2 Oct 1998