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.
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.
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 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.
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.
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 colorsIn 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: