Fundamentals of Computer Science II (CSC-152 99S)


Answer Key for Assignment 2 (Lab J3): Building Your Own Classes

Some subtle (and not-so subtle) notes:

Experiment J3.1: Program State

Consider the following fragment of Java code

int x = 0;
int y = 0;
int z = 0;
...
// position 1 (before the following steps)
x = 2 * y;
y = 5;
z = x + y;
// position 2 (after the first set of steps)
x = 2 * y;
y = 5;
z = x + y;
// position 3 (after both sets of steps)

What are the values of x, y, and z at positions 1, 2, and 3?

You do not know the values of x, y, and z at position 1, before the first set of steps. (Since each could have been given a different value in the intervening code.)

At position 2, you still don't know the value of x or z (which depends on x). However, the value of y must be 5.

At position 3, x is 10, y is 5, and z is 15.

If you decided that nothing happened to x, y, and z between initialization and position 1, then all three have the value 0 at position 1; x=0, y=5, and z=5 at position 2; and x=10, y=5, and z=15 at position 3.

Experiment J3.2: Fields

Required code files:

Step 1. Make copies of the required code files. Modify PointPrinter.java so that it includes a numprinted field which it prints and updates every time it prints a point. Modify PointFun.java so that it creates and uses a PointPrinter object for output. Compile the various files, execute PointFun, and record the results. Are they what you expected? Why or why not?

Yes, the results are what I expected. Each point is preceded by the number of the point. Here are the lines that I added to the print method of PointPrinter.

    // Update our count.  Added in Step 1 of J3.2.
    this.numprinted = this.numprinted + 1;
    // Print the number of the point.  Added in Step 1 of J3.2.
    out.print(this.numprinted + ": ");

Note that the first number printed is 1. That is because the default initialization of numprinted is 0.

Step 2. Rename numprinted to printed everywhere that it appears in PointPrinter.java and recompile. Execute PointFun and record the results. Are they what you expected? Why or why not?

Yes, the results are what I expected. What we name a field should not matter, as long as we are uniform in our naming (and don't use a keyword). This is an instance of the power of information hiding. Since other classes don't know what you've named the field, changes to the field don't affect the operation of your program.

Experiment J3.3: Fields, Revisited

Step 1. Create a new class, MyPoint, that contains two integer fields, xcoordinate and ycoordinate. Note that all you will need for this class (at least at this point) are the class declaration and the field declarations. Compile your class and correct any errors reported by the compiler. Enter the code for your class here.

/**
 * Points on the plane, using integer coordinates.
 *
 * @author Samuel A. Rebelsky
 * @version 1.0 of February 1999
 */
public class MyPoint {
   // +--------+--------------------------------------------------
   // | Fields |
   // +--------+

   /** The x coordinate.  Surprise surprise. */
   protected int xcoordinate;

   /** The y coordinate.  Surprise surprise. */
   protected int ycoordinate;
} // class MyPoint

Step 2. Create a print(SimpleOutput out) method for your MyPoint class. This method should print out the point in a reasonable format. Recompile your class and correct any errors reported by the compiler. Enter the code for that method here.

   /**
    * Print the current point, using something that knows how
    * to print integers.
    */
   public void print(SimpleOutput out) {
     out.println("(" + this.xcoordinate + "," + this.ycoordinate + ")");
   } // print(SimpleOutput)

Step 3. Create a TestMyPoint class with a main method that creates a MyPoint and asks it to print itself out. Compile and execute TestMyPoint. What values are printed for the x and y coordinates? Why do you think this is?

Here is the main method, using two kinds of printing. The note at the end of the lab should describe the differences.

  public static void main(String[] args) {
    // Build a point.
    MyPoint mypt = new MyPoint();
    // Build something that knows how to print.
    SimpleOutput out = new SimpleOutput();
    // Print, using the incorrect method.  Since
    // out doesn't know anything about points, who
    // knows what we'll get.
    out.println(mypt);
    // Print, using the correct method.  Since points
    // know how to use output objects, we should get
    // something reasonable (but what?)
    mypt.print(out);
  } // main(String[])

The values printed for the x and y coordinates are 0 and 0. Why? We didn't give any values, so the Java compiler chose ``reasonable'' defaults. (Any time you don't explicitly initialize an integer field, it gets initialized to 0.)

Step 4. Create a setLocation(int x, int y) method for the MyPoint class. This method will set the xcoordinate and ycoordinate fields to the corresponding parameters. Recompile MyPoint and correct any compiler errors.

Extend TestMyPoint to test this method by setting the x coordinate to 2 and the y coordinate to 3 and then asking the point to print itself. Recompile and run TestMyPoint and correct any errors.

Enter the code for the setLocation method here.

   /**
    * Set the x and y coordinates.
    */
   public void setLocation(int x, int y) {
     this.xcoordinate = x;
     this.ycoordinate = y;
   } // setLocation(int,int)

Experiment J3.4: Constructors

Required code files:

Step 1. Create a constructor for the PointPrinter class that takes no parameters and initializes any fields to appropriate values. Recompile PointPrinter and execute PointFun. What happens? Is that what you expected? Why or why not?

  /**
   * Create a new printer, initializing the count of printed points
   * to 0.
   */
  public PointPrinter() {
    this.printed = 0;
  } // PointPrinter()

Exactly the same thing happened as before. Since the default is to initialize an integer field to 0, this doesn't seem to do much.

Step 2.

In step 1, we recompiled PointPrinter but execute PointFun. Why didn't you need to recompile PointFun?

PointFun did not change. Since Java only loads classes when needed, when we run PointFun it loads the current version of PointPrinter.

Step 3. Create a one-parameter constructor for the PointPrinter class that initializes the count of points printed. Update PointFun to use that constructor and to use 100 as the first value. Recompile and test. Enter your code for the constructor here.

  /**
   * Create a new printer, initializing the count of printed points
   * to the given value.
   */
  public PointPrinter(int init) {
    this.printed = init;
  } // PointPrinter(int)

Step 4. Update PointFun to use the zero-parameter constructor. Recompile and test. What happens? Is that what you expected? Why or why not?

Not so surprisingly, the results are the same as in the first step. It runs, using 0 as the initial value.

Step 5. Delete the zero-parameter constructor for the PointPrinter class (leaving you with just the one-parameter constructor). Recompile PointPrinter and try to execute PointFun (you should not need to recompile PointFun). What happens? Is that what you expected? Why or why not?

Hmmm .... it won't run. I get the completely unreadable error message of

java.lang.NoSuchMethodError: PointPrinter: method <init>()V not found
        at PointFun.main(PointFun.java:17)
Why won't it run? Because there is no 0-parameter constructor, and we've tried to use one. What happens if we try to recompile PointPrinter? We get the same error, but in a slightly different form.
PointFun.java:19: No constructor matching PointPrinter() found in class PointPrinter.
    PointPrinter printer = new PointPrinter();

Step 6. Delete the one-parameter constructor for the PointPrinter class (leaving you with no constructors). Recompile PointPrinter and try to execute PointFun (you should not need to recompile PointFun, but you may do so if you wish). What happens? Is this the same as in the previous step? Why or why not?

It runs! (It also compiles!) That's strange, isn't it. The explanation is that when you don't give any constructor, Java creates a `default' constructor. However, when you give a constructor, Java no longer assumes the default constructor.

Experiment J3.5: Constructors, revisited

Required code files:

Step 1. Create a constructor for the MyPoint class that takes two parameters: the initial x value and the initial y value. Recompile MyPoint and correct any errors.

Update TestMyPoint to use that constructor (and only that constructor). For example, you might write

MyPoint pt = new MyPoint(2,3);

Recompile and run TestMyPoint.

Enter your code for the constructor here.

   /**
    * Create a new point with given x and y coordinates.
    */
   public MyPoint(int x, int y) {
     // Take advantage of the function we've already written.
     this.setLocation(x,y);
   } // MyPoint(int,int)

You might also use

   public MyPoint(int x, int y) {
     this.xcoordinate = x;
     this.ycoordinate = y;
   } // MyPoint(int,int)

Step 2.

Update TestMyPoint to use a parameterless constructor for MyPoint. For example, you might write

    MyPoint pt2 = new MyPoint();

What happens when you try to compile and run TestMyPoint? Why?

You get a ``no such constructor'' error message. Why? Because there is no zero-parameter constructor, and you're trying to use one.

Step 3. Create a parameterless constructor for MyPoint that initializes both coordinates to 0. Recompile MyPoint and correct any errors. What happens when you try to compile and run TestMyPoint? Is your result the same as or different from the result in Step 2? Why?

   /**
    * Create a new point initialized to (0,0).
    */
   public MyPoint() {
     this.setLocation(0,0);
   } // MyPoint

I am now able to compile and run TestMyPoint. This is different than in the previous step. It was expecting a constructor, and now there is noe.

Step 4. Make a copy of MyPoint named MyPt (when making the copy, you will need to change the file name to MyPt.java and the class name to MyPt). Try to compile the new class. What happens? Why? How can you correct that problem?

We now get error messages about the constructors because they no longer have the same name as the class, and are therefore interpreted as normal functions, which therefore need return values. We should rename the constructors.

Experiment J3.6: Overloading read methods

Required files:

Step 0. Make a copy of Point.java, PointReader.java and PointPrinter.java. Compile the three files.

Step 1. Make a copy of ReadTester.java. Compile and execute ReadTester. Record the output.

Enter the x coordinate: 2
Enter the y coordinate: 3
1: (2.0,3.0)
  distance from origin: 3.605551275463989

Whop de doo!

Step 2. Add the following lines to the main method of ReadTester.

    // Step 2.  Read a point without prompting.
    pt = reader.read(in);
    printer.print(out,pt);

What do you expect to happen when you compile this modified class?

There is no read(SimpleInput) method in the PointReader class, so I'll get an error.

Step 3. Attempt to compile the modified ReadTester. Record any errors. What do these errors suggest?

ReadTester.java:23: Wrong number of arguments in method.
    pt = reader.read(in);

Just as we've guessed, there is no corresonding method in the PointReader class.

Step 4. Add the following method to PointReader.

    /**
     * Read a point without prompting.
     */
    public static Point read(SimpleInput in) {
      float x;	// The x coordinate.
      float y;	// The y coordinate.
      x = in.readFloat();
      y = in.readFloat();
      return new Point(x,y);
    } // read(SimpleInput)

What do you expect to happen when you compile this modified class?

It should compile without any errors. Sometimes things go okay.

After entering your answer, compile the modified class and correct any errors.

Step 5. What do you expect to happen when you now try to compile ReadTester?

It should now compile without any errors, since the requisite method is now in PointReader.

After entering your answer, compile the modified class.

Step 6. Execute ReadTester and record the results. Note that you will have to enter the two components of the second point without being prompted.

Enter the x coordinate: 2
Enter the y coordinate: 3
1: (2.0,3.0)
  distance from origin: 3.605551275463989
2
3
2: (2.0,3.0)

Experiment J3.7: Choosing the class

Required files:

Step 0. Make sure you have a fresh copy of AverageComputer.java, which you should have created in a previous lab. Also make sure that you have a copy of AverageTester.java.

Step 1. Compile the two files and execute AverageTester. When prompted for input, enter 2 and 3 and record the results.

Enter a number: 2
Enter another number: 3
The average of 2.0 and 3.0 is 2.5

Step 2. Add the following lines to the end of the main method of AverageTester.

    // Step 2.  Average two integers.
    int firstInt;
    int secondInt;
    out.print("Enter an integer: ");
    firstInt = in.readInt();
    out.print("Enter another integer: ");
    secondInt = in.readInt();
    out.println("The average of " + firstInt + " and " + secondInt + 
                " is " + computer.average(firstInt,secondInt));

What do you think will happen when you try to compile and execute the modified AverageTester?

It will once again print that the average is 2.5. I have some worry that the parameters to average are supposed to be doubles, and I'm using ints.

Step 3. Compile and execute the modified AverageTester, using 2 and 3 as the pair of numbers in each case. Record and explain the output.

Enter a number: 2
Enter another number: 3
The average of 2.0 and 3.0 is 2.5
Enter an integer: 2
Enter another integer: 3
The average of 2 and 3 is 2.5

Although average expects two doubles, Java knows how to convert ints to doubles.

I've also observed a small difference in the two outputs. In the second case, the output read 2 and 3 rather than 2.0 and 3.0. That's because the things being printed are integers rather than reals.

Step 4. Add the following lines to the end of the main method of AverageTester.

    // Step 4.  Compute the average in a different way.
    out.print("That average might also be stated as ");
    out.println((firstInt + secondInt) / 2);

What do you expect the new output to be?

At first guess, I'd expect it to be the same as in the past: 2.5. (I'd be wrong, though.)

Step 5. Compile and execute the modified AverageTester, using 2 and 3 as the pair of numbers in each case. Record and explain the output.

Enter a number: 2
Enter another number: 3
The average of 2.0 and 3.0 is 2.5
Enter an integer: 2
Enter another integer: 3
The average of 2 and 3 is 2.5
That average might also be stated as 2

In that final computation, it must be doing integer division, in which case it rounds down.

Step 6. Add the following method to AverageComputer.

  /**
   * Compute the average of two integers.
   */
  public int average(int a, int b) {
    return (a + b) / 2;
  } // average(int,int)

Do you expect this modification to have any effect on the output of AverageTester?

Yes. Before I was calling average(double,double), with the integers being converted. Now I will be calling this new version of the method.

Step 7. Recompile AverageComputer and AverageTester. (Even though you have not modified AverageTester, you should recompile it because you've overloaded a method it uses in AverageComputer.) Execute AverageTester. Enter 2 and 3 for each pair of numbers. Record your output. Does this output differ from that in step 5? If so, why?

Enter a number: 2
Enter another number: 3
The average of 2.0 and 3.0 is 2.5
Enter an integer: 2
Enter another integer: 3
The average of 2 and 3 is 2
That average might also be stated as 2

Clearly, there's a difference. Now, when we average 2 and 3 we get 2, rather than 2.5. This is because the new method is being used.

As a side note, I checked what happened if I only recompiled AverageComputer and not AverageTester. We still use average(double,double) method, because that's all that AverageTester knew about when it was compiled.

Step 8. Remove the average(int,int) method from AverageComputer. Then add the following methods to AverageComputer.

  /**
   * Compute the average of an integer and a double.
   */
  public double average(int a, double b) {
    return (a + b) / 2;
  } // average(int,double)

  /**
   * Compute the average of a double and an integer.
   */
  public double average(double a, int b) {
    return (a + b) / 2;
  } // average(double,int)

Do you expect this modification to have any effect on either program?

It doesn't seem that the modification should have any great effect, but I've been known to be wrong in the past.

Step 9. Compile AverageComputer. If that succeeds, compile AverageTester. If that succeeds, execute AverageTester. What happened? Why?

AverageTester.java:43: Reference to average is ambiguous. It is defined in double average(double, int) and double average(int, double).
                " is " + computer.average(firstInt,secondInt));

Clearly, Java can't decide what to do. There are two different methods that come close to matching average(int,int) and it has no way to decide which to use.


Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.

This page may be found at http://www.math.grin.edu/~rebelsky/Courses/CS152/99S/Assignments/notes.02.html

Source text last modified Tue Aug 10 10:15:11 1999.

This page generated on Tue Aug 10 10:15:20 1999 by SiteWeaver. Validate this page's HTML.

Contact our webmaster at rebelsky@math.grin.edu