Computer Science 212 - Tutorial 3

Method parameters, program modularity, assertions, and runtime exception throwing

Copyright © 2005 by Dorothy L. Nixon. All rights reserved.



Table of contents

  1. Value parameters vs. reference parameters
  2. Program modularity and error checking
  3. Programmer-friendly vs. user-friendly error messages
  4. Assertions
  5. Throwing runtime exceptions
  6. Exceptions and the runtime stack



Example files (download as ZIP file here)

The example files may be downloaded in a ZIP file here.


Tutorial on method parameters, program modularity, assertions, and runtime exception throwing


  1. Value parameters vs. reference parameters
  2. Compile the application ValueParametersDemo.java and run it. It tests one of the two power methods in class MathUtility, the one which raises a floating-point number to an integer power. The method has the following heading:

       public static double power(float base, int exponent)
    

    This method raises base to the exponent power initializing a local variable to 1, then multiplying that variable by base each time through a loop that repeats exponent times:

          double product = 1;
          for ( int i = 0; i < exponent; i++ )
             product *= base;
          return product;
    

    The above will work only if exponent is non-negative. For negative exponents, the method takes advantage of the fact that raising base to a negative exponent is equivalent to raising 1/base to the positive power 0-exponent. If exponent is negative, the method replaces base by 1/base and replaces exponent by 0-exponent, and then proceeds as it normally would for positive exponents. So, within the method, the following precedes the above loop:

       {
          if ( exponent < 0 )  {
             base = 1/base;
             exponent = 0-exponent;
          }  // if exponent < 0
    

    An important question to ask is: Does changing the values of base and exponent within the method affect anything outside the method? In the main method of class ValueParametersDemo, the variables enteredBase and enteredExponent are given values and passed as parameters to the power method.

          System.out.print("Enter base (floating-point):>");
          float enteredBase = new Float(br.readLine()).floatValue();
          System.out.print("Enter exponent (integer):>");
          int enteredExponent = Integer.parseInt(br.readLine());
    
          double result = MathUtility.power(enteredBase, enteredExponent);
    
          System.out.println(enteredBase + " to the " +
                             enteredExponent + " power is " +
                             result + ".");
    

    After the call to the power method, the values of enteredBase and enteredExponent are printed out, along with the result. Do enteredBase and enteredExponent still have the same values they had before the method call, or have their values been changed via the changing of the values of parameters base and exponent within the method? Run the program again, using a negative exponent this time, and find out.

    In Java, when a parameter is passed to a method, its value is copied to the variable that has been declared as a formal parameter. In the case of primitive data types, this means that there are now two separate copies of the value, one inside the method (the value of the formal paremeter), and one outside the method (the value of whatever expression got passed to the method as an actual parameter). Thus, any change to the value of the formal parameter inside the method does not affect the value of the actual parameter outside the method.

    The above is always true for primitive data types. What happens with object references, including array references, is more complicated. For example, consider SortingDemo.java. Here, the selectionSort method is able to modify - in this case, to rearrange - the contents of the array that has been passed to it as a parameter. After the selectionSort method is finished executing, the array is modified in the main method too, where the contents of the modified array are written to a text file.

    For another example involving an array as a parameter, compile and run SuccessfulSwap.java and look at the source code.

    For another, more interesting example involving object references (other than array references this time) as parameters, compile and run SwapTextGUI.java. For now, don't worry about the details of most of the source code, which will be explained later, in the next tutorial. For now, simply note the following:

    When you run the program, it pops up a window with a text field at the top saying, "Starting on top, ending on bottom," and another text field at the bottom saying, "Starting on bottom, ending on top." It also tells you to press ENTER in the console (your DOS) window). When you do so, the strings in the two text fields swap positions. The one on the top now says, "Starting on bottom, ending on top," whereas the one on the bottom now says, "Starting on top, ending on bottom."

    The actual swapping of strings is performed by the following static method, which takes two JTextField objects (the text fields) as parameters:

       /**
        * Swaps the strings contained in the two
        * specified text fields.
        * 
        * @param field1 - a text field
        * @param field2 - a text field
        */
       public static void swapText(JTextField field1,
                                   JTextField field2)
       {
          String temp = field1.getText();
          field1.setText(field2.getText());
          field2.setText(temp);
       }  // swapText
    

    The swapText method is called by an instance method interactWithConsole, which in turn is called by the main method. In any case, the swapping of strings is an effect that clearly lasts longer than the lifetime of a call to the swapText method. By swapping the strings, the swapText has permanently altered the state of the two JTextField objects. Changing the string displayed by a text field is an example of changing the state of an object.

    When an object reference is passed as a parameter to a method what gets copied to the formal parameter is not the object itself, but a reference to an object. An object reference variable is the name of a memory location in which is stored NOT the object itself, but rather the address of ANOTHER area in memory where the object itself is actually stored.

    Why is changing the state of a parameter object so radically different, in its consequences, from changing the value of a primitive data type parameter? Because passing an object reference as a parameter to a method does not create a new, separate copy of the entire object. It creates only a new, separate reference to the original object. Hence, if the state of an object is changed inside a method. this does affect the object's behavior outside the method as well, after the method is finished executing.

    However, besides changing the state of an object, there is also another, very different way that an object reference parameter can be modified. An object reference parameter can be reassigned to a different object.

    Look now at FailedSwap.java and observe the assignment statements in the swap method, which reassign the parameters s1 and s2 so that s1 points to the String object formerly pointed to by s2, and, conversely, s2 points to the String object formerly pointed to by s1.

       public static void swap(String s1, String s2)
       {
          System.out.println("Inside swap, beginning: "
                             + s1 + s2);
    
          String temp = s1;
          s1 = s2;
          s2 = temp;
    
          System.out.println("Inside swap, end: "
                            + s1 + s2);
       }  // method swap
    

    Compile and run the application FailedSwap.java and observe that the swap does take effect within the swap method itself, but its effects are NOT noticed in the main method, after the swap method is finished executing. In the main method, the variables text1 and text2 still point to the same String objects they pointed to before.

    Note that the swap method does NOT make any changes to the String objects themselves. (It can't, anyway, because String objects are immutable. The String class does not contain any methods that can change the state of a String object.) The only change that the swap makes is a reshuffing of which of its own variables points to which object. Such a change is NOT noticed after the method is finished executing.

    As we saw in SwapTextGUI.java and SuccessfulSwap.java, if an object is passed as a parameter to a method and that method changes the object itself, such a change does remain in effect after the method is finished executing. On the other hand, as we saw in FailedSwap.java, if an object reference parameter is reassigned to a different object, such a change does NOT have any effect on the objects themselves and hence is not noticed by other parts of the program after the method is finished executing.

    Other parts of the program are not affected by the assignment of parameters and local variables within a method. This holds true regardless of whether the parameters are of primitive data types or object reference types.

    Now compile and run the application ValueVsReference.java, which demonstrates, for an array parameter, the differing effects of (1) changing the contents of the array itself, (2) changing which array an array reference points to, and (3) changing a primitive data type parameter, in a case where the argument that has been passed to the method happens to be an element of an array.

    In Java, arrays are objects, though they have a syntax different from that of objects in general. An array reference variable, likewise, is a name of a memory location which contains a pointer to another area in memory where the array itself is actually stored.

    ValueVsReference.java defines a main method and three other methods, modifyPrimitive, modifyArray, and modifyArrayReference.

    The first method, modifyPrimitive, modifies the value of a parameter of type short, a primitive data type. As we saw in ValueParametersDemo.java, such a change has no effect on the rest of the program. The change is not noticed outside the method, after it has been called. In the main method of ValueVsReference.java, the actual parameter that is passed to the call to modifyPrimitive happens to be an element in an array. This fact is of no consequence. The element's type is still a primitive data type (short), and the modifyPrimitive method still does nothing except reassign its own parameter to a new value.

    The second method, modifyArray, changes the CONTENTS of the array that has been passed to it as a parameter. Note that the entire array, not just a single element, is passed as a parameter to the modifyArray method. And, by changing the contents of the array, the modifyArray method changes the array object itself; it does not merely reshuffle references to the array. Hence, the modifyArray method's changes persist after the method is finished executing, and are noticed in the main method.

    The third method, modifyArrayReference, resets its array parameter to point to a new and different array. Afterward, it changes the contents of the new array, with no effect on the old array. The old array remains unchanged, and the new array ceases to exist after the method is finished executing. Hence there are NOT any lasting effects that would be noticed in the main method.


  3. Program modularity and error checking
  4. In SortingDemo.java, recall that the selectionSort method has the following heading:

       private static void selectionSort(short[] array,
                                         int length)
    

    where length is the length of the subarray containing the elements to be sorted. The variable length is needed if we want to allow for the possibility that the array is partially filled.

    In order for the selectionSort method to work properly, length must be a valid subarray length. It cannot be greater than the length of the array itself, or you'll get an ArrayIndexOutOfBoundsException. Also we want length to be non-negative, because there is no such thing as a negative array length or subarray length.

    As an exercise, carefully examine SortingDemo.java to see whether there is any possible way that the length parameter to the selectionSort method can ever reach an inappropriate value. Carefully examine both the main method and the inputFromFile method, step by step.

    If the rest of the program is written correctly, the length parameter will never be out of range. If the rest of the program is written correctly, the selectionSort method will always be called in such a way that the value of the length is between 0 and the length of the array, inclusive.

    So, from the user's point of view, there should be no need for the selectionSort method to print any error messages.

    However, for the programmer's sake, it might be a good idea to include some error-checking to help make the program easier to debug in case other parts were written incorrectly -- or in case the selectionSort method itself was written incorrectly.

    In general, when writing a large program, it's usually a good idea to write it one small piece at a time, each part with some built-in error-checking to help make sure that it is used properly by other parts of the program. This will make the program much easier to debug, especially if it is being written by a team of programmers, or if the program needs to be upgraded later.

    Also, it's often a good idea to test each method (or at least each method which does anything at all complicated) separately from the rest of the program, using a test program (also known as a driver program) whose specific purpose is to test a given method.

    The program SortingDemo.java itself can be considered a driver program for the selectionSort method, because its other methods do nothing else except input and output the contents of the array to be sorted. If used with a variety of different data files, running SortingDemo.java can be a good way to test the selectionSort method's ability to sort a variety of different arrays successfully.

    However, SortingDemo.java cannot be used to test what happens when the selectionSort method is used incorrectly, e.g. with an out-of-range value of the length parameter, because SortingDemo.java is written in such a way that such abnormal conditions never occur.

    But it's a good idea to test a method under abnormal conditions as well as under normal conditions. In this tutorial, we will consider several driver programs which can be used to test abnormal conditions for the selectionSort method, as well as two more programs like SortingDemo.java which can be used to test the selectionSort method under normal conditions for a variety of different arrays.

    First we will consider the following three programs for testing the selectionSort method under abnormal conditions: (1) SortingDemoUnchecked.java, in which the selectionSort method does no error checking, (2) SortingDemoMessages.java, in which the selectionSort method prints error messages if either the length parameter is out of range or the sorting is unsuccessful for any other reason, and (3) SortingDemoBoolean.java, in which the selectionSort method is a boolean method that returns true if successful, boolean if unsuccessful. Each of these programs contain a main method which is intended to test both a selectionSort method and another method isSorted that are defined within the same class. The isSorted method can also be used to check whether the array is sorted.

    In each of these programs, the main method begins by instantiating an array as follows:

       short[] numbers = new short[] {5, -1, 9, 4, 8, 2, -3, 10, 3};
    

    (Note that Java allows you to instantiate an array by specificying values, as above.)

    The main method contains a commented-out section intended to facilitate experimentation with various values of both the array and lengthFilled:

       // To test various alternative values of numbers,
       // both appropriate and inappropriate, uncomment one
       // of the following lines at a time:
       //
       // numbers = new short[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
       // numbers = new short[] {9, 8, 7, 6, 5, 4, 3, 2, 1};
       // numbers = new short[] {2, 1};
       // numbers = new short[] {1};         // trivial array (okay)
       // numbers = new short[] {};          // empty array (okay)
       // numbers = null;                    // no array (not okay)
    
       // To test various alternative values of lengthFilled,
       // both appropriate and inappropriate, uncomment one
       // of the following lines at a time:
       //
       // lengthFilled = 0;                    // appropriate
       // lengthFilled = numbers.length;       // appropriate
       // lengthFilled = -1;                   // inappropriate
       // lengthFilled = numbers.length + 1;   // inappropriate
    

    Observe that the commented-out options for lengthFilled are intended to test boundary cases, i.e. cases right at the boundary between appropriate and inappropriate values. In general, when testing a program or module, it is a good idea to focus on boundary cases whenever possible.

    In each of the above three programs, try uncommenting (removing the double slash from) the beginning of the last statement which sets lengthFilled equal to numbers.length + 1:

       lengthFilled = numbers.length + 1;   // inappropriate
    

    and compile and run the program, and see what happens. Then comment out, again, the line you uncommented:

       // lengthFilled = numbers.length + 1;   // inappropriate
    

    and then try uncommenting another one of the commented-out lines, and see what happens.

    In SortingDemoMessages.java and SortingDemoBoolean.java, the selectionSort method begins by checking whether the value of length is appropriate. It then actually bothers to do the sorting only if the value is appropriate.

    In SortingDemoBoolean.java, the selectionSort method also returns a boolean value indicating whether the sorting was actually done. The return value true if the array was actually sorted, false if the sorting was aborted due to an inappropriate value of length, or if the sorting was unsuccessful for any other reason.

    In the programming languages C and C++, there are many library functions that similarly return a value to indicate whether the function was successful or not. Although the use of a returned value to indicate errors is common in C and C++, it is NOT nearly as common in Java.

    Later in this tutorial, you'll be taught some other means by which a method can signal an error. Two common means of detecting programmer errors are assertons and runtime exceptions, both of which will be discussed later in this tutorial.

    For the sake of ease in debugging and upgrading, it is a good idea to write programs - especially large programs - in as modular a fashion as possible. That is to say, they should be divided into smaller parts (modules) which have as little to do with each other as possible, and which each do their own error-checking to make sure that they do not receive inappropriate information from other parts of the program. When debugging a large program, modularity makes it much easier to determine where the error is. And, when debugging a large program, you absolutely must determine where the error is before you can possibly have any hope of determining what the error is.

    In Java, there are three levels of program modules: (1) methods, (2) classes (the most important kind of module in Java), and (3) packages (directories containing multiple classes). You have already seen the use of packages in the Java library. In addition, very large programs will use packages created by the programmer (or, more likely, by a team of programmers).

    In most methods, it's a good idea to begin by doing error-checking of preconditions - things that should be true before the method begins execution. In most cases this means checking whether the parameters have appropriate values. In our example programs SortingDemoMessages.java and SortingDemoBoolean.java, both the selectionSort and isSorted methods begin by checking preconditions on the value of the length parameter.

    In addition, if a method does something at all complicated like sorting, it's a good idea to do error-checking of postconditions - things that should be true after the method has finished execution, assuming that the preconditions were met. In SortingDemoMessages.java and SortingDemoBoolean.java, the selectionSort method ends by calling the isSorted method to check whether the array has in fact been sorted correctly.

    By checking preconditions, a method checks whether it is being used correctly by other parts of the program. On the other hand, by ckecking postconditions, a method checks whether the method itself has been written correctly. Thus, when runtime errors occur, it can be determined immediately whether a given error has occurred inside a given module or outside it, thereby making it much easier to track down errors.


  5. Programmer-friendly vs. user-friendly error messages
  6. You should think of errors as being divided into the following three categories, which are typically handled in different ways: (1) errors by the programmer of the module you are currently working on, (2) errors by the programmers of other modules using your module, and (3) errors by the user, or due to other circumstances outside any programmer's control.

    For purposes of this course, think of "your module" as being an individual class (with methods as sub-modules). In a very large program, "your module" might also be a package containing many classes, within a program containing many packages.

    The three categories of errors are typically handled as follows:

    1. Errors by the programmer of your module, including both (1) inappropriate parameters to private methods and (2) bugs WITHIN methods, of any access level (public, or private, or the two other in-between access levels you'll learn about later in the course) within your module, or at least any methods carrying out tasks that are at all complicated (such as sorting). You should aim to detect such errors via assertions.
    2. Errors by the programmers of other modules, including inappropriate parameters to public methods. These are typically detected by means of throwing exceptions.
    3. User errors, and other errors beyond programmer control. These should be handled in a user-friendly fashion, which in many cases (though not always) may involve both throwing and catching exceptions.

    Only for the last cetegory of errors it it necessary to display user-friendly errors. The first two categories of errors do NOT need to be handled in a user-friendly fashion, but do need to be handled in a programmer-friendly fashion. In this case, "programmer-friendly" means making the errors easy to debug.

    In this tutorial, we'll discuss how to handle errors of only the first two kinds, i.e. errors by the programmers of both your own module and other modules. As for handling of user errors, you've already seen some simple examples, e.g. the error messages that some of our example programs have printed out when the user didn't type enough command-line arguments. More complicated kinds of user errors, which may need to be handled via throwing and catching exceptions, will be discussed later in this course. (In this tutorial, you'll be taught how to throw runtime exceptions, but not how to catch exceptions.)

    To see what it means to handle errors in a programmer-friendly fashion, let's first see what kinds of error messages can be generated by a version of our selection sort method which does no error-checking of any kind. Look again at SortingDemoUnchecked.java.

    Let's now try uncommenting (removing the double slash from) the beginning of the last statement which sets lengthFilled equal to numbers.length + 1:

       lengthFilled = numbers.length + 1;   // inappropriate
    

    and see what happens. When we compile and run the program, we get the following message:

    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 9
            at SortingDemoUnchecked.selectionSort(SortingDemoUnchecked.java:86)
            at SortingDemoUnchecked.main(SortingDemoUnchecked.java:49)
    

    This error message actually gives us quite a bit of useful information. It tells us (1) that the error was an attempt to access a nonexistent array element outside the range of the array; (2) that the out-of-range index was 9; (3) that the error occurred in the selectionSort method, at line 86 in our program; and (4) that the error occurred when the selectionSort method was called by the main method at line 49.

    However, to see what actually went wrong, we would need to look inside the the nested for loops in the selectionSort method. To debug this error (if we didn't already know what the problem was), we would need to put an output statement (e.g. a System.out.println statement) inside the inner for loop, to output the values of i, j and indexLowest at each iteration.

    It would be desirable not to have to do this in order to debug an error that occurred in a part of the program outside our selectionSort method. The actual problem is an inappropriate value being passed to the length parameter, not anything wrong with our for loops. So, it would be desirable to have an error message telling us simply that the value of the parameter is inappropriate, without requiring us to debug a nested for loop to find the error.

    Let's now comment out, again, the line we uncommented:

       // lengthFilled = numbers.length + 1;   // inappropriate
    

    (In other words, put the double slash back in at the beginning of the line.) Then let's uncomment (remove the double slash from the beginning of) the following line:

       // numbers = null;                    // no array (not okay)
    

    We now get the following error message:

    Exception in thread "main" java.lang.NullPointerException
            at SortingDemoUnchecked.selectionSort(SortingDemoUnchecked.java:86)
            at SortingDemoUnchecked.main(SortingDemoUnchecked.java:49)
    

    A NullPointerException means you tried to access an object that wasn't there because the object reference was null rather than pointing to any actual object. In most cases, a NullPointerException is generated when you try to call an instance method of a nonexistent object. In this particular case, you tried to access an element of a array that did not exist because the array reference was null.

    Again, to locate where the problem occurred, given the above error message, you would need to look inside the nested for loops inside the selectionSort method. It would be much better to have an error message telling us simply that the array parameter was null.


  7. Assertions
  8. Look now at SortingDemoAssertions1.java, which is similar to SortingDemoUnchecked.java except that it uses assertions. If you have Java version 1.4, compile this program by typing:

    javac -source 1.4 SortingDemoAssertions1.java
    

    If you have Java 1.5 or later, just compile it normally, without the -source 1.4 switch. In Java version 1.4, the -source 1.4 switch is needed in order to compile programs containing assertions, because assertions were a new Java language feature as of Java version 1.4.

    Regardless of which version of Java you have, run the program by typing:

    java -ea SortingDemoAssertions1
    

    where the -ea switch means "enable assertions." If a program contains assertions, you have the option of either enabling or disabling them. Assertions are disabled by default, because the program can run more efficiently when assertions are disabled. You should enable assertions when you need to debug a program.

    Since the program SortingDemoAssertions1.java is now working fine, there is no output from the assertions even when they are enabled. To see the assertions in action, let's put some bugs in this program. First, try uncommenting the beginning of the following line:

       // numbers = null;                    // no array (not okay)
    

    Compile and run the program again, with the needed switches, as instructed above. You should see the following:

    Exception in thread "main" java.lang.AssertionError: parameter array is null
            at SortingDemoAssertions1.selectionSort(SortingDemoAssertions1.java:85)
            at SortingDemoAssertions1.main(SortingDemoAssertions1.java:57)
    

    The above message tells us (1) that the error was signalled by an assertion, (2) "parameter array is null" (exactly what we needed to know!), (3) that the problem occurred in the selectionSort method and was signalled there on line 85 (where the assertion is), and (4) that the error occurred when the selectionSort method was called in the main method on line 57.

    The above error message is much better than the one we had before, because it lets us know, right away, that the problem was an invalid parameter value and NOT something wrong with the code inside the selectionSort method itself.

    Now comment out the following line again:

       // numbers = null;                    // no array (not okay)
    

    and uncomment this line instead:

       // lengthFilled = numbers.length + 1;   // inappropriate
    

    Compile and run the program again with the required switches. We get the following error message, which likewise is much more helpful than our previous "ArrayIndexOutOfBoundsException" message.

    Exception in thread "main" java.lang.AssertionError: length 10 not in range [0,9]
            at SortingDemoAssertions1.selectionSort(SortingDemoAssertions1.java:90)
            at SortingDemoAssertions1.main(SortingDemoAssertions1.java:57)
    

    This too is a much better error message than the ArrayIndexOutOfBoundsException we had before. Again, it's better because it lets us know immediately that the problem was an incorrect value being passed to the parameter length, rather than anything wrong with the code inside the selectionSort method itself.

    Look now at the source code. The selectionSort method now begins with the following assertions:

       // Does array exist?
       assert ( array != null ) : "parameter array is null";
    
       // Is length within range?
       assert ( length >= 0 && length <= array.length ) :
              "length " + length + " not in range [0, "
                        + array.length + "]";
    

    An assertion is a program statement of the following form:

       assert booleanExpression : expressionToPrint ;
    

    where booleanExpression is an expression that can be either true or false and represents a condition that should be true when the program is working correctly. Note that booleanExpression is NOT an error condition; it is a condition that SHOULD be true. If the condition is true, the assertion does nothing. If the condition is false, then the program prints an error message containing expressionToPrint.

    The main use of assertions is in checking the parameters of private methods, to enforce any restrictions on the appropriate values of those parameters. In addition, if a method (of whatever access level - public, private, or anything in between) does anything at all complicated such as sorting, it's a good idea to put an assertion at the end to check whether the method actually did what it was supposed to do. Thus, at the end of our selectionSort method, we've put the following assertion:

       // Has the array been sorted successfully?
       assert ( isSorted(array, length) ): "Array not sorted.";
    

    For the required boolean expression, this assertion uses a call to our boolean method isSorted, which checks whether an array is sorted and is a much simpler method than the selectionSort method itself.

    To test whether our selectionSort method can successfully sort a subarray, assuming suitable parameters, we could now run SortingDemoAssertions2.java with a wide variety of input data files. SortingDemoAssertions2.java is similar to our original SortingDemo.java program except that it uses assertions.

    Thus, in our selectionSort method, we've used assertions to test both (1) preconditions (things that need to be true before the method begins executing, e.g. that the parameters have appropriate values) and (2) postconditions (things that should be true when the method is finished executing, assuming that the preconditions were satisfied).

    If you're an experienced programmer, you may also want to read this article on the Sun website about assertions.


  9. Throwing runtime exceptions
  10. In our examples so far, our selectionSort method has been private.

    Suppose we decide to make our selectionSort method public, so that the programmers of other modules can use it. If those programmers misuse our selectionSort method by giving it inappropriate parameters, we want to let them know immediately that they have misused it. They shouldn't have to enable assertions to find this out. They should be informed immediately that the problem is their fault, not ours, and they shouldn't have to look at the innards of our source code to debug such errors. So, to generate error messages for the programmers of other modules, we need a mechanism somewhat like assertions except that they are always enabled. To that end, we will throw runtime exceptions.

    To simulate the situation of our selectionSort method being used by the programmers of another module, let's now divide our program into two classes: (1)class SortingDemoExceptions, which contains only the selectionSort and isSorted methods, both of which are now public, and (2) class SortingDemoExceptionsTest1, which contains only a main method whose purpose is to test the methods of class SortingDemoExceptions.

    The file SortingDemoExceptions.java, which contains the selectionSort and isSorted methods, still uses an assertion at the bottom of the selectionSort method. It throws runtime exceptions rather than using assertions at the top of each method, to check for incorrect parameter values which are not the fault of the programmer of class SortingDemoExceptions. But it still uses an assertion to check whether selectionSort worked properly, given correct parameters, because, if it didn't work properly, this WOULD be the fault of the programmer of class SortingDemoExceptions and not the programmer using it.

    If you have Java version 1.4, Compile SortingDemoExceptionsTest1.java by typing, at the console:

    javac -source 1.4 SortingDemoExceptionsTest1.java
    

    (Otherwise, if you have Java 1.5 or later, compile it the usual way, without the -source 1.4 switch.)

    The original version of SortingDemoExceptionsTest1.java runs normally, without throwing any exceptions, and without any assertion complaining either.

    This program too has a main function with commented-out options. First, try uncommenting the following line:

       // numbers = null;                    // no array (not okay)
    

    Compile the program again, with the needed switch as instructed above, and run it. You should see the following:

    Exception in thread "main" java.lang.NullPointerException: parameter array is null
            at SortingDemoExceptions.selectionSort(SortingDemoExceptions.java:40)
            at SortingDemoExceptionsTest1.main(SortingDemoExceptionsTest1.java:62)
    

    Although this is a NullPointerException, it's a much more programmer-friendly NullPointerException than the one we saw earlier. The above message tells us not only that the problem was a "null pointer," i.e. an attempt to access an object that was nonexistent due to an object reference having a null value, but also, more specifically, that the parameter array was null. It also tells us that the problem occurred in the selectionSort method of class SortingDemoExceptions (it also tells us the line number, but let's assume that the programmer does NOT have access to our source code for class SortingDemoExceptions, so the line number in that file is irrelevant), and (4) that the error occurred when the selectionSort method was called in the main method of class SortingDemoExceptionsTest1, on line 62 (which IS relevant, because SortingDemoExceptionsTest1.java is simulating our hypothetical programmer's errors, so let's assume that the programmer does have access to the source code of class SortingDemoExceptionsTest1.)

    Note that the error message does contain enough information to tell our hypothetical programmer how he or she has misused our selectionSort method. The programmer has been told that the problem is an incorrect argument that has been passed to our method, and the programmer is told the line number - in the programmer's own program - where this was done.

    Now comment out the following line again:

       // numbers = null;                    // no array (not okay)
    

    and uncomment this line instead:

       // lengthFilled = numbers.length + 1;   // inappropriate
    

    Compile the program again with the required switch and run it. We get the following error message:

    Exception in thread "main" java.lang.IndexOutOfBoundsException: length 10 not in range [0, 9]
            at SortingDemoExceptions.selectionSort(SortingDemoExceptions.java:44)
            at SortingDemoExceptionsTest1.main(SortingDemoExceptionsTest1.java:62)
    

    Look now at the source code in SortingDemoExceptions.java. The selectionSort method now begins by checking for erroneous parameter values and throwing runtime exceptions, as follows:

          // Does array exist?
          if ( array == null )
             throw new NullPointerException("parameter array is null");
    
          // Is length within range?
          if ( length < 0 || length > array.length )
             throw new IndexOutOfBoundsException("length " + length
                                                 + " not in range [0, "
                                                 + array.length + "]");
    

    Note that the if clauses are checking for error conditions. Unlike assertions, which contain conditions that should be true if the program is working properly, the above if conditions should be false if the program is working properly. Compare the above if conditions with the assertion conditions in our previous programs, and observe that the if conditions are the exact negations of the corresponding assertion conditions.

    When one of the above if conditions is true, an object of class NullPointerException or class IndexOutOfBoundsException is instantiated, and then it is thrown via the throw clause. Note that throw is a keyword in the Java language. On the other hand, NullPointerException and IndexOutOfBoundsException are not keywords but classes defined in the java.lang package of the Java library. Another runtime exception class you'll use frequently is IllegalArgumentException, commonly used for invalid parameters other than null object references and invalid indices.

    Because exceptions are thrown for the convenience of the programmers of other classes, they should be mentioned in Javadoc comments. See, for example, the Javadoc comment for the selectionSort method in class SortingDemoExceptions:

       /**
        * Sorts a partially-filled array of short
        * integers using selection sort.
        *
        * @param array the array to be sorted.
        * @param length length of filled portion of array.
        *               Must be between 0 and the length
        *               of <code>array</code>, inclusive.
        *
        * @exception NullPointerException if <code>array</code>
        *                 is null
        * @exception IndexOutOfBoundsException if
        *                 <code>length</code> is outside the
        *                 range of possible subarray lengths
        *                 of <code>array</code>
        */
    

    Generate HTML documentation for our example programs by typing, in the DOS (Command Prompt) window:

    javadoc *.java
    

    Then, in your folder window, double-click on index.html to bring up the documentation in a web browser. Then click on SortingDemoExceptions in the column on the left to look at the documentation for that class, and observe how the exceptions are described.

    Look now at SortingDemoExceptionsTest2.java, which is the kind of program a quality control worker might use to test your selectionSort method using a variety of input data files. Like the programmers of other modules, a QC worker should not need to look at your source code either.


  11. Exceptions and the runtime stack
  12. Consider again the kinds of messages you see when exceptions are thrown or when assertions are activated. Ugly though they may look, and intimidating though they may be to a non-programmer, they do contain information that could be very useful to someone trying to debug a program. Among other things, they list all the methods that were being executed at the time the runtime error occurred. They list the method in which the exception was thrown or the assertion was activated; and then they list the method which called that method; and then the method which, in turn, called that method; and so on, down to the main method. They also list the relevant line numbers in the source code files containing the listed methods.

    Internally, the computer needs to keep track of what methods are being executed (or waiting for a method they called to finish executing) at any given time. This information is stored in a region of memory known as the runtime stack, which also contains, among other things, the memory that has been allocated for all the parameters and local variables of all the methods currently being executed. You will learn more about the runtime stack later in the course. The listing of method calls in a runtime error message is known as a trace of the runtime stack.

    To gain a bit more insight into what a runtime stack trace is, compile and run StackTraceDemo.java, which generates a series of stack traces, handling the exceptions so that they don't halt the program. (The exceptions are handled via a try-catch block. Do not worry about try-catch blocks for now. You will be taught about them later in the course.) Stack traces are generated by the method throwAndCatch, which is called at various times by all the other methods in the program. Run it while looking carefully at the source code to see, step by step, which methods should be calling which other methods at various times.

    In StackTraceDemo.java, we've used only static methods, no instance methods or constructors. For an example involving instance methods and a constructor as well as atatic methods, compile and run StackTraceDemo2.java. Run this program too while looking carefully at the source code to see, step by step, which methods should be calling which other methods at various times.

    In a runtime stack trace, constructor calls are shown via expressions of the form ClassName.<init>, and all other method calls, both static and instance, are show via expressions of the form ClassName.methodName -- although, in the case of instance methods, this syntax is different from the way the methods are called in the program.



Computer Science 212:  main page