Lecture 8

  1. Announcements
  2. "this"
  3. Some simple exercises
  4. Parameters
  5. Instance variables
  6. Instance variables and parameters
  7. Local variables
  8. Local variables vs instance variables vs parameters

Announcements

None.

"this"

In Java, you can use the term this to let an object refer to itself.

Let's explore this a bit more by looking again at the Tshirt class that we've been studying:

import objectdraw.*;
import java.awt.*;

// A class that defines a graphical type that looks a bit
// like a t-shirt
public class Tshirt {

        // overall size of shirt
    private static final double SIZE = 120;
    
        // size of sleeves
    private static final double SLEEVE_WIDTH = SIZE;        
    private static final double SLEEVE_HEIGHT = 0.2*SIZE;

        // size of body
    private static final double BODY_WIDTH = 0.6*SIZE;
    private static final double BODY_HEIGHT = (0.65)*SIZE;
    
        // horizontal inset of body from sleeve
    private static final double BODY_INSET = 0.2*SIZE;
    
        // size of neck
    private static final double NECK_WIDTH = 0.3*SIZE;
    private static final double NECK_HEIGHT = 0.06*SIZE;
    
        // horizontal inset of neck from sleeve
    private static final double NECK_INSET = 0.35*SIZE;


    private FramedRect sleeveTrim, bodyTrim;    // rectangles that form a 
    private FramedOval neckTrim;                // border around the shirt

    private FilledRect body, sleeves;           // rectangles that form the 
    private FilledOval neck;                    // interior color of the shirt


    // create a new Tshirt with its upper left corner at (x,y)
    public Tshirt( double x, double y, DrawingCanvas canvas )
    {
            // create boundary rectangles
        sleeveTrim = new FramedRect( x, y + NECK_HEIGHT/2, SLEEVE_WIDTH, 
                                         SLEEVE_HEIGHT, canvas);
        bodyTrim = new FramedRect( x + BODY_INSET, y + NECK_HEIGHT/2, 
                                         BODY_WIDTH, BODY_HEIGHT, canvas);
        
            // create interior rectangles
        sleeves = new FilledRect( x+1, y+NECK_HEIGHT/2+1, SLEEVE_WIDTH-1, 
                                      SLEEVE_HEIGHT-1, canvas);
        body = new FilledRect( x+BODY_INSET+1, y+NECK_HEIGHT/2+1, 
                                   BODY_WIDTH-1, BODY_HEIGHT-1, canvas);

            // give it a neck hole
        neck = new FilledOval( x + NECK_INSET, y, NECK_WIDTH, 
                                   NECK_HEIGHT, canvas);
        neckTrim = new FramedOval( x + NECK_INSET, y, NECK_WIDTH, 
                                       NECK_HEIGHT, canvas);
        
            // set the interior to white
        body.setColor(Color.white);
        neck.setColor(Color.white);
        sleeves.setColor(Color.white);
    }

    // move the t-shirt by specified offsets.
    public void move( double xOffset, double yOffset )
    {
        body.move(xOffset,yOffset);
        neck.move(xOffset,yOffset);
        sleeves.move(xOffset,yOffset);
        bodyTrim.move(xOffset,yOffset);
        sleeveTrim.move(xOffset,yOffset);
        neckTrim.move(xOffset,yOffset);
    }

    // move the t-shirt to a new upper-left coordinate position
    public void moveTo( double x, double y)
    {
        move( x - sleeves.getX(), y - neck.getY());
    }

    // returns true if the t-shirt contains the point; false otherwise
    public boolean contains( Location pt)
    {
        return body.contains(pt) || sleeves.contains(pt) || 
               neck.contains(pt);
    }
}

As we discussed earlier, the method move is straightforward. It simply moves all 6 pieces of the t-shirt the same amount. The method moveTo uses a clever trick. Rather than computing where to move each of the six pieces (notice that all 6 of them are created at different positions in the constructor), the method calculates how far (in each of the x and y directions) the new position is from the left and top of the entire t-shirt.

Notice the left edge of the t-shirt is that of the sleeves, while the top is the same as that of the neck. Hence the distance to move the t-shirt in the x direction is x - sleeves.getX(), while the distance to move the T-shirt in the y-direction is given by y - neck.getY(). Now the move method is invoked with those computed values as the distance to move in the x and y directions.

We took advantage of the fact that we had already written the move method, and used it to perform the actions necessary for moveTo. We had to compute how far to move it from the absolute coordinates given by the parameters of moveTo, but this was significantly easier than calculating where to move each individual piece.

There is one strange thing about the body of the moveTo method:

    move( x - sleeves.getX(), y - neck.getY());
The move message is not sent to an object! We are used to writing body.move(...), neck.move(...), etc., but not just writing move by itself without an object that it is being sent to.

When we write a method name by itself inside the body of another method, it means execute that method in the same class. Technically, Java considers the line of code above as an abbreviation for:

    this.move( x - sleeves.getX(), y - neck.getY());
The new version looks a bit more familiar, in that at least now the move message is being sent to an object, this. As we described above, this stands for the object executing the current method.

Some simple exercises

Now it's time for a few simple exercises. Make sure you can do all of these:
  1. Write a setColor method for class Tshirt that takes a color as a parameter. The declaration of setColor for our graphic objects is:
            public void setColor(Color newColor)
    
    Execution of the method should result in all of the pieces of the t-shirt being of the same newColor.
  2. Our t-shirt dragging program implemented onMouseEnter by moving the t-shirt back to its original starting position. Add a new method reset() to the Tshirt class which moves the t-shirt to the point at which it was originally created. In order to do this, you will need to add new instance variables which remember the starting position passed in to the constructor. When this is done, change the original t-shirt dragging program so that the body of onMouseEnter simply reset's the t-shirt.

Parameters

Parameters are used to pass information from one part of a program to another. Let's look back at the general shape of the t-shirt class (where we have omitted all constants, comments, and details of method bodies so that we can focus on parameters and instance variables):
public class Tshirt{

    private FramedRect sleeveTrim, bodyTrim;    
    private FramedOval neckTrim;                

    private FilledRect body, sleeves;            
    private FilledOval neck;            
            
    public Tshirt( double x, double y, DrawingCanvas canvas ){...}

    public void move( double xOffset, double yOffset ){...}

    public void moveTo( double x, double y){...}

    public boolean contains( Location pt){...}

    public void setColor(Color newColor){...}
        
}

The Tshirt constructor has formal parameters x, y, and canvas. Just like the instance variables, the parameters must be supplied with a type definition so that we know what kind of values they can hold. Inside the constructor we can use any of the instance variables (and constants) and the three formal parameters declared in the constructor header.

Look at the code in the constructor body:

public Tshirt( double x, double y, DrawingCanvas canvas )
{
        // create boundary rectangles
    sleeveTrim = new FramedRect( x, y + NECK_HEIGHT/2, SLEEVE_WIDTH, 
                                 SLEEVE_HEIGHT, canvas);
    bodyTrim = new FramedRect( x + BODY_INSET, y + NECK_HEIGHT/2, 
                               BODY_WIDTH, BODY_HEIGHT, canvas);
    
        // create interior rectangles
    sleeves = new FilledRect( x+1, y+NECK_HEIGHT/2+1, SLEEVE_WIDTH-1, 
                              SLEEVE_HEIGHT-1, canvas);
    body = new FilledRect( x+BODY_INSET+1, y+NECK_HEIGHT/2+1, 
                           BODY_WIDTH-1, BODY_HEIGHT-1, canvas);

        // give it a neck hole
    neck = new FilledOval( x + NECK_INSET, y, NECK_WIDTH, 
                           NECK_HEIGHT, canvas);
    neckTrim = new FramedOval( x + NECK_INSET, y, NECK_WIDTH, 
                               NECK_HEIGHT, canvas);
    
        // set the interior to white
    body.setColor(Color.white);
    neck.setColor(Color.white);
    sleeves.setColor(Color.white);
}
Notice that all of the parameters x, y, and canvas are used in the constructor. If any were not used, we would have dropped them from the list of parameters.

The parameters declared in the headers of methods and constructors are called formal parameters because they are place holders for the actual values (i.e., actual parameters) which will be passed in at run-time. In the Tshirt constructor above, x, y, and canvas are formal parameters. To actually construct a Tshirt object, we write something like

   new Tshirt(100,150,canvas)
In this call the actual parameters are 100, 150, and canvas. In such a call, Java matches up actual and formal parameters by position. Thus 100 is matched up with the formal parameter x, 150 is matched up with y, and the actual parameter canvas (e.g., from Drag2Shirt) is matched up with the formal parameter canvas (the parameter in the Tshirt class).

For the last of these, the name of the actual parameter and formal parameter are the same, but this is a coincidence rather than being significant. The fact that the names are the same is irrelevant, because they are names of distinct variables declared in different classes. It is no more significant than the fact that there are two students named Davis in the Monday lab section. I could dispatch one Davis to carry a message to the other Davis, but that would be no more significant than to asking one Davis to carry a message to Scott in the other lab.

Because we will try to use names that are descriptive, we will often end up with these accidental correspondences in names of actual and formal parameters. If you find that confusing, you can always use different names. For example, we could uniformly change each occurrence of the name of the last formal parameter in the Tshirt constructor to aCanvas with no impact on the execution of the program.


Instance variables

Instance variables are used to remember information that is needed between invocations of methods in a class. A good example is the instance variable lastPoint that we have declared in the various dragging programs we have written. lastPoint is initialized each time the onMousePress method is executed. It is used and updated each time onMouseDrag is executed. If lastPoint was not an instance variable, it would not still be around after execution of each of these methods, and hence would not be available when it was needed in the next invocation of onMouseDrag.

Instance variables are usually initialized either in their declarations or in the constructor, as they were in our Tshirt class. Occasionally there will be an instance variable which is not initialized in either of those places. lastPoint is a good example of such an instance variable.


Instance variables and parameters

Let's now review one of the exercises we did earlier -- the one in which we added a reset method to the Tshirt class. This method moves the t-shirt back to its original starting position. The body simply invokes the moveTo method on the t-shirt, but we need to know where to move it back to. The x and y values passed in as parameters to the constructor are the key to that information. But because they're parameters, they're forgotten as soon as the constructor is done executing.

To remember these values, we must save them as instance variables. That is, we need to introduce two new instance variables, say startX and startY. They will be used to save the values of the constructor parameters x and y so that they can be used in the method reset. Because x and y are declared to be of type double in the constructor declaration, startX and startY must be declared to be of the same type. At the end of the Tshirt constructor we add two lines initializing the values of startX and startY. Because these variables are instance variables, they will still be around when we execute the reset method.

The skeleton of this updated class is shown below:

public class Tshirt{
    ...           
    private double startX, startY;            
            
    public Tshirt( double x, double y, DrawingCanvas canvas )
    {
       ...
       startX = x;
       startY = y;
    }

    ...
    
    public void moveTo( double x, double y){...}

    public void reset()
    {
       this.moveTo(startX, startY);
    }       
}

The use of instance variables to remember the values of parameters to constructors is very common, and will occur in many of your labs. For example, the Pole class, which is provided for you in the Magnet lab, contains an instance variable which is used to remember the object of type Magnet which was passed in to the constructor.


Local variables

Local variables differ from instance variables in that they are needed only during the execution of a single method and need not be remembered after that method has completed execution. A good example of variables that should be declared local are the variables used to generate a new cloth color in part 2 of the Laundry lab. Recall that on the release of the mouse, we had to check whether the user had dropped the clothing into the correct laundry bin. If so, we generated a new color and determined its target bin. The segment of code that does the new color generation (and determination of the target bin) is as follows:
    int redNum = colorGenerator.nextValue();
    int greenNum = colorGenerator.nextValue();
    int blueNum = colorGenerator.nextValue();
    int colorSum = redNum + greenNum + blueNum;

    if (colorSum < DARKS_BOUNDARY) // DARKS_BOUNDARY is 230
    { ... }  // set target bin to darks
    else if (colorSum > WHITES_BOUNDARY) // WHITES_BOUNDARY is 600
    { ... } // set target bin to whites
    else
    { ... } // set target bin to colors

A random number generator is used to generate numbers (let's call them redNum, greenNum, and blueNum) representing the red, green, and blue components of a new color. The method then determines whether the laundry with this new color should be deposited in the darks, whites, or colors bin. The criterion used to determine this is to compare the sum of the numeric components to the boundary cases of 230 and 600.

The variables colorSum, redNum, greenNum, and blueNum are not declared as parameters of onMouseRelease. However, it is not necessary to declare them as instance variables, as there is no need to remember them between invocations of method onMouseRelease. The values of all four of the variables are calculated from scratch each time the method is executed, and hence there is no reason to save the old values.

As a result, these variables should be declared as local variables of the method onMouseRelease. Local variables are only visible inside the method they are declared in, and their values disappear once the current execution of the method ends. In this way they are similar to parameters, but unlike parameters they are not provided with values by actual parameters sent in from the outside. They must be initialized inside the method body.

The user may declare all the variables at the beginning of the method body:

    public void onMouseRelease(Location point)
    {
       int redNum, greenNum, blueNum;
       int colorSum;
       ...
    }
or the user can declare the variables when they are initialized:
    public void onMouseRelease(Location point)
    {
        ...
        int redNum = colorGenerator.nextValue();
        int greenNum = colorGenerator.nextValue();
        int blueNum = colorGenerator.nextValue();
        int colorSum = redNum + greenNum + blueNum;
        ...
    }
Notice that these declarations are not labelled as private. Because they are local variables, they are automatically even more restricted than private instance variables. They can only be seen and used within the body of the method they are declared in, and go out of existence when the method terminates.

In the Laundry lab, some of you made the first swatch of cloth white by default. Others made it a random color, as above. If you did the latter, then your begin method would contain the code:

    int redNum = colorGenerator.nextValue();
    int greenNum = colorGenerator.nextValue();
    int blueNum = colorGenerator.nextValue();
    int colorSum = redNum + greenNum + blueNum;

    if (colorSum < DARKS_BOUNDARY) // DARKS_BOUNDARY is 230
    { ... }  // set target bin to darks
    else if (colorSum > WHITES_BOUNDARY) // WHITES_BOUNDARY is 600
    { ... } // set target bin to whites
    else
    { ... } // set target bin to colors
In this case, is it still correct to make redNum, greenNum, blueNum, and colorSum local variables? They're used in more than one method, so shouldn't they be instance variables? The answers to these to questions are yes and no. It is correct to declare them as local variables. There are a couple of reasons for this:
  1. the values of these variables in begin are not needed in onMouseRelease. They are completely independent of each other.
  2. the values of these variables don't need to be remembered from one invocation of onMouseRelease to the next. The necessary values are generated anew.

Local variables vs instance variables vs parameters

From now on, be careful of where you declare variables.