CSC 241- week2 (starting January 27th, 1997)


The Tank class

The class Tank is presented as an example of object oriented design. All objects of class Tank are of fixed capacity. A tank can be constructed with 0 or more gallons of water. There are two constructors build into this class, one with no parameter that sets tank's content to 0 at the start, and one with a parameter that is used to set the initial content of tank. The Tank class has three methods: add, remove, and content. The first two methods change a tank's content, the third, just returns the content of a tank. Three exceptions may be thrown by the methods in this class: TankOverFlowException, IllegalArgumentException, TankUnderFlowException. A tank may overflow when constructed with an initial content that is larger than capacity or when its add method is invoked and the amount provided would send the content over capacity. Underflow simply means that the amount supplied for the remove method would cause tank to go below empty. IllegalArumentException is thrown when amount or init_cont parameters are negative.

Consider this constructor

  // Construct a new Tank with the indicated initial content.  Guard against
  // overflow due to initial content or negative initial content.
  public Tank(int init_cont)  throws
    TankOverFlowException, IllegalArgumentException {
      if (init_cont < 0) 
        throw new IllegalArgumentException();
      int overflow = init_cont - Tank.capacity;
      if (overflow > 0)
        throw new TankOverFlowException(this, overflow);
    content_ = init_cont;
  }

What we want to do is to set content_ to init_cont when a tank object is created with this constructor. Note that the throw statements only occur when we detect logic errors. Exceptions are generally abnormal cases. Why abnormal? For instance, in your documentation, you make it clear that you don't want to handle negative values for your parameter. If the user of this class gives you one anyway, you throw the exception. We will later see how we catch exceptions in our testing of class Tank. An exception doesn't have to terminate the user's program, although, in all the test programs for the Tank class it does.

Check out this statement carefully: throw new TankOverFlowException(this, overflow);. The word new means that we are constructing an object of some class. What class, you ask! we are throwing an object of class TankOverFlowException.

TankOverFlowException

Here is the code for the class TankOverFlowException.

public class TankOverFlowException extends Exception {
  public Tank overflowTank;
  public int overflow;
  public TankOverFlowException(Tank t, int amount) {
    super(); overflowTank = t; overflow = amount;
  }
}

This is your first exposure to inheritance in Object Oriented design. In java there are existing exception classes that are very specific, such as, FileNotFoundException used in one of the test programs for Tank and IllegalArgumentException used in the Tank class itself. But, we don't have an existing exception that would deal with our overflow problem. So, we built one. Notice that TankOverFlowException extends Exception, that means it inherits everything in Exception, and is going to add on some new things. For instance, for this subclass of the Exception class, the variable overflowtank which is an object of class Tank is added. Also, the constructor is specific for TankOverFlowException. super(); is an invocation of the constructor for the super class Exception.

Lets take a second and look at throw new TankOverFlowException(this, overflow); from the Tank constructor shown above. When we throw an exception, we create an an object of the particular exception class. In this case, we have defined two argument for construction of such an object. The first one, based on the definition of the constructor, must be an object class Tank. So, what is this? this represents the object where the overflow occurred in. This way, exception object that is thrown here knows what tank overflowed and the second argument provides by what amount.

Catching TankOverFlowException

Here is the code from test_simple_tank.java that shows how we try something that may cause an exception and how we catch the exception if it was thrown.

    try {
      Tank t1 = new Tank(50);//create a tank with 50 as content
      ...
    }
    ...
	catch (TankOverFlowException e) {
		System.err.println("Tank overflow by "+e.overflow);
	}

Remember that Tank construction may cause an overflow exception. So, in this test program, we need the try/catch combination to try to construct a tank, amongst other things, and catch the exception, if it failed. When using a method that may throw an exception, you must do so in the context of a try/catch or the compiler will complain.

The order in which you catch exceptions is sometimes important. The following two catch clauses in test_more_tank.java had to go in the order given:

    catch (FileNotFoundException e) {
      System.err.println("File does not exist");
      return; 
    }
    catch (IOException e) {
      System.err.println("Unsuccessful read");
      return; 
    }

Why should FileNotFoundException go before IOException? because, the first is a subclass of the later. If a FileNotFoundException is thrown when we perform InputStream ist = new FileInputStream(fname);, we want it caught as such and not just as an IOException! More over, if you change the order of the catch clauses, you'll notice that the compiler will, very nicely, let you know that your catch (FileNotFoundException e) {...} is not reachable!

IO in Java

Lets discuss the test_more_tank.java in more detail. This program is a more representative of test programs that one would typically write. It takes input values from a data file, such as, test.dat which enables us to have more flexibility in what we test a class for. Basically, it is designed to prompt for a file name, and read integer values from the file that are either added or removed from a tank object. If the value read from the data file is positive, it is added to the tank, otherwise, it is removed from the tank. After each add or remove, a message is output that indicates how much was added or removed and what the content of the tank was after the operation. When there are no more values in the file, the program stops. If exception occurs, such as, tank overflow, the program would output a message and stop. Here, I am mostly interested in discussing the logic and the IO operation of this program. The exception handling aspects have already been covered earlier. Lets look at the code piece by piece:

String text; declares a string variable that we will use for reading integer values from our data file. Tank t = new Tank(500); creates a tank with a 500 as its content, named t.

DataInputStream inp = new DataInputStream(System.in); is a standard way for creating a handle for the object that represents the standard input file (System.in). System.in is an object of InputStream. This object only allows for input that is not just single characters. Class DataInputStream enables you to read strings amongst other things. Notice that the next statement is just prompting the user for a file name using standard output: System.out.println("Enter File Name:");. The next line,String fname = inp.readLine(); is used to read the input file name from standard input. What does the following sequence of code do? simply open the input file, and read the first line of it into text.

      InputStream ist = new FileInputStream(fname);
	  DataInputStream istream = new DataInputStream(ist);
      text = istream.readLine();

You should notice text = istream.readLine(); also appears before the end of the while loop. If end of file occurs, null is returned from istream.readLine(), which is why the while loop is designed to terminate if text is null.

The statement int am = Integer.parseInt(text); invokes a class method in class Integer that allows for parsing a string and returning the integer value found in the string. The next few line simply check the value read, and if positive, add it to tank, otherwise, remove it from tank. Note that -amt is sent t.remove.

        if (am > 0) {
          t.add(am);
          System.out.println("tank content after adding "+ am + " = "+
                             t.content());
        }
        else {
          t.remove(-am);
          System.out.println("tank content after removing "+ -am + " = "+
                             t.content());
        }