CHAPTER 2

Additional Features of Programming and Java

In Chapter 1, the primary goal was to introduce object-oriented concepts, such as interfaces, inheritance and polymorphism, in the context of the Java programming language. This chapter introduces more topics on programming in general and Java in particular, and illustrates how they can aid your programming efforts. For example, Java's exception-handling mechanism provides programmers with significant control over what happens when errors occur.

CHAPTER OBJECTIVES

  1. Distinguish between static members and instance members.
  2. Be able to develop JUnit tests for a class's methods.
  3. Be able to create try blocks and catch blocks to handle exceptions.
  4. Compare file input/output with console input/output.
  5. Understand the fundamentals of the Java Virtual Machine.
  6. Be able to override the Object class's equals method.
  7. Understand the interplay between packages and visibility modifiers.

2.1 Static Variables, Constants and Methods

Recall, from Section 1.2.2, that a class member is either a field or method in the class1. Let's look at some of the different kinds of members in Java. There are two kinds of fields. An instance variable is a field associated with an object—that is, with an instance of a class. For example, in the FullTimeEmployee class from Chapter 1, name and grossPay are instance variables. Each FullTimeEmployee object will have its own pair of instance variables. Suppose we declare

FullTimeEmployee oldEmployee,
                 currentEmployee,
                 newEmployee;

Then the object referenced by oldEmployee will have its own copy of the instance variables name and grossPay, and the objects referenced by currentEmployee and newEmployee will have their own copies also.

In addition to instance variables, which are associated with a particular object in a class, we can declare static variables, which are associated with the class itself. The space for a static variable—also known as a class variable —is shared by all instances of the class. A field is designated as a static variable by the reserved modifier static. For example, if a count field is to maintain information about all objects of a class student, we could declare the field count to be a static variable in the student class:

protected static int count = 0;

This static variable could be incremented, for example, whenever a student constructor is invoked. Then the variable count will contain the total number of student instances created.

A class may also have constant identifiers, also called "symbolic constants" or "named constants". A constant identifier is an identifier that represents a constant, which is a variable that can be assigned to only once. The declaration of a constant identifier includes the reserved word final—indicating only one assignment is allowed—as well as the type and value of the constant. For example, we can write

protected final static int SPEED_LIMIT =  65.0;

Constant identifiers promote both readability (SPEED_LIMIT conveys more information than 65.0) and maintainability (because SPEED_LIMIT is declared in only one place, it is easy to change its value throughout the class). There should be just one copy of the constant identifier for the entire class, rather than one copy for each instance of the class. So a constant identifier for a class should be declared as static; constants within a method cannot be declared as static. At the developer's discretion, constant identifiers for a class may have public visibility. Here are declarations for two constant class identifiers:

public final static char COMMAND_START = '$';

public final static String INSERT_COMMAND = "$Insert";

To access a static member inside its class, the member identifier alone is sufficient. For example, the above static field count could be accessed in a method in the Student class as follows:

count++;

In order to access a static member outside of its class, the class identifier itself is used as the qualifier. For example, outside of the wrapper class Integer, we can write:

if  (size == Integer.MAX_VALUE)

Here is the declaration for an often-used constant identifier in the System class:

public final static PrintStream out = nullPrintStream();

Because out is declared as static, its calls to the PrintStream class's printin method include the identifier System rather than an instance of the System class. For example,

System.out.println ("The Young Anarchists Club will hold a special election next week"  +
                    "to approve the new constitution.");

The static modifier is used for any constant identifier that is defined outside of a class's methods. The static modifier is not available within a method. For example, in the definition of the default constructor in the FullTimeEmployee class, we had:

final String EMPTY_STRING =   "";

It would have been illegal to use the static modifier for this constant.

In Chapter 1, the Employee interface declared a constant:

final static DecimalFormat MONEY = new DecimalFormat  ("  $0.00");
             // a class constant used in formatting a value in dollars and cents

The constant identifier MONEY can be used in any class that implements the Employee interface. Recall that any constant or method identifier in an interface automatically has public visibility.

Java also allows static methods. For example, the Math class in the package java.lang has a floor method that takes a double argument and returns, as a double, the largest value that is less than or equal to the argument and equal to an integer. We can write

System.out.println (Math.floor (3.7));  // output: 3.0

When floor is called, there is no calling object because the effect of the method depends only on the double argument. To signify this situation, the class identifier is used in place of a calling object when floor is called. A method that is called without a calling object is known as a static method, as seen in the following heading

public static double floor (double a)

The execution of every Java application (excluding applets, servlets, and so on) starts with a static main method. And static methods are not virtual; that is, static methods are bound to method identifiers at compile time, rather than at run time. The reason is that static methods are associated with a class itself rather than an object, so the issue of which object is invoking the method does not arise.

2.2 Method Testing

A method is correct if it satisfies its method specification. The most widely used technique for increasing confidence in the correctness of a method is to test the method with a number of sample values for the parameters. We then compare the actual results with the results expected according to the method's specification.

The purpose of testing is to discover errors, which are then removed. When—eventually—no errors are discovered during testing, that does not imply that the method is correct, because there may be other tests that would reveal errors. In general, it is rarely feasible to run all possible tests, and so we cannot infer correctness based on testing alone. As E. W. Dijkstra has noted:

Testing can reveal the presence of errors but not the absence of errors.

The testing software we utilize is JUnit: the "J" stands for "Java," and each method in a project is referred to as a "unit." JUnit is an Open Source (that is, free) unit-testing product—available from www.junit.org—that allows the methods in a class to be tested systematically and without human intervention—for example, without keyboard input or Graphical User Interface (GUI) mouse clicks. The web page http://junit.sourceforge.net/README.html#Installation has information on installation. In general, the success or failure of an individual test is determined by whether the expected result of the test matches the actual result. The output from testing provides details for each failed test. For a simple example, here is a test of the toString() method in the FullTimeEmployee class:

@Test
public void toStringTest1()
{
     FullTimeEmployee full = new FullTimeEmployee  ("a",  150.00);

     String expected =   "a $150.00 FULL TIME";

     assertEquals  (expected,   full.toString());
} // method testToStringl

"@Test " is referred to as an annotation. The assertEquals method determines whether its arguments are equal. In this case, if the two strings are equal, the test is passed. Otherwise, the test fails.

The assertEquals method is an overloaded method in the Assert class of the org.junit package. The heading for the version called above is

public static void assertEquals (java.lang.String expected, java.lang.String actual)

There are other versions of the method to compare primitive values. For example, we could have

int median = roster.findMedian(); assertEquals (82, median);

Also, there is a version to compare any two objects. Here is the method heading:

public static void assertEquals (java.lang.Object expected, java.lang.Object actual)

According to the Subclass Substitution Rule, the arguments can be instances of any class—because any class is a subclass of Object. In fact, expected is a polymorphic reference when the AssertEquals method is invoked: the code executed depends on the types of the objects involved. For example, with String objects, this version has the same effect as the version in which both parameters have type String. (But this version is slightly slower than the String-parameter version due to the run-time test to make sure that actual is an instance of the String class.)

Finally, there are several assertArrayEquals methods for comparing two arrays of int values, two arrays of double values, two arrays of Object references, and so on.

The details of running JUnit test classes will depend on your computing environment. Typically, you will run the tests in an Integrated Development Environment (IDE) such as Eclipse or DrJava, and the output of testing may combine text and graphics (for example, a green bar if all tests were successful, and a red bar if one or more tests failed). For the sake of generality, the following test class is not tied to any IDE, but simply prints the string returned by the getFailures() method in the runClasses class of the package org.junit. If you are running tests in an IDE, the main method from the class below will be ignored. Here is a complete class for testing the toString() method in the FullTimeEmployee class—followed by a discussion of the details:

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;
import static org.junit.runner.JUnitCore.runClasses;

public class FullTimeEmployeeTest
{
    public static void main(String[ ] args)
    {
        Result result = runClasses (ToStringTest.class);
        System.out.println ("Tests run = " + result.getRunCount() +
                            "
Tests failed = " + result.getFailures());
    } // method main

    protected FullTimeEmployee full;

    protected String expected;


    @Test
    public void toStringTest1()
    {
        full = new FullTimeEmployee ("a", 150.00);
        expected = "a $150.00 FULL TIME";
        assertEquals (expected, full.toString());
    } // method toStringTest1


    @Test
    public void toStringTest2()
    {
        full = new FullTimeEmployee ("b", 345.678);
        expected = "b $345.678 FULL TIME";     // error!
        assertEquals (expected, full.toString());
    } // method toStringTest2


    @Test
    public void toStringTest3()
    {
        full = new FullTimeEmployee();
        expected = " $0.00 FULL TIME";
        assertEquals (expected, full.toString());
    } // method toStringTest3


 } // class FullTimeEmployeeTest

The line

import static org.junit.Assert.*;

allows static methods—such as assertEquals—in the Assert class to be accessed without specifying the class name. The main method runs this cluster of tests, one after the other, in no particular order. Because of the mistake in test 2, the output from the program is

Tests run = 3

Tests failed = [toStringTest2(FullTimeEmployeeTest): expected:<b $345.6[7]8 FULL TIME>

but was:<b $345.6[]8 FULL TIME>]

Note that the mistake—the extra "7" in the expected value—was in the test, not in the method being tested, and was written just so you would know what is output when a test fails.

When should these tests be developed and run? Most unit-testing enthusiasts recommend that

A method's tests should be developed before the method is defined.

The advantage to pre-definition testing is that the testing will be based on the method specification only, and will not be influenced by the method definition. Furthermore, the tests should be run both before and after the method is defined (and after any subsequent changes to the method definition). That will illustrate the transition from a method that fails the tests to a method that passes the tests. But how can a method be compiled before it is defined? To satisfy the Java compiler, each method definition can be a stub: a definition that has only enough code to avoid a compile-time error. For example, here is a stub for the toString() method in the FullTimeEmployee class:

public String toString()
{
      return null;
} // method toString

When FullTimeEmployeeTest was run with this stub for the toString( ) method, all three tests failed. (Of course, the mistake in test2 ensures that test will fail anyway.) Because this chapter introduces unit testing intermingled with several important language features, the method testing in this chapter will be presented after the method has been fully defined. In subsequent chapters we will adhere to the test-first paradigm.

In Section 2.3.2 we will see how to create a stub that will fail any test even if the return type of the method to be tested is boolean.

2.2.1 More Details on Unit Testing

In Section 2.2, we developed a test suite for the toString( ) method in the FullTimeEmployee class. There was so little that could go wrong with the toString( ) method that we could barely justify the testing. (In fact, in the applications in subsequent chapters, a class's toString( ) method will often be untested, but used in testing other methods.) The real purpose of the example was to show how a test suite could be developed. What about the other methods in the FullTimeEmployee class? Should they be tested also? Probably not. The constructors cannot be tested in isolation; in fact, you could argue that the suite in FullTimeEmployeeTest tests the constructors as much as testing the toString( ) method. Also, there is no point in testing the accessor methods getName( ) and getGrossPay ( ): they simply return the values assigned in a constructor.

So at this point, we can be fairly confident in the correctness of the methods in the FullTime Employee class. For the Company class from Chapter 1, which methods are suitable for testing? There is no point in testing the main method: it simply invokes the run( ) method. The run( ) method cannot be tested without human intervention because the end user must enter the input-file path from the keyboard. The protected method getNextEmployee (Scanner sc) can be tested—the CompanyTest class will be a subclass of the Company class. Finally, the findBestPaid (Scanner sc) method can and should be tested. In fact, that method was, originally, designed to facilitate testing: The reading of file name and printing of the best-paid employee were moved up to the run( ) method. This illustrates an important aspect of method design:

In general, methods should be designed to facilitate testing.

Here is a CompanyTest class to test both the getNextEmployee and findBestPaid methods. Note that the @Before annotation precedes any method that is automatically invoked just prior to each test.

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;
import static org.junit.runner.JUnitCore.runClasses; import java.util.*;

import java.io.*; // for IOException, see Section 2.3


public class CompanyTest extends Company
{
   public static void main(String[ ] args)
   {
        Result result = runClasses (CompanyTest.class);
        System.out.println ("Tests run = " + result.getRunCount() +
                            "
Tests failed = " + result.getFailures());
   } // method main


   protected Company company;


   protected FullTimeEmployee best;


   protected Scanner sc;


   protected String expected;


   @Before
   public void runBeforeEveryTest()
   {
        company = new Company();
   } // method runBeforeEveryTest


   @Test
   public void getNextEmployeeTest1()
   {
        sc = new Scanner ("Lucas 350.00");
        expected = "Lucas $350.00 FULL TIME";
        assertEquals (expected, company.getNextEmployee (sc).toString());
   } // method getNextEmployeeTest1


   @Test
   public void findBestPaidTest1() throws IOException
   {
        sc = new Scanner (new File ("company.in1"));
        best = company.findBestPaid (sc);
        expected = "b $150.00 FULL TIME";
        assertEquals (expected, best.toString());
   } // method findBestPaidTest1


  @Test
  public void findBestPaidTest2() throws IOException
  {
        sc = new Scanner (new File ("company.in2"));
        best = company.findBestPaid (sc);
        assertEquals (null, best);
  } // method findBestPaidTest2
} // class CompanyTest

The file company.inl contains

a 100
b 149.995
c 140

The file company.in2 is empty. When the above tests were run with the versions of getNextEmployee (Scanner sc) and findBestPaid (Scanner sc) from Chapter 1, all three test cases were successful.

For the HourlyEmployee class, the only method worthy of testing is the three-parameter constructor. As noted earlier in this section, constructors cannot be tested in isolation. Instead, we will test the getRegularPay, getOvertimePay, and getGrossPay methods. These accessor methods are worthy of testing since they do more than simply returning values passed to a constructor. The important aspect of the following HourlyEmployeeTest class is the testing of a boundary condition: the comparison (> =, >, <=, <) of a variable to some fixed value; the result of the comparison determines the action taken. Specifically, the hoursWorked is compared to 40 to determine the regular pay, overtime pay, and gross pay. To make sure that all boundary cases are covered, there are separate tests for hoursWorked equal to 39, 40, and 41. In comparing the expected result with the actual result for regularPay, overTimePay, and grossPay, we should not compare double values for exact equality. So we utilize a three-parameter assertEquals method, with the third parameter the (very small) difference we will allow between the expected and actual double values.

Make sure that any boundary conditions are thoroughly tested.

Here is the HourlyEmployeeTest class:

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;
import static org.junit.runner.JUnitCore.runClasses;
import java.util.*;


public class HourlyEmployeeTest
{
    public static void main(String[ ] args)
    {
        Result result = runClasses (HourlyEmployeeTest.class);
        System.out.println ("Tests run = " + result.getRunCount() +
                            "
Tests failed = " + result.getFailures());
    } // method main


    public static final double DELTA = 0.0000001;


    protected HourlyEmployee hourly;


   @Test
   public void test1()
   {
        hourly = new HourlyEmployee ("andrew", 39, 10.00);
        assertEquals (390.00, hourly.getRegularPay(), DELTA);

        assertEquals (0.00, hourly.getOvertimePay(), DELTA);
        assertEquals (390.00, hourly.getGrossPay(), DELTA);
   } // method test1


   @Test
   public void test2()
   {
        hourly = new HourlyEmployee ("beth", 40, 20.00);
        assertEquals (800.00, hourly.getRegularPay(), DELTA);
        assertEquals (0.00, hourly.getOvertimePay(), DELTA);
        assertEquals (800.00, hourly.getGrossPay(), DELTA);
   } // method test2


   @Test
   public void test3()
   {
        hourly = new HourlyEmployee ("terry", 41, 20.00);
        assertEquals (800.00, hourly.getRegularPay(), DELTA);
        assertEquals (30.00, hourly.getOvertimePay(), DELTA);
        assertEquals (830.00, hourly.getGrossPay(), DELTA);
   } // method test3


   @Test
   public void test4()
   {
        hourly = new HourlyEmployee ("karen", 50, 10);
        assertEquals (400.00, hourly.getRegularPay(), DELTA);
        assertEquals (150.00, hourly.getOvertimePay(), DELTA);
        assertEquals (550.00, hourly.getGrossPay(), DELTA);
   } // method test4


} // class HourlyEmployeeTest

What about testing other methods? There is no rule to determine which methods in a class should be tested. The best strategy is to assume that a method contains subtle flaws that can be revealed only by rigorous testing.

Good testing requires great skepticism.

This can be a challenge to programmers, who tend to view their work favorably, even glowingly ("a thing of beauty and a joy forever"). As such, programmers are ill-suited to test their own methods because the purpose of testing is to uncover errors. Ideally the person who constructs test data should hope that the method will fail the test. If a method fails a test and the method is subsequently revised, all tests of that method should be re-run.

In the next section, we introduce Java's exception-handling facilities, and consider the interplay between exception-handling and testing.

2.3 Exception Handling

An exception is an object created by an unusual condition, typically, an attempt at invalid processing. When an exception object is constructed, the normal flow of control is halted; the exception is said to be thrown. Control is immediately transferred to code—either in the current method or in some other method—that "handles" the exception. The exception handling usually depends on the particular exception, and may involve printing an error message, terminating the program, taking other action, or maybe doing nothing.

A robust program is one that does not terminate unexpectedly from invalid user-input. We almost always prefer programs that—instead of "crashing"—allow recovery from an error such as the input of 7.o instead of 7.0 for a double. Java's exception-handling feature allows the programmer to avoid almost all abnormal terminations.

For a simple introduction to exception handling, let's start with a method that takes as a parameter a (non-null reference to a) String object. The String represents a person's full name, which should be in the form "first-name middle-name last-name". The method returns the name in the form "last-name, first-name middle-initial.". For example, if we have

rearrange  ("John Quincy Adams"))

The String returned will be

Adams,  John Q.

Here is the method specification and a preliminary definition:

/**
 * Returns a specified full name in the form "last-name, first-name middle-initial.".
 *
 * @param fullName - a (non-null reference to a) String object that represents the
 *                    specified full name, which should be in the form
 *                    "first-name middle-name last-name".
 *
 * @return the name in the form "last-name, first-name middle-initial.".
 *
 */
public String rearrange (String fullName)
{
       Scanner sc = new Scanner (fullName);


       String firstName = sc.next(),
              middleName = sc.next(),
              lastName = sc.next();


       return lastName + ", " + firstName + " " + middleName.charAt (0) + ".";
} // method rearrange

The problem with this method, as currently defined, is that the execution of the method can terminate abnormally. How? If the argument corresponding to fullName is a (reference to a) String object that does not have at least three components separated by whitespace, a NoSuchElementException object will be thrown. In this case, the execution of the method will terminate abnormally. Instead of an abnormal termination, we want to allow execution to continue even if the argument corresponding to fullName is not a reference to a String that consists of those three components. That is, we "try" to split up fullName, and "catch" the given exception. The revised specification and definition are

/**
 *  Returns a specified full name in the form "last-name, first-name middle-initial.".
 *
 * @param fullName - a (non-null reference to a) String object that represents the
 *                    specified full name, which should be in the form
 *                    "first-name middle-name last-name".
 *
 * @return the name in the form "last-name, first-name middle-initial." if fullName
 *          has three components. Otherwise, return
 *          "java.util.NoSuchElementException: the name is not of the form
 *          "first-name middle-name last-name"".
 *
 */
public String rearrange (String fullName)
{
     String result;


     try
      {
        Scanner sc = new Scanner (fullName);


        String firstName = sc.next(),
               middleName = sc.next(),
               lastName = sc.next();


        result = lastName + ", " + firstName + " " + middleName.charAt (0) + ".";
      } // try
      catch (NoSuchElementException e)
      {
        result = e.toString() + ": " + ": The name is not of the form  "first-name " +
                 "middle-name last-name"";
      } // catch
      return result;
} // method rearrange

In the execution of this method, the flow of control is as follows. Inside the try block, if fullName can be split into first, middle, and last names, the three calls to sc.next( ) and the subsequent assignment to result will be executed. The entire catch block will be skipped over, and the return statement will be executed. But if fullName cannot be split into first, middle, and last names, one of the calls to sc.next( ) will throw a NoSuchElementException object, the try block will be exited, and the statement inside the catch block will executed. Then the return statement will be executed, as before.

In the catch block, the parameter e is (a reference to) the NoSuchElementException object created and thrown during the execution of one of the calls to sc.next( ). Specifically, e.toString( ) is the string "java.util.NoSuchElementException". We will see shortly, in Section 2.3.1, how an exception can be thrown in one method and caught in another method.

Here is a test class for the rearrange method (assume that method is in the NameChange class, which may consist of nothing except the rearrange method):

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;

import static org.junit.runner.JUnitCore.runClasses;
import java.util.*;

public class NameChangeTest
{
   public static void main(String[ ] args)
   {
      Result result = runClasses (NameChangeTest.class);
      System.out.println ("Tests run = " + result.getRunCount() +
                          "
Tests failed = " + result.getFailures());
   } // method main
   public final static String EXCEPTION = "java.util.NoSuchElementException";

   public final static int EXCEPTION_LENGTH = EXCEPTION.length();

   protected NameChange change;

   protected String result;

   @Before
   public void runBeforeEveryTest( )
   {
      change = new NameChange();
   } // method runBeforeEveryTest

   @Test
   public void rearrangeTest1()
   {
      result = change.rearrange ("John Quincy Adams");
      assertEquals ("Adams, John Q.", result);
   } // method rearrangeTest1

   @Test
   public void rearrangeTest2()
   {
      result = change.rearrange ("John Adams");
      assertEquals (EXCEPTION, result.substring (0, EXCEPTION_LENGTH));
   } // method rearrangeTest2

   @Test
   public void rearrangeTest3()
   {
      result = change.rearrange ("John");
      assertEquals (EXCEPTION, result.substring (0, EXCEPTION_LENGTH));
   } // method rearrangeTest3

   @Test
   public void rearrangeTest4()
   {
      result = change.rearrange ("");
      assertEquals (EXCEPTION, result.substring (0, EXCEPTION_LENGTH));
   } // rearrangeTest4

} // class NameChangeTest

In this example, the exception was handled—in the catch block—of the rearrange method. In the next section, we see how to handle exceptions that are not caught within the method in which they are thrown.

2.3.1 Propagating Exceptions

What happens if an exception, such as NoSuchElementException, is thrown in a method that does not catch that exception? Then control is transferred back to the calling method: the method that called the method that threw the exception. This transferring of control is known as propagating the exception. For example, the following method determines whether or not an integer scanned in is a leap year2 (one of the exceptions is explicitly thrown, with a throw statement):

/**
 * Determines if the integer scanned in is a leap year.
 *
 * @param sc - a (reference to) a Scanner object from which
 *             the year is scanned in.
 *
 *  @return true - if the integer is a leap year; otherwise, returns false.
 *
 * @throws InputMismatchException - if the string scanned in from sc is not
 *                                empty but does not consist of an integer.
 * @throws NoSuchElementException - if the value scanned in from sc is an
 *          empty string.
 *
 * @throws NullPointerException - if sc is null.
 * @throws IllegalArgumentException - if the value scanned in from
 *          sc is an integer less than 1582.
 */
public boolean isLeapYear (Scanner sc)
{
     final int FIRST_YEAR = 1582; // start of Gregorian Calendar


     int year = sc.nextInt();


     if (year < FIRST_YEAR)
         throw new IllegalArgumentException();
     if ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0))
         return true;
     return false;
} // method isLeapYear

What can go wrong in a call to this method? One possible error, as indicated in the @throws sections of the javadoc specification, if the string scanned in from sc is not empty but does not consist of an integer, InputMismatchException will be thrown. This exception is not caught in the isLeapYear method, so the exception is propagated back to the method that called isLeapYear. For example, the following LeapYear class has a run() method that scans five lines from System.in, and determines which lines contain leap years and which lines contain non-integers.

import java.util.*; // for the Scanner class

public class LeapYear
{

   public static void main (String args [ ])
   {
       new LeapYear().run();
   } // method main



   public void run()
   {
       final String INPUT_PROMPT = "Please enter the year: ";


       Scanner sc = new Scanner (System.in);


       for (int i = 0; i < 5; i++)
           try
           {
               System.out.print (INPUT_PROMPT);
               System.out.println (isLeapYear (sc));
           } // try
           catch (InputMismatchException e)
           {
               System.out.println ("The input is not an integer.");
               sc.nextLine();
           } // catch InputMismatchException
   } // method run



   public boolean isLeapYear (Scanner sc)
   {
       final int FIRST_YEAR = 1582; // start of Gregorian Calendar


       int year = sc.nextInt();


       if (year < FIRST_YEAR)
              throw new IllegalArgumentException();


       if ((year % 4 == 0) && (year % 100 != 0 || year % 400 == 0))
           return true;
       return false ;
   } // method isLeapYear


} // class LeapYear

For input of

2000
2100
2010

2010
2008

the output will be:

true
false
The input is not an integer.
false
true

The above catch block includes a call to sc.nextLine(). If that call had been omitted, the output for the above input would be

true
false
The input is not an integer.
The input is not an integer.
The input is not an integer.

Why? When the third call to sc.nextInt() in isLeapYear throws InputMismatchException for "201o", the scanner remains positioned on the third line instead of advancing to the fourth line. Then the next two calls to sc.nextInt( ) also throw InputMismatchException for "201o". We needed to include sc.nextLine( ) in the catch block to ensure that the scanner skips over the illegal input.

It is worth noting that in a method's specification, only propagated exceptions are included in the @throws javadoc comments. Any exception that is caught within the method definition itself (such as we did in the rearrange method of Section 2.3) is an implementation detail, and therefore not something that a user of the method needs to know about.

Incidentally, without too much trouble we can modify the above run method to accommodate an arbitrary number of input values. To indicate the end of the input, we need a value—called a sentinel —that is not a legal year. For example, we can use "***" as the sentinel. When that value is entered from the keyboard, InputMismatchException is thrown in the isLeapYear method and caught in the run method, at which point a break statement terminates the execution of the scanning loop. Here is the revised run method:

public void run()
{
  final String SENTINEL = "***";


  final String INPUT_PROMPT =
     "Please enter the year (or " + SENTINEL + " to quit): ";


  Scanner sc = new Scanner (System.in);
  while (true)
  {
     try
     {
         System.out.print (INPUT_PROMPT);
         System.out.println (" " + isLeapYear (sc) + "
");
     } // try
     catch (InputMismatchException e)

     {
         if (sc.nextLine().equals (SENTINEL))
             break;
         System.out.println (" The input is not an integer.
");
     } // catch
  } // while
} // method run

If a propagated exception is not caught in a method, the exception is propagated back to the calling method. If the calling method does not handle the exception, then the exception is propagated back to the method that called the calling method itself. Ultimately, if the exception has not been caught even in the main method, the program will terminate abnormally and a message describing the exception will be printed. The advantage to propagating an exception is that the exception can be handled at a higher level in the program. Decisions to change how exceptions are handled can be made in one place, rather than scattered throughout the program. Also, the higher level might have facilities not available at lower levels, such as a Graphical User Interface (GUI) window for output.

2.3.2 Unit Testing and Propagated Exceptions

How can we test a method that propagates an exception? Right after the @Test annotation, we specify the expected exception. For example, a test of the isLeapYear method might have

@Test (expected = InputMismatchException.class)
public void isLeapYearTest()
{
      leap.isLeapYear  (new Scanner ("201o"));
} // isLeapYearTest

For a complete test suite of the isLeapYear method, we cannot scan over System.in because such tests would require human intervention. Another option is to scan over a string of lines that contain the values to be tested. But in JUnit, test methods can be invoked in any order, and the results of one test do not affect any other test. So to ensure that the calls to the scanner would start on successive lines, we would have to place all tests in one method. This would be legal but inappropriate because the tests are independent of each other.

The following test suite for the isLeapYear method has each test in a separate method, and

includes tests for InputMismatchException, NoSuchElementException, NullPointerException and IllegalArgumentException.

Here is the test class:

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;
import static org.junit.runner.JUnitCore.runClasses;
import java.util.*;

public class LeapYearTest
{
   public static void main(String [  ] args)
   {
      Result result = runClasses (LeapYearTest.class);
      System.out.println ("Tests run = " + result.getRunCount() +
                  "
Tests failed = " + result.getFailures());
   } // method main

protected LeapYear leap;

protected boolean answer;

@Before
public void runBeforeEveryTest()
{
   leap = new LeapYear();
} // method runBeforeEveryTest


@Test
public void leapYearTest1()
{
   answer = leap.isLeapYear (new Scanner ("2000"));
   assertEquals (true, answer);
} // method leapYearTest1


@Test
public void leapYearTest2()
{
   answer = leap.isLeapYear (new Scanner ("2100"));
   assertEquals (false , answer);
} // method leapYearTest2


@Test
public void leapYearTest3()
{
   answer = leap.isLeapYear (new Scanner ("1582"));
   assertEquals (false , answer);
} // method leapYearTest3


@Test (expected = InputMismatchException.class)
public void leapYearTest4()
{
   leap.isLeapYear (new Scanner ("201o"));
} // method leapYearTest4


@Test (expected = NoSuchElementException.class)
public void leapYearTest5()
{
   leap.isLeapYear (new Scanner (""));
} // method leapYearTest5


@Test (expected = NullPointerException.class)
public void leapYearTest6()
{
     leap.isLeapYear (null);
} // method leapYearTest6


@Test (expected = IllegalArgumentException.class)
public void leapYearTest7()
{
     leap.isLeapYear (new Scanner ("1581"));

   } // method leapYearTest7

} // class LeapYearTest

What if the exception propagated in the method being tested is not the exception expected in the testing method? Then the testing method will generate an error message that the exception thrown was not the one expected. Finally, what if the method being tested propagates an exception but no exception was expected by the testing method? For example, at the start of leapYearTest5, suppose we replaced

@Test  (expected = NoSuchElementException.class)

with

@Test

Then the test would generate the following error message:

Tests failed = [leapYearTest5(LeapYearTest): null]

The keyword null signifies that an exception was thrown but no exception was expected. In JUnit, an error in running a test method occurs if an unexpected exception is thrown or if an expected exception is not thrown. So it is an error if an exception is thrown but a different exception is expected. Errors are included in the string returned by the getFailures() method in the runClasses class, but the term failure is often applied only to those situations in which an assertion is tested and fails. Because testing assertions is what unit testing is all about, errors must be removed before serious testing can begin.

We defined the above isLeapYear method before we introduced the exception-propagation feature needed to test that method. What if, as is normally the case, we wanted to test a method before the method is defined? Specifically, how can we create a stub that will generated an error message for all of the above tests? If the stub returns true, leapYearTest1() will succeed, and if the stub returns false, leapYearTest2() will succeed. Clearly, the stub cannot return either true or false. Instead, the stub will throw an exception other than the exceptions thrown according to the specifications. For example,

public boolean isLeapYear  (Scanner sc)
{
      throw new UnsupportedOperationException();
}  // method isLeapYear

When the test suite LeapYearTest was run on this stub, every test generated an error message (that is good news), and the output (formatted for readability) was

Tests run = 7
Tests failed =
        leapYearTest1(LeapYearTest): null
        leapYearTest2(LeapYearTest): null,
        leapYearTest3(LeapYearTest): null,
        leapYearTest4(LeapYearTest): Unexpected exception,
              expected<java.util.InputMismatchException> but
              was<java.lang.UnsupportedOperationException>,
        leapYearTest5(LeapYearTest): Unexpected exception,
              expected<java.util.NoSuchElementException> but
              was<java.lang.UnsupportedOperationException>,
        leapYearTest6(LeapYearTest): Unexpected exception,
              expected<java.lang.NullPointerException> but
              was<java.lang.UnsupportedOperationException>,

        leapYearTest7(LeapYearTest): Unexpected exception,
              expected<java.lang.IllegalArgumentException> but
              was<java.lang.UnsupportedOperationException>]

2.3.3 Checked Exceptions

Exceptions related to input or output, such as when a file is not found or the end-of-file marker is encountered while input is being read, are the most common examples of checked exceptions. With a checked exception, the compiler checks that either the exception is caught within the method itself or—to allow propagation of the exception—that a throws clause is appended to the method heading. For an example of the latter, we might have

public void sample()  throws IOException
{

This indicates that the sample method might throw an IOException object. If so, the exception will be propagated back to the method that called sample. That calling method must either catch IOException or append the same throws clause to its method heading. And so on. Checked exceptions are propagated for the same reason that other exceptions are propagated: It might be preferable to handle all exceptions at a higher level for the sake of uniformity, or there might be better facilities (such as a GUI window) available at the higher level.

For an example of how a checked exception can be handled in a method, we can revise the run( ) method from Section 2.3.1 to scan lines from a file and determine which lines consist of leap years. The name of the file will be read from the keyboard in a loop that continues until the name corresponds to an existing file. Here is the revised run( ) method:

public void run()
{
    final String INPUT_PROMPT = "Please enter the file name: ";

    Scanner keyboardScanner = new Scanner (System.in);


    String fileName;


    while (true)
    {
       System.out.print (INPUT_PROMPT);
       fileName = keyboardScanner.next();
       try
       {
           Scanner sc = new Scanner (new File (fileName));
           while (sc.hasNext())
              try
              {
                 System.out.println (isLeapYear (sc));
              } // try to scan a year
              catch (InputMismatchException e)
              {
                 System.out.println ("The input is not an integer.");
                 sc.nextLine();
              } // catch input mismatch
           break;

       } // try to scan the name of an existing file
       catch (FileNotFoundException e)
       {
           System.out.println (e);
       } // catch file not found
    } // while true
} // method run

The break statement—to exit the outer loop—is executed when the file name scanned from the keyboard represents an existing file, and that file has been scanned for leap years. The inner-loop condition—sc.hasNext()—is slightly preferable to sc.hasNextLine(). In particular, if the last line in the file is blank, sc.hasNext() will return false and the execution of the inner loop will terminate, as desired. But if the last line in the file is blank, sc.hasNextLine() will return true, and the subsequent call to sc.nextInt() in the isLeapYear method will throw NoSuchElementException. Of course, if that exception is caught in the run() method, then sc.hasNextLine() will not be a problem.

A checked exception must be caught or must be specified in a throws clause, and the compiler "checks" to make sure this has been done. Which exceptions are checked, and which are unchecked? The answer is simple: run-time exceptions are not checked, and all other exceptions are checked. Figure 2.1 shows Java's exception hierarchy, including IOException with its subclasses (such as FileNotFound Exception), and RuntimeException with its subclasses (such as NullPointerException).

Why are run-time exceptions not checked? The motivation behind this is that an exception such as NullPointerException or NumberFormatException, can occur in almost any method. So appending a throws clause to the heading of such a method would burden the developer of the method without providing any helpful information to the reader of that method.

When an exception is thrown, the parameter classes of the subsequent catch blocks are tested, in order, until (unless) one is found for which the thrown exception is an instance of that class. So if you want to ensure that all run-time exceptions are caught in a method, you can insert the following as the last catch block:

FIGURE 2.1 The exception hierarchy. In the unified modeling language, inheritance is represented with an arrow from a subclass to its superclass

image

catch (RuntimeException e)
{
     // code to handle the exception
} // catch RuntimeException

If you do have a catch block for RuntimeException, make sure that catch block is not followed by a catch block for a subclass of RuntimeException. For example, because NullPointerException is a subclass of RuntimeException, the following sequence of catch blocks will generate a compile-time error:

catch (RuntimeException e)
{
     // code to handle the exception
} // catch RuntimeException
catch (NullPointerException e)   // error!
{
     // code to handle the exception
} // catch NullPointerException

The error message will inform you that the second catch block is unreachable code.

An exception can be explicitly thrown by the programmer, who gets to decide which exception class will be instantiated and under what circumstances the exception will be thrown. For example, suppose we want a method to return the smaller of two double values that represent prices obtained by comparison shopping. If the prices are too far apart—say, if the difference is greater than the smaller price—we throw an exception instead of returning the smaller price. The mechanism for explicitly throwing the exception is the throw statement, which can be placed anywhere a statement is allowed. For example, the code may be as in the following smaller method (the Math class's static method abs returns the absolute value of its argument):

public class Compare
{
     public static void main (String[ ] args)
     {
          new Compare().run();
     } // method main

     public void run()
     {
          System.out.println (smaller (5.00, 4.00));
          System.out.println (smaller (5.00, 20.00));
     } // method run

     public double smaller (double price1, double price2)
     {
          if (Math.abs (price1 - price2) > Math.min (price1, price2))
               throw new ArithmeticException ("difference too large");
          return Math.min (price1, price2);
     } // method smaller

} // class Compare

If the given comparison is true, the throw statement is executed, which creates a new instance of the exception class ArithmeticException by calling the constructor that takes a String argument. The exception will be propagated back to the method that called smaller and the execution of the smaller method will immediately terminate. In the above example, the exception is not caught, so the program terminates. The output is

4.0

java.lang.ArithmeticException: difference too large

The choice of ArithmeticException as the exception class to be instantiated is somewhat arbitrary.

A user can even create new exception classes. For example,

public class UnreasonablenessException extends RuntimeException
{
      public UnreasonablenessException (String s)
      {
            super  (s);
      } // constructor with String parameter

} // class UnreasonablenessException

We can rewrite the smaller method to throw this exception:

public double smaller (double price1, double price2)
{
    if (Math.abs (price1 - price2) > Math.min (price1, price2))
          throw new UnreasonablenessException ("difference too large");
    return Math.min (price1, price2);
} // method smaller

This creates a new instance of the class UnreasonablenessException. The above program would terminate with the message:

UnreasonablenessException: difference too large

The UnreasonablenessException class is a subclass of RuntimeException. The Runtime Exception class handles3 some of the low-level details of exception-handling, such as keeping track of the method the exception occurred in, the method that called that method, and so on. Such a "call stack" sequence can help a programmer to determine the root cause of an error.

An alternative to explicitly throwing an exception is to take a default action that overcomes the mistake. For example, here is a version of the 2-parameter constructor in the FullTimeEmployee class that replaces a negative value for gross pay with 0.00:

public FullTimeEmployee (String name, double grossPay)
{
     this.name = name;
     this.grossPay = Math.max  (grossPay,  0.00);
} // 2-parameter constructor

2.3.4 The finally Block

Under normal circumstances, any code you place after the last catch block will be executed whether or not any exceptions were thrown in the try block. So you can place clean-up code—such as closing files—after the last catch block. There are two drawbacks to this approach. First, there may be an exception thrown in the try block that is not caught in a catch block. Another danger is that one of the catch blocks may itself throw an exception that is not caught. Then the clean-up code will not be executed. To avoid these pitfalls, Java allows a finally block after the last catch block. We can write

try
{
          ... // code that may throw an exception
} // try
catch (NumberFormatException e)
{
          ... // code to handle NumberFormatException
} // catch NumberFormatException
catch (IOException e)
{
          ... // code to handle IOException
} // catch IOException
finally
{
          ... // clean-up code; will be executed even if there are uncaught
              // exceptions thrown in the try block or catch blocks.
} // finally

If your try or catch blocks may throw uncaught exceptions, you should include a finally block—otherwise, any code placed after the last catch block may not be executed. Finally, a finally block is required by the Java language if you have a try block without a catch block.

Lab 2 provides the opportunity for you to practice exception-handling.

You are now prepared to do Lab 2: Exception Handling

The handling of input-output exceptions is one of the essential features of file processing, discussed in Section 2.4.

2.4 File Output

File output is only slightly different from console output. We first associate a PrintWriter reference with a file name. For example, to associate printWriter with "scores.out":

PrintWriter printWriter = new PrintWriter (new BufferedWriter
                         (new FileWriter ("scores.out")));

The PrintWriter object that is referenced by printWriter can now invoke the print and println methods. For example,

printWriter.println (line);

The output is not immediately stored in the file "scores.out". Instead, the output is stored in a buffer: a temporary storage area in memory. After all calls to print and println have been made by print Writer's object, that object's close method must be called:

printWriter.close();

The close method flushes the buffer to the file "scores.out" and closes that file.

The file-processing program we will develop in this section is based on a program from Section 0.2.5 of Chapter 0. That program calculates the sum of scores read in from the keyboard. Here is a slightly modified version of that program, with a separate method to scan in and add up the scores:

import java.util.*; // for the Scanner class


public class Scores1
{
   public final int SENTINEL = -1;


   public static void main (String[ ] args)
   {
      new Scores1().run();
   } // method main


   public void run()
   {
      final String INPUT_PROMPT = "
On each line, enter a test score (or " +
                                   SENTINEL + " to quit): ";


      final String RESULT = "
 nThe sum of the scores is ";

      Scanner sc = new Scanner (System.in);


      System.out.print (INPUT_PROMPT);


      int sum = addScores (sc);


      System.out.println (RESULT + sum);
   } // method run


   /**
    *  Returns the sum of the scores scanned in.
    *
    * @param sc - a (non-null reference to a) Scanner object from
    *               which the scores are scanned in.
    *
    * @return the sum of the scores scanned in from sc.
    *
    * @throws InputMismatchException - if a value scanned in from sc is not an
    *                                   integer.
    *
    */
   public int addScores (Scanner sc)
   {

     int score,
         sum = 0;


     while (true)
     {
        score = sc.nextInt();
        if (score == SENTINEL)
             break;
        sum += score;
     } // while
     return sum;
   } // method addScores

} // class Scores1

In the next version, the output goes to a file. To enable someone reading that file to confirm that the result is correct for the given input, each score is written to the output file. IOException is caught for output-file creation. The corresponding try block encompasses the creation of the output file and the input loop. For the sake of simplicity, there is no try block to catch input-mismatch exceptions (arising from input values that are not integers).

import java.util.*;

import java.io.*;

public class Scores2
{
    public final int SENTINEL = -1;


    public static void main (String [ ] args)
    {
        new Scores2().run();
    } // method main



    public void run()
    {
        final String INPUT_PROMPT =
            "
On each line, enter a test score (or " + SENTINEL + " to quit): ";

        final String RESULT = "

The sum of the scores is ";

        PrintWriter printWriter = null; // to ensure that printWriter is initialized
                                        // before it is closed in the finally block

        try
        {
            Scanner sc = new Scanner (System.in);
            printWriter = new PrintWriter (new BufferedWriter
                              (new FileWriter ("scores.out")));

            System.out.print (INPUT_PROMPT);
            addScores (sc, printWriter);
        } // try

        catch (IOException e)
        {
            System.out.println (e);
        } // catch IOException
        finally
        {
            printWriter.println (RESULT + sum);
            printWriter.close();
        } // finally
    } // method run


    public int addScores (Scanner sc, PrintWriter printWriter)
    {

        int score,
            sum = 0;

        while (true)
        {
            score = sc.nextInt();
            if (score == SENTINEL)
              break;
            printWriter.println (score);
            sum += score;
        } // while
        return sum;
    } // method addScores

} // class Scores2

The simplification of ignoring input-mismatch exceptions leads to an unfortunate consequence: If an input-mismatch exception is thrown, the program will terminate without printing the final sum. The output file will be closed before the final sum is printed, and the InputMismatchException message—signifying abnormal termination—will be printed. We could add a catch block for InputMismatchException right after (or right before) the catch block for IOException. This change would not be much of an improvement: The program would still terminate without printing the final sum, but the termination would be normal.

To enable the program to continue after an input-mismatch exception, we create a new try block and a corresponding catch block inside the while loop. If the input contains no legal scores, we throw an exception related to that after the while loop. Here is the revised code:

boolean atLeastOneScore = false;
while (true)
{
      try
      {
           score = sc.nextInt();
           if (score == SENTINEL)
                   break;
           printWriter.println (score);
           sum += score;
           atLeastOneScore = true;
      } // try

      catch (InputMismatchException e)
      {
           printWriter.println (e + " " + sc.nextLine());
      } // catch InputMismatchException
} // while
if (!atLeastOneScore)
    throw new RuntimeException ("The input contains no legal scores. ");

Here is a sample run of the resulting program, with input in boldface:

Please enter a test score, or −1 to quit: 50
Please enter a test score, or −1 to quit: x
Please enter a test score, or −1 to quit: 80
Please enter a test score, or −1 to quit: y
Please enter a test score, or −1 to quit: -1
The execution of this project has ended.

The file scores.out will now contain the following:

50
java.lang.InputMismatchException: x
80
java.lang.InputMismatchException: y

The sum of the scores is 130

The call to nextLine( ) in the catch block of the addScores method allows the offending input to be printed to the output file, and also allows the scanner to skip over that line (otherwise, the input prompt will be continuously repeated, and the output file will continuously get copies of the exception message.

The most important fact to remember about file output is that the file writer must be explicitly closed, or else the file will be incomplete, and probably empty (depending on whether there was an intermediate flushing of the buffer). As we will illustrate in the next class, Scores3, we can ensure that a file writer is closed when (if) a program terminates by enveloping the construction of the file writer in a try block, which is followed by a finally block that closes the file writer.

For this final version of the program, we scan from an input file (with one score per line) instead of from the keyboard. As we saw in Section 0.2.5—file input is almost identical to console input. For example, to read from the file "scores.in1", we start with

Scanner fileScanner = new Scanner (new File ("scores.in1"));

Warning: This assumes that the file scores.in1 is in the expected directory. For some Integrated Development Environments, the input file is assumed to be in the directory that is one level up from the source-file directory. Sometimes, you may need to specify a full path, such as

Scanner fileScanner = new Scanner (new File
                      ("c: \projects\score_project\scores.in1"));

Two back-slashes are required because a single back-slash would be interpreted as the escape character.

Input files seldom end with a sentinel because it is too easy to forget to add the sentinel at the end of the file. Instead, scanning continues as long as the next() or nextLine() method returns true. So for file input, we write

while  (fileScanner.hasNext())

For the sake of simplicity, if there is only one input file, we will not worry about closing that file at the end of the program: it will automatically be closed. And when it is re-opened in a subsequent program, its contents will be unchanged. A program that leaves many input files unclosed can run out of file descriptors, and an IOException will be thrown.

As noted earlier in this section, closing an output file entails copying the final contents of the file buffer to the file, so we should explicitly close each output file before the end of a program. Of course, if the program does not terminate—due to an infinite loop, for example—the file buffer will not be copied (unless the file was closed before the infinite loop).

The following program combines file input and file output. For the sake of generality, the program does not "hardwire" the file names (for example, "scores.in" and "scores.out"). In response to prompts, the end-user enters, from the keyboard, the names of the input and output files. If there is no input file with the given name, FileNotFoundException is caught, an error message is printed, and the end-user is re-prompted to enter the name of the input file. To allow this iteration, the try and catch blocks that involve throwing and handling IOException are placed in an outer while loop.

What if there is no file corresponding to the output file name? Normally, this is not a problem: an empty output file with that name will be created. But if file name is too bizarre for your system, such as

!@#$%"Θ*()

an IOException object (specifically, a FileNotFoundException object) will be thrown. The following program has three try blocks:

  1. an outermost try block to set up the files and process them, a catch block to handle a Number FormatException if the input contains no legal scores, followed by a finally block to close the file writer;
  2. a try block/catch block sequence in an outer while loop to create the file scanner and file writer from file names scanned in from the keyboard;
  3. a try block/catch sequence block in an inner while loop to scan each line from the input file and process that line, with output going to the file writer. If the input contains no legal scores, a NumberFormatException is thrown after this loop.

Here is the program, whose general structure is the same for all file-processing programs:

import java.util.*;


import java.io.*;


public class Scores3
{
   public static void main (String [ ] args)
   {
      new Scores3().run();
   } // method main


   public void run()
   {
      final String IN_FILE_PROMPT =
          "
Please enter the name of the input file: ";

   final String OUT_FILE_PROMPT =
       "
Please enter the name of the output file: ";

   final String RESULT = "

The sum of the scores is ";

   Scanner keyboardScanner = new Scanner (System.in),
           fileScanner;


   PrintWriter printWriter=null; // to ensure that printWriter has been initialized
                                  // before it is closed in the finally block

   int sum = 0;


   try
   {
       while (true)
       {
           try
           {
               System.out.print (IN_FILE_PROMPT);
               fileScanner=new Scanner (new File (keyboardScanner.nextLine()));
               System.out.print (OUT_FILE_PROMPT);
               printWriter=new PrintWriter (new BufferedWriter
                                 (new FileWriter (keyboardScanner.nextLine())));
               sum = addScores (fileScanner, printWriter);
               break;
           } // try
           catch (IOException e)
           {
               System.out.println (e);
           } // catch
       } // while files not OK
   } // try
   catch (NumberFormatException e)
   {
       System.out.println (e);
   } // catch NumberFormatException
   finally
   {
       printWriter.println (RESULT + sum);
       printWriter.close();
   } // finally
} // method run

/**
 * Returns the sum of the scores scanned in.
 *
 *  @param fileScanner - the Scanner object from which the scores are scanned
 *
 *  @param printWriter - the PrintWriter object to which the scores are written.
 *           If a score generates InputMismatchException, the message

 *           "java.util.InputMismatchException: " precedes the score.
 *
    * @return the sum of the scores scanned in from fileScanner.
    *
    * @throws NumberFormatException - if the values scanned in do not include
    *                                 an integer.
    *
    */
   public int addScores (Scanner fileScanner, PrintWriter printWriter)
   {
      final String NO_LEGAL_SCORES_MESSAGE=
          "The input contains no legal scores.";

      int score,
          sum=0;

      boolean atLeastOneScore=false;

      while (fileScanner.hasNext())
      {
          try
          {
              score=fileScanner.nextInt();
              printWriter.println (score);
              sum+=score;
              atLeastOneScore=true ;
          } // try
          catch (InputMismatchException e)
          {
              printWriter.println (e+": "+fileScanner.nextLine());
          } // catch InputMismatchException
      } // while more scores in input file
      if (!atLeastOneScore)
          throw new NumberFormatException (NO_LEGAL_SCORES_MESSAGE);
      return sum;
   } // method addScores

} // class Scores3

Note that the message printWriter.close() is not in a catch block because the printWriter should be closed whether or not any exceptions are thrown.

Assume that the file "scores.ini" consists of the following four lines:

82
8z
77
99

Also, assume that there is no file named "scores.inO" or "scores3.in" in the working directory. Whether there is already a file named "scores.outi" or not is irrelevant. Here is a sample keyboard session, with input in boldface:

Please enter the name of the input file:  scores.in0
java.io.FileNotFoundException: scores.inO  (The system cannot find the file specified)
Please enter the name of the input file:  scores3.in
java.io.FileNotFoundException: scores3.in  (The system cannot find the file specified)

Please enter the name of the input file:  scores.in1

Please enter the name of the output file:  scores.out1

The final contents of the file "scores.out1" will be

82
java.util.InputMismatchException: 8z
77
99

The sum of the scores is 258

With file input, it is not sufficient that the file exist in order to associate a file scanner with that file. Your code must also account for the possibility that the file does not exist. The easy way to accomplish this is to include a throws FileNotFoundException clause immediately after the heading of the method that associates a file scanner with the file. The drawback to this approach is that if the file name is incorrect—if either the file does not exist or the file name is misspelled—then the end-user will not have the opportunity to correct the mistake.

A better alternative, as we did in the run() method of the class Scores3, is to include a try block and catch block for FileNotFoundException. To enable end-users to recover from incorrect file names, those blocks should be within a loop that continues until a correct file name has been entered. Similarly, to construct a file writer, IOException must be caught or declared in a throws clause. That is why, in the above program, the type of the relevant catch-block parameter is IOException instead of FileNotFoundException.

There is a common thread in the above examples. The run() method handles the aspects of the program that require the end user's intervention, such as input from the keyboard or from a GUI window, or interpretation, such as output to the console window or to a GUI window. Accordingly, the method calledbythe run() method should be testable in JUnit.

The major problem in testing the addScores method above is that the method outputs information to a file. So we will create an expected output file from a given input file, and check to make sure the expected output file matches the actual file generated by the addScores method. The expected file will have one line for each line in the input file, and will not include the final sum - because that value is not printed in the addScores method. We will also need an exception test for an input file with no legitimate scores, and exception tests if either the fileScanner or printWriter argument is null. Here is part of the Scores3Test.java file:

import org.junit.*;
import static org.junit.Assert.*;
import org.junit.runner.Result;
import static org.junit.runner.JUnitCore.runClasses;
import java.util.*;
import java.io.*;

public class Scores3Test
{
   public static void main(String[ ] args)
   {
       Result result = runClasses (Scores3Test.class);

    System.out.println ("Tests run = " + result.getRunCount() +
                        "
Tests failed = " + result.getFailures());
} // method main


protected Scores3 scores;

@Before
public void runBeforeEveryTest()
{
    scores = new Scores3();
} // method runBeforeEveryTest


@Test
public void scores3Test1() throws IOException
{
    Scanner fileScanner = new Scanner (new File ("scores3.in1"));
    PrintWriter printWriter = new PrintWriter (new BufferedWriter
                                              (new FileWriter ("scores3.out1")));


    int actualSum = scores.addScores (fileScanner, printWriter);
    printWriter.close();
    Scanner scActual = new Scanner (new File ("scores3.out1")),
            scExpected = new Scanner (new File ("scores3.exp"));


    final int INPUT_LINES = 4;
    for (int i = 0; i < INPUT_LINES; i++)
            assertEquals (scExpected.nextLine(), scActual.nextLine());
    if (scExpected.hasNext())
            fail();
} // method scores3Test1


@Test (expected = NumberFormatException.class)
public void scores3Test2() throws IOException
{
    Scanner fileScanner = new Scanner (new File ("scores3.in2"));
    PrintWriter printWriter = new PrintWriter (new BufferedWriter
                                               (new FileWriter ("scores3.out2")));


    int actualSum = scores.addScores (fileScanner, printWriter);
} // method scores3Test2


@Test (expected = NullPointerException.class)
public void scores3Test3() throws IOException
{
    int actualSum = scores.addScores (null,
                                    new PrintWriter (new FileWriter("scores3.out3")));
} // method scores3Test3


@Test (expected = NullPointerException.class)

   public void scores3Test4() throws IOException
   {
       int actualSum = scores.addScores (new Scanner (new File("scores3.in1")), null);
   } // method scores3Test4


} // class Scores3Test

The relevant files are as follows:

scores3.in1
80
x
50
y

scores3.in2 x
y

scores3.exp
80
java.util.InputMismatchException: x
50
java.uti.InputMismatchException: y

All tests were passed.

You are now prepared to do Lab 3:

More Practice on Unit Testing

2.5 System Testing

Just as it is unusual for a class to have a single method, it is unlikely that a project will have a single class. For a multi-class project, which class should be tested first? In an object-oriented environment, bottom-up testing is the norm. With bottom-up testing, a project's low-level classes—those that are used by but do not use other classes—are tested and then integrated with higher-level classes and so on. After each of the component classes has satisfied its tests, we can perform system testing, that is testing the project as a whole. Inputs for the system tests are created as soon as the project specifications are created. Note that system tests are not necessarily unit tests because system tests may entail human intervention—for example, to enter file path from the keyboard.

The purpose of testing is to detect errors in a program (or to increase confidence that no errors exist in the program). When testing reveals that there is an error in your program, you must then determine what brought about the error. This may entail some serious detective work. And the purpose of detection is correction. The entire process—testing, detection and correction—is iterative. Once an error has been corrected, the testing should start over, because the "correction" may have created new errors.

2.6 The Java Virtual Machine

Your Java classes are compiled into a low-level but machine-independent language called Java bytecode. For example, the bytecode version of the file HourlyEmployee.java is stored in the file Hourly Employee.class. The bytecode files are then interpreted and executed on your computer. The program that interprets and executes bytecode is the Java Virtual Machine. It is called a virtual machine because it executes what is almost machine-level code. There are several advantages to this arrangement

source code ------> bytecode -------> Java Virtual Machine

instead of

source code  ------> machine code

The main advantage is platform independence. It doesn't matter whether your computer's operating system is Windows, Linux, or something else, the results of running your Java program will be exactly (well, almost exactly) the same on all platforms. A second benefit is customized security. For example, if the bytecode file is coming from the web, the virtual machine will not allow the application to read from or write to the local disk. But such activities would be allowed for a local application.

The Java Virtual Machine oversees all aspects of your program's run-time environment. In Sections 2.6.1 and 2.6.2, we investigate two tasks of the Java Virtual Machine.

2.6.1 Pre-Initialization of Fields

One of the Java Virtual Machine's duties is the initialization of fields just prior to the invocation of a constructor. For example, we might have the following:

new FullTimeEmployee  ("Dilbert",  345.00)

First, the new operator allocates space for a FullTimeEmployee object. Then, to ensure that each field has at least a minimal level of initialization, the Java Virtual Machine initializes all of the class's fields according to their types. Reference fields are initialized to null, integer fields to 0, floating-point fields to 0.0, char fields to the character at position 0 in the Unicode collating sequence, and boolean fields to false. Then the specified constructor is called. Finally, the starting address of the newly constructed FullTimeEmployee object is returned.

There is an important consequence of this pre-initialization by the Java Virtual Machine. Even if a default constructor has an empty body—such as the one supplied by the Java compiler if your class does not declare any constructors—all fields in the class will still get initialized.

Unlike fields, local variables are not automatically initialized. Section 0.2.4 has the details.

2.6.2 Garbage Collection

The memory for objects is allocated when the new operator is invoked, but what about de-allocation? Specifically, what happens to the space allocated for an object that is no longer accessible? For example, suppose an object is constructed in a method, and at the end of the execution of that method, there are no references pointing to the object. The object is then inaccessible: garbage, so to speak. If your program generates too much garbage, it will run out of memory, which is an error condition. Errors, unlike exceptions, should not be caught, so an error will force the abnormal termination of your program. Are you responsible for garbage collection, that is, for de-allocating inaccessible objects?

Fortunately, you need not worry about garbage collection. The Java run-time system includes a method that performs automatic garbage collection. This method will be invoked if the new operator is invoked but there is not enough memory available for the object specified. With the supersizing of memory in recent years, this is an increasingly rare occurrence. To free up unused memory, the space for any object to which there are no references can be de-allocated. The garbage collector will seek out big chunks of garbage first, such as an array. In any event, this is all taken care of behind the scenes, so your overall approach to the topic of garbage collection should be "Don't worry. Be happy."

Section 2.6 investigates the relationship between packages and visibility modifiers.

2.7 Packages

A package is a collection of related classes. For each such class, the file in which the class is declared starts with the package declaration. For example, a file in a package of classes related to neural networks might start with

package neuralNetwork;

For another example, the Scanner class, part of the package java.util, is in the file Scanner.java, which starts with

package java.util;

If a file includes an instance of the Scanner class, that class can be "imported" into the file. This is done with an import directive, starting with the reserved word import:

import java.util.Scanner;

The advantage of importing is convenience: A declaration such as

Scanner sc;

can be used instead of the fully qualified name:

java.util.Scanner sc;

Many of the classes you create will utilize at least one class from the package java.util, so you can simply import the whole package:

importjava.util.*;//the asterisk indicates that all files from java.util will be available

Occasionally, you may prefer to use the fully qualified name. For example, suppose your project uses two classes named Widget: one in the package com.acme and one in the package com.doodads. To declare (a reference to) an instance of the latter, you could write

com.doodads.Widget myWidget;

Every Java file must have a class with the visibility modifier public. Also, the name of that public class must be the same as the name of the file—without the .java extension. At the beginning of the file, there must be import directives for any package (or file) needed by the file but not part of the file. An exception is made for the package java.lang, which is automatically imported for any file.

A class member with no visibility modifier is said to have default visibility. A member with default visibility can be accessed by any object (or class, in the case of a static member) in the same package as the class in which the member is declared. That is why default visibility is sometimes referred to as "package-friendly visibility." All classes without a package declaration are part of an unnamed package. But there may be more than one unnamed package so, as a general rule, if your project contains more than one class file, each file should include a package declaration.

Technically, it is possible for a Java file to have more than one class with public visibility; all but one of those classes must be nested, that is, declared within another class. The Java Collections Framework, part of the package java.util, has many nested classes. Except for nested classes, a Java file is allowed to have only one class with public visibility. Every other non-nested class must have default visibility.

Because of the way the Java language was developed, protected visibility is not restricted to subclasses. In general, if an identifier in a class has protected visibility, that identifier can also be accessed in any class that is in the same package as the given class. For example, any class—whether or not a subclass—that is in the same package as FullTimeEmployee can access the name and grossPay fields of a FullTimeEmployee object.

In the Java Collections Framework, most of the fields have default visibility or private visibility. Almost no fields have protected visibility: Subclassing across package boundaries is discouraged in the Java Collections Framework. Why? The main reason is philosophical: a belief that the efficiency to users of the subclass is not worth the risk to the integrity of the subclass if the superclass is subsequently modified. This danger is not merely hypothetical. In Java 1.1, a class in java.security was a subclass of the Hashtable class. In Java 2, the Hashtable class was modified, and this opened a security hole in the subclass. Subclassing represents more of a commitment than mere use. So even if a class permits subclassing, it is not necessarily the wisest choice.

The bottom line is that protected visibility is even less restrictive than default visibility. This corruption of the meaning of protected visibility may make you reluctant to designate your fields as protected. An alternative is to designate the fields as private, but to create public methods to get and set the values of those private fields. As described in Programming Exercise 1.3, an accessor method returns a copy of a field (or a copy of the object referenced, if the field is a reference), and a mutator method alters a field (or the object referenced by the field). The usefulness of this approach diminishes as the number of fields increases.

The final topic in this chapter looks at the importance of overriding the Object class's equals method, the barriers to overriding that method, and how those barriers are overcome.

2.8 Overriding the Object Class's equals Method

In Section 1.3.3, we saw the method specification for the equals method in the Object class, the superclass of all classes. Here is that specification:

/**
 *  Determines if this Object object is the same as a specified Object
 *  object.
 *
 *  @param obj  - the Object object to be compared to the calling Object object.
 *
 *  @return true - if the two objects are the same.
 *
 */
public boolean equals   (Object obj)

This method, as with the other methods in the Object class, is intended to be overridden by subclasses, which can compare field values, for example. The object class has no fields, so what does it compare? It compares references, specifically, the calling object reference with the argument reference. Here is the definition:

public boolean equals   (Object obj)
{
      return this == obj;
}  // method equals

As we saw in Section 1.3.2, in any class, the reserved word this is a reference to the calling object. For example, suppose the call is

objLequals  (obj2)

Then in the definition of the equals method, this is a reference to the object that is also referenced by obj1, and obj is a reference to the object that is also referenced by obj2.

Because the Object class's equals method compares references, any class with an equals method should define its own version of that method. For example, suppose we decide to add an equals method to the FullTimeEmployee class. The first question is: Should we overload, that is,

public boolean equals   (FullTimeEmployee full)

or override, that is,

public boolean equals   (Object obj)

Overloading equals—that is, having a different parameter list than the version inherited from the Object class—can be done fairly simply. The only obstacle is that double values should not be directly tested for equality; note, for example, that System.out.println (.4==10.0 - 9.6) outputs "false", (but System.output.println (.4==1.0 - .6) outputs "true"). Here is the definition:

public boolean equals   (FullTimeEmployee full)
{
     return name.equals  (full.name)   ""
            MONEY.format  (grossPay).equals  (MONEY.format  (full.grossPay));
} // overloading method equals

Recall that the format method rounds off the value in the grossPay field, so we need not compare grossPay and full.grossPay for equality. This version compares objects, not references, and so the value true would be printed by each of the following:

System.out.println (new FullTimeEmployee ("a", 100.00).equals
                   (new FullTimeEmployee ("a", 100.00)));

System.out.println (new HourlyEmployee ("a", 10, 10.00).equals
                   (new FullTimeEmployee ("a", 100.00)));

The overloaded version works well as long as the type of the calling object is known, at compile-time, to be FullTimeEmployee (or subclass of FullTimeEmployee). Sadly, that is not always the case. For example, many of the classes in the Java Collections Framework store a collection of objects. Those classes have a contains method to determine if a given object occurs in the collection. The contains method's heading is

public boolean contains (Object obj)

Typically, in testing for containment, the equals method is invoked, with obj as the calling object. For a given application, the collection may consist of FullTimeEmployee objects. But when the equals method—called by contains—is compiled, the only information available about the calling object is its declared type: Object. Therefore, the compiler generates bytecode for a call to the equals method in the Object class, which takes an Object parameter. At run time, when the class of the object (referenced by) obj is available, the version of the Object-parameter equals method executed will be the one in the Object class unless that method has been overridden. Whether the equals method has been overloaded is irrelevant!

Now that we have established the significance of overriding the Object class's equals method, let's see how to do it. We will take the FullTimeEmployee class as an example. The basic idea is simple: if the type of the argument object is not FullTimeEmployee, return false. Otherwise, as we did earlier in this section, compare the values returned by the toString( ) method of the calling object and the argument object. Here are some sample results:

System.out.println (new FullTimeEmployee ("a", 100.00).equals
                            ("yes"));                        // false
System.out.println (new FullTimeEmployee ("a", 100.00).equals
                            (new FullTimeEmployee ("a", 100.00))); // true
System.out.println (new FullTimeEmployee ("a", 100.00).equals
                            (new FullTimeEmployee ("b", 100.00))); // false
System.out.println (new FullTimeEmployee ("a", 100.00).equals
                            (new FullTimeEmployee ("a", 200.00))); // false

Here is the full definition:

public boolean equals (Object obj)
{
       if (!(obj instanceof FullTimeEmployee))
               return false;
       FullTimeEmployee full = (FullTimeEmployee)obj;
       return name.equals (full.name) &&
               MONEY.format (grossPay).equals (MONEY.format (full.grossPay));
} // method equals

To summarize this section:

  1. Every class whose instances might be elements of a collection should have an equals method that overrides the Object class's equals method.
  2. The instanceof operator returns true if and only if, at run-time, the object referenced by the left operand is an instance of the class that is the right operand.
  3. Before comparing the calling object with the argument object, cast the parameter type, Object, to the class in which equals is being defined.

Programming Exercise 2.11 has even more information about the equals method.

SUMMARY

The static modifier is used for identifiers that apply to a class as a whole, rather than to a particular instance of a class. Constants should be declared to be static, because then there will be only one copy of the constant, instead of one copy for each instance of the class. To access a static identifier outside of its class, the class identifier—rather than an object—is the qualifier.

JUnit is an Open Source software product that allows the methods in a class to be tested without human intervention. The tests are developed as soon as the method specifications are created. In general, methods should be designed to facilitate testing without human intervention, so input from System.in and output to System.out should be avoided in methods to be tested.

An exception is an object that signals a special situation, usually that an error has occurred. An exception can be handled with try/catch blocks. The sequence of statements in the try block is executed. If, during execution, an exception is thrown (indicating that an error has occurred), the appropriate catch block is executed to specify what, if anything, is to be done.

File output is similar to console-oriented output, except that a PrintWriter object is explicitly created to write to the specified output file. The output is not immediately sent to the output file, but rather to a buffer. At the conclusion of file processing, the buffer is flushed to the output file by a call to the close method.

The Java run-time, also known as the Java Virtual Machine, is a program that interprets and executes the bytecode output from a Java compiler. Among other tasks, the Java Virtual Machine is responsible for pre-initialization of fields, de-allocation of inaccessible objects, and managing threads.

A package is a collection of related classes. An identifier with no visibility modifier is said to have default visibility. Java is "package friendly." That is, an identifier with default visibility can be accessed by any object (or class, in the case of a static member) in the same package as the class in which the identifier is declared. If a given class's identifier has protected visibility, that identifier can be accessed in any subclass of the given class, even in a different package. Unfortunately, that identifier may also be accessed in any class—even if not a subclass—within the given package's class.

The equals method in the Object class should be overridden for any class C whose instances might become elements of a collection. The overriding method invokes the instanceof method to return false for any argument object that is not an instance of class C, and then casts the Object class to class C in order to make the appropriate comparison(s).

CROSSWORD PUZZLE

image

ACROSS DOWN
6. An object created by an unusual condition, typically, an attempt at invalid processing. 1. The kind of exception for which the compiler confirms that the exception is caught within the method or that a throws clause is appended to the method's heading.
8. An identifier associated with a class itself rather than with an instance of the class is called a _______ identifier. 2. When an exception is thrown in a method that does not catch the exception, the transferring of control back to the calling method is referred to as _______ the exception.
9. A reserved-word modifier associated with a location that can be assigned to only once. 3. A class member that can be accessed in any class within the same package as the given class or in any subclass of the given class is said to have _______ visibility.
10. A method in the PrintWriter class that ensures a file is complete by flushing the output buffer. 4. A class member that can be accessed in any class within the same package as the given class, but not elsewhere, is said to have _______ visibility.
5. A program that does not terminate unexpectedly from invalid user-input is called a _______ program.
7. A collection of related classes.

CONCEPT EXERCISES

2.1 The System class in java.lang has a class constant identifier that has been extensively used in Chapters 0, 1 and 2. What is that constant identifier? Why should a class's constant identifiers be static ? Should a method's constant identifiers be static ? Explain.

2.2 Create a catch block that will handle any exception. Create a catch block that will handle any input/output exception. Create a catch block that will handle any run-time exception.

2.3 What is wrong with the following skeleton?

try
{
        ...
} // try
catch (IOException e)
{
        ...
} // catch IOException
catch (FileNotFoundException e)
{
        ...
} // catch FileNotFoundException

2.4 Suppose fileScanner is a Scanner object for reading from an input file, and printWriter is a PrintWriter object for writing to an output file. What will happen if, at the end of a program, you forget to close fileScanner ? What will happen if, at the end of a program, you do not close printWriter ?

2.5 What does "bottom-up" testing mean with respect to the classes in a project?

2.6 Suppose we create a two-dimensional array (literally, an array in which each element is an array). The following creates an int array with 50000 rows and 100000 columns:

int  [][]  a= new int  [50000][100000];

If this code is executed, the program terminates abnormally, and the message is

java.lang.OutOfMemoryError
Exception in thread "main"

Why wasn't memory re-allocated by the garbage collector? Hypothesize whether this abnormal termination be handled with a try-block and catch-block. Test your hypothesis and explain.

2.7 Can a protected field be accessed outside of the class in which it is declared and subclasses of that class? What does the following statement mean? "Subclassing represents more of a commitment than mere use."

2.8 Arrays are strange objects because there is no array class. But an array object can call methods from the Object class. Determine and explain the output from the following code:

int  []  a= new int  [10];

int  []  b= new int  [10];

a [3] = 7;
b [3]   = 7;
System.out.println  (a.equals(b));

PROGRAMMING EXERCISES

1.1 Develop the specification for a method that scans one line that is supposed to contain three double values and returns the largest. Throw all possible exceptions. Start with a stub for your method and create a test class to test your method. Re-test your method as you define it. Finally, include a main method and a run( ) method that calls the method you developed.

2.2 Develop the specification for a method that scans (what are supposed to be) double values from a file and returns the largest. Throw all possible exceptions. Start with a stub for your method and create a test class to test your method. Re-test your method as you define it. Finally, include a main method and a run( ) method that calls the method you developed.

2.3 Modify the run method for the Company class to scan from an input file and write to an output file. Include a re-prompt if either the input or output path is incorrect.

2.4 Hypothesize what is wrong with the following method:

public static boolean isEven (int i)
{
    if  (i % 2 == 0)
        return true;
    if  (i % 2 != 0)
        return false;
} // method isEven

Test your hypothesis by calling this method from a run( ) method. Can a try-block and catch-block handle the problem? Explain.

2.5 Hypothesize the output from the following:

System.out.println  (null +  "null");

Test your hypothesis. Provide the code in the String class that explains why the output is what it is.

2.6 Give an example to show that private visibility is more restrictive than default visibility. Give an example to show that default visibility is more restrictive than protected visibility. Give an example to show that protected visibility is more restrictive than public visibility. In each case, test your code to make sure that the more restrictive choice generates a compile-time error message. No error message should be generated for the less restrictive choice.

2.7 Protectedness transfers across packages, but only within a subclass, and only for objects whose type is that subclass. For a bare-bones illustration, suppose we have class A declared in package APackage:

package APackage;

public class A
{
      protected int t;
} // class A

Also, suppose that classes C and D are subclasses of A and that C and D are in a different package from A. Then within class D, the t field is treated as if it were declared in D instead of in A. Here are possible declarations for classes C and D:

import APackage.*;

public class C extends A{  }

Class D is declared in another file. For each of the four accesses of t in the following declaration of class D, hypothesize whether the access is legal or illegal:

import APackage.*;


public class D extends A
{


    public void meth()
     {
          D d = new D();
          d.t = 1;          // access 1
          t = 2;            // access 2
          A a = new A();
          a.t = 3;          // access 3
          C c = new C();
          c.t = 4;          // access 4
    } method meth


} // class D

Test your hypotheses by creating and running a project that includes the above files.

2.8 Re-do Programming Exercise 1.2 to print out the number of above-average salaries. Use an array field to hold the salaries, and assume there will be at most 10 salaries in the input.

2.9 Study the specification of the arraycopy method in the System class, and then write a short program that uses the arraycopy method to copy all the elements of an array to another array. Output the elements in the destination array to make sure the copying actually occurred.

2.10 Re-do Programming Exercise 2.8 if the input can contain an arbitrary number of salaries.

Hint: Start with an array of length 10. Whenever the number of salaries in the input exceeds the current length of the array field, create a new array of twice that length, copy the old array to the new array—see Programming Exercise 2.9—and then assign the new array (reference) to the old array (reference).

2.11 According to the full method specification in the Object class, any override of the Object class's equals method should satisfy the following five properties:

  1. reflexivity, that is, for any non-null reference x,
    x.equals  (x)
    should return true.
  2. symmetry, that is, for any non-null references x and y,
    x.equals  (y)

    should return the same result as

    y.equals  (x)
  3. transitivity, that is, for any references x, y and z if
    x.equals  (y)

    returns true, and

    y.equals  (z)
    returns true, then
      x.equals   (z)

    should return true.

  4. consistency, that is, for any non-null references x and y, multiple invocations of
    x.equals   (y)

    should consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.

  5. actuality, that is, for any non-null reference x,
    x.equals   (null)

    should return false.

    For the FullTimeEmployee class's equals method (see Section 2.7), provide examples to support the claim that the equals method satisfies those five properties. You are not being asked to prove that the FullTimeEmployee class's equals method satisfies those properties.

2.12 Create and run a test class for the equals method defined in Section 2.7 for the FullTimeEmployee class.

Programming Project 2.1: An Integrated Web Browser and Search Engine, Part 1

Note: This project assumes familiarity with developing graphical user interfaces.

This is the first part of a seven-part project to create an integrated web browser and search engine. The remaining parts are in Chapters 6, 7, 12, 13, 14 and 15. The project is based on a paper by Newhall and Meeden [2002].

Basically, all the project does at this stage is to display web pages. Initially the output area of the Graphical User Interface (GUI) window displays the home page. That page has a link to another page, and if the end user clicks on the link, that page will be displayed in the output area. In addition to the output area, the GUI window will also have four buttons: one to go forward (currently disabled), one to go backward (currently disabled), one to display the home page (enabled), and one to conduct a search (currently disabled). Finally, the GUI window will have an input line that allows an end user to enter a file path; when the Enter key is pressed, that file (that is, that page) will be displayed.

Analysis The following specifications apply to the GUI:

  1. The size of the window will be 700 pixels wide and 500 pixels high.
  2. The upper-left-hand corner of the window will have x-coordinate 150 and y-coordinate 250.
  3. Each of the four buttons on the top of the window will be 80 pixels wide and 30 pixels high. The foreground color of the Home button will be green, and the foreground color of the other three buttons will be red.
  4. The input line will be 50 pixels wide.
  5. The output area will be scrollable in both directions.

    Here is a diagram of the GUI:

    image

  6. The only tags allowed in a page are link tags, for example,
    < a href = browser.in4 > browser4 < /a>

    In this example, the only part that will appear in the output area is browser4.

  7. For simplicity, all links (such as browser.in4 above) will come from the same directory, and all link ''nicknames" (such as browser4 above) will consist of a single word.
  8. In the output area, the foreground color of each link's nickname should be blue.
  9. A line in a page may have several link tags, but no tag will be split between two lines.
  10. If a page clicked on or typed in does not exist, the following error message should appear in the output area:
    Web page not found:  HTTP 404

    At that point, the end user can click on the Home button, can enter a new file path in the input line, or can close the application.

Hints:

  1. Use the layout manager FlowLayout.
  2. Use a JTextField for the input line and a JTextPane (made scrollable as a JScrollPane)for the output area. A JTextPane component supports embedded links (as JButton objects).
  3. Use a separate listener class so that there can be a listener object for each link that is clicked on.

Here is a sample home page:

In Xanadu did Kubla Khan
A stately pleasure-dome decree:

Where Alph,  the sacred river,  ran
Through caverns <a href=browser.in2>browser2</a> measureless to man
Down to a sunless sea.

When that page is displayed in the output area, it will appear as

In Xanadu did Kubla Khan
A stately pleasure-dome decree:
Where Alph,  the sacred river,  ran
Through caverns browser2 measureless to man
Down to a sunless sea.

If the end user now clicks on browser2, the contents of browser.in2 will be displayed. Here are the contents of browser.in1, browser.in2, browser.in4, and browser.in5 (browser.in3 does not exist):

browser.in1:

In Xanadu did Kubla Khan
A stately pleasure-dome decree:
Where Alph,  the sacred river,  ran
Through caverns measureless to man
Down <a href=browser.in2>browser2</a> to a sunless <a href=browser.in4>browser4</a> sea.

browser.in2:

In Xanadu did Kubla Khan
A stately <a href=browser.in3>browser3</a> pleasure-dome decree:
Where Alph,  the sacred river,  <a href=browser.in4>the browser4</a> ran
Through caverns measureless to man
Down to a <a href=browser.in5>browser5</a> sunless sea.

browser.in4

In Xanadu did <a href=browser.in1>browser1</a> Kubla Khan
A stately pleasure-dome decree:
Where Alph,  the sacred river,  ran
Through caverns    measureless to man
Down to a sunless sea.

browser.in5:

In Xanadu did <a href=browser.in2>browser2</a> Kubla Khan
A stately pleasure-dome decree:
Where Alph,  the sacred river,  ran
Through caverns    measureless to man
Down to a sunless sea.

1 In Section 4.2.3.1, we will see that a class may also have another class as a member.

2 Because the earth makes one full rotation around the sun in slightly less than 365.25 days, not every year divisible by 4 is a leap year. Specifically, a leap year must be both divisible by 4 and either not divisible by 100 or divisible by 400. So 2000 was a leap year, but 2100 will not be a leap year.

3 Actually, RuntimeException consists of several constructors, each of which merely invokes the corresponding constructor in Exception, the superclass of RuntimeException. The Exception class passes the buck to its superclass, Throwable, where the low-level details are dealt with.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.189.23