[ Chapter Index | Main Index ]

Answers for Quiz on Chapter 8

This page contains sample answers to the quiz on Chapter 8 of Introduction to Programming Using Java. Note that generally, there are lots of correct answers to a given question.

Question 1:

What does it mean to say that a program is robust?

Answer:

A robust program is one that can handle errors and other unexpected conditions in some reasonable way. This means that the program must anticipate possible errors and respond to them if they occur.

Question 2:

Why do programming languages require that variables be declared before they are used? What does this have to do with correctness and robustness?

Answer:

It's a little inconvenient to have to declare every variable before it is used, but its much safer. If the compiler would accept undeclared variables, then it would also accept misspelled names and treat them as valid variables. This can easily lead to incorrect programs. When variables must be declared, the unintentional creation of a variable is simply impossible, and a whole class of possible bugs is avoided.

Question 3:

What is a precondition? Give an example.

Answer:

A precondition is a condition that has to hold at given point in the execution of a program, if the execution of the program is to continue correctly. For example, the statement "x = A[i];" has two preconditions: that A is not null and that 0 <= i < A.length. If either of these preconditions is violated, then the execution of the statement will generate an error.

Also, a precondition of a subroutine is a condition that has to be true when the subroutine is called in order for the subroutine to work correctly.

Question 4:

Explain how preconditions can be used as an aid in writing correct programs.

Answer:

Suppose that a programmer recognizes a precondition at some point in a program. This is a signal to the programmer that an error might occur if the precondition is not met. In order to have a correct and robust program, the programmer must deal with the possible error. There are several approaches that the programmer can take. One approach is to use an if statement to test whether the precondition is satisfied. If not, the programmer can take some other action such as printing an error message and terminating the program. Another approach is to use a try statement to catch and respond to the error. This is really just a cleaner way of accomplishing the same thing as the first approach. The best approach, when it is possible, is to ensure that the precondition is satisfied as a result of what has already been done in the program. For example, if the precondition is that x >= 0, and the preceding statement is "x = Math.abs(y);", then we know that the precondition is satisfied, since the absolute value of any number is greater than or equal to zero.

Question 5:

Java has a predefined class called Throwable. What does this class represent? Why does it exist?

Answer:

The class Throwable represents all possible objects that can be thrown by a throw statement and caught by a catch clause in a try..catch statement. That is, the thrown object must belong to the class Throwable or to one of its (many) subclasses such as Exception and RuntimeException. The object carries information about an exception from the point where the exception occurs to the point where it is caught and handled.

Question 6:

Write a method that prints out a 3N+1 sequence starting from a given integer, N. The starting value should be a parameter to the method. If the parameter is less than or equal to zero, throw an IllegalArgumentException. If the number in the sequence becomes too large to be represented as a value of type int, throw an ArithmeticException.

Answer:

The problem of large values in a 3N+1 sequence was discussed in Section 8.1. In that section, it is pointed out that the test "if (N > 2147483646/3)" can be used to test whether the value of N has become too large. This test is used in the following method.

static void printThreeNSequence(int N) {
      // Print the 3N+1 sequence starting from N.  If N
      // is not greater than 0 or if the value of N exceeds
      // the maximum legal value for ints, than an
      // exception will be thrown.
   if (N < 1) {
      throw new IllegalArgumentException(
                  "Starting value for 3N+1 sequence must be > 0.");
   }
   System.out.println("3N+1 sequence starting from " + N " is: ");
   System.out.println(N);
   while (N > 1) {
      if (N % 2 == 0) {  // N is even.  Divide by 2.
          N = N / 2;
      }
      else {  // N is odd.  Multiply by 3 and add 1.
          if (N > 2147483646/3) {
             throw new ArithmeticError("Value has exceeded the largest int.");
          }
          N = 3 * N + 1;
      }
      System.out.println(N);
   }
}

(Note that it would be possible to declare that this routine can throw exceptions by adding a "throws" clause to the heading:

static void printThreeNSequence(int N)
           throws IllegalArgumentException, ArithmeticException {

However, this is not required since IllegalArgumentExceptions and ArithmeticExceptions do not require mandatory exception handling.)

Question 7:

Rewrite the method from the previous question, using assert statements instead of exceptions to check for errors. What the difference between the two versions of the method when the program is run?

Answer:

We can replace the if statements that check for errors with assert statements that give the same error messages:

static void printThreeNSequence(int N) {
      // Print the 3N+1 sequence starting from N.
      // Precondition:  N > 0 and the 3N+1 sequence for N does not contain
      // any numbers that are too big to be represented as 32-bit ints.
   
   assert  N > 0 : "Starting value for 3N+1 sequence must be > 0.";

   System.out.println("3N+1 sequence starting from " + N " is: ");
   
   System.out.println(N);
   while (N > 1) {
      if (N % 2 == 0) {  // N is even.  Divide by 2.
          N = N / 2;
      }
      else {  // N is odd.  Multiply by 3 and add 1.
          assert  N <= 2147483646/3 : "Value has exceeded the largest int.";
          N = 3 * N + 1;
      }
      System.out.println(N);
   }
   
}

The first version of the method will always check for errors when the program is run. The second version, on the other hand, does not actually do any error checking when the program is run in the ordinary way. In order for assert statements to be executed, the program must be run with assertions enabled. The assert statements are really there only to do error-checking during debugging and testing. (In this particular case, I would say that an exception should definitely be thrown when N exceeds the maximum legal value, but that it's reasonable to use an assert to check whether N > 0.)

Question 8:

Some classes of exceptions require mandatory exception handling. Explain what this means.

Answer:

Subclasses of the class Exception which are not subclasses of RuntimeException require mandatory exception handling. This has two consequences: First, if a method can throw such an exception, then it must declare this fact by adding a throws clause to the method heading. Second, if a routine includes any code that can generate such an exception, then the routine must deal with the exception. It can do this by including the code in a try statement that has a catch clause to handle the exception. Or it can add a throws clause to the method definition to declare that calling the method might throw the exception.

Question 9:

Consider a subroutine processData() that has the header

static void processData() throws IOException

Write a try..catch statement that calls this subroutine and prints an error message if an IOException occurs.

Answer:
try {
   processData();
}
catch (IOException e) {
   System.out.println("An IOException occurred while processing the data.");
}
Question 10:

Why should a subroutine throw an exception when it encounters an error? Why not just terminate the program?

Answer:

Terminating the program is too drastic, and this tactic certainly doesn't lead to robust programs! It's likely that the subroutine doesn't know what to do with the error, but that doesn't mean that it should abort the whole program. When the subroutine throws an exception, the subroutine is terminated, but the program that called the subroutine still has a chance to catch the exception and handle it. In effect, the subroutine is saying "Alright, I'm giving up. Let's hope someone else can deal with the problem."

Question 11:

Suppose that a program uses a single thread that takes 4 seconds to run. Now suppose that the program creates two threads and divides the same work between the two threads. What can be said about the expected execution time of the program that uses two threads?

Answer:

The execution time will depend on whether the program is being run on a computer that has more than one processor. If so, the execution time could be as little as 2 seconds, since each of two processors can do half of the 4-seconds worth of work. If the computer has only one processor, however, the two-threaded program will still take 4 seconds, since all the work will have to be done by the single processor.

Question 12:

Consider the ThreadSafeCounter example from Subsection 8.5.3:

public class ThreadSafeCounter {
   
   private int count = 0;  // The value of the counter.
   
   synchronized public void increment() {
      count = count + 1;
   }
   
   synchronized public int getValue() {
      return count;
   }
   
}

The increment() method is synchronized so that the caller of the method can complete the three steps of the operation "Get value of count," "Add 1 to value," "Store new value in count" without being interrupted by another thread. But getValue() consists of a single, simple step. Why is getValue() synchronized? (This is a deep and tricky question.)

Answer:

The getValue() method has to be synchronized because of the caching of local data that was discussed in Subsection 8.5.5. If getValue() were not synchronized, it is possible that a thread that calls getValue() would see an old, cached value of count rather than the most current value. Synchronization ensures that the most current value of count will be seen. If count were declared to be a volatile variable, then getValue() would not have to be synchronized. However, increment() would still need to be synchronized to prevent the race condition.


[ Chapter Index | Main Index ]