if (boolean expression) statement-to-execute-if-boolean-expression-true else statement-to-execute-if-boolean-expression-falseAs we've already seen, the boolean expression must appear in parentheses. We've also seen that the else part is optional. The indenting I've given here is a style suggestion. It is unnecessary from Java's perspective, but is very important for readability, because it helps to indicate that this is not simply a straight line of code in which everything is necessarily executed.
Now let's examine
statement-to-execute-if-boolean-expression-true
statement-to-execute-if-boolean-expression-falseEach of these can be:
if (boolean expression) { statement-to-execute-if-boolean-expression-true } else { statement-to-execute-if-boolean-expression-false }If it's the case that
statement-to-execute-if-boolean-expression-trueis just a primitive instruction, you can drop the curly braces. The same is true for the else part. We recommend keeping the braces, however. One good reason is that if you decide to make your statements more complex later, you'll already be set with the braces in the right places. A common mistake is to add lines and forget to add the braces!
There's one exception to our "use the braces" rule. That's the case where the else part is another if-statement. Consider the final version of the Pong program that we discussed last time. In that version we determined how to move the paddle as follows:
if (point.getX() < COURT_LEFT) { // move paddle to left edge .. } else if (point.getX() > COURT_LEFT + COURT_WIDTH - PADDLE_WIDTH) { // move paddle to right edge .. } else { // move paddle to mouse .. }We didn't say:
if (point.getX() < COURT_LEFT) { // move paddle to left edge .. } else { if (point.getX() > COURT_LEFT + COURT_WIDTH - PADDLE_WIDTH) { // move paddle to right edge .. } else { // move paddle to mouse .. } }This second way would be completely legal, and it follows our rule for always using braces. But in this second case:
Suppose the first thing that we wanted to do was to check if it was between the edges? Because we need it to be inside the left edge and inside the right edge, we need to write an expression to determine if both of these are true. In Java we use && betwen two boolean expressions to indicate that both must be true.
In order to ensure that point is between the left and right edges of the court we would need to write a statement as below:
if (point.getX() > COURT_LEFT && point.getX() < COURT_LEFT + COURT_WIDTH - PADDLE_WIDTH) {...}For the code in the if statement to be executed, it is necessary for the boolean expression to the left of the && (checking to see if point is to the right of COURT_LEFT) to be true as well as the expression on the right.
Unfortunately computer programming languages do not usually allow one to combine two inequalities as one, as in the expression 1 < x < 10. Instead one must write this out the long way as 1 < x && x < 10.
Why can't we combine inequalities into one expression in Java? Why can't we say
1 < x < 10The answer is that < is an operator which produces a value. When Java process a line like this one, it evaluates
1 < xfirst, getting the result true (if x has a value like 9, for example). Then it compares the result (which is true) to 10. This makes no sense! So while it may seem inconvenient to write complex conditionals in the way that we have to write them, it really makes perfect sense.
Just as Java uses && to represent the logical and operation, it uses || to represent the logical or. The operation || is used in Java to determine whether at least one of two expressions is true. Thus x < 5 || x > 20 will be true if x is less than 5 or if x is greater than 20. An expression of the form bexp1 || bexp2 will be true if either or both of bexp1 and bexp2 are true.
Note that not every expression is evaluated strictly left to right. There is a set of precedence rules that specify the order in which sub-parts of an expression should be evaluated:
The types int and long both represent integers. Type int represents numbers between about -2 * 109 and 2 * 109, while long represents integers up to about 1019.
Numbers written with decimal points are represented by the types float and double. The type float only retains about 7 digits of precision, so we usually use double instead. Type double has about 15 digits of precision and can represent numbers up to about 10308 and as small as 10-308. These numbers can be written either simply with a decimal point, e.g., 4.735, or in scientific notation, e.g., 5.146E+47, representing 5.146*1047.
Because of the way many of the Java libraries are written we will tend to use type int for integers and double for numbers with decimal points. Occasionally we will use long when we need to represent more digits with integers, but we will find no need to use float in this course.
Like int, the type double supports operations +, -, *, and /, but does not have an operation corresponding to %. The results of applying these operators to doubles is an answer which is also a double. Thus 3.0 / 2.0 = 1.5.
If an arithmetic operator has one operand with type int and the other with type double, the result will be a double. Basically what happens is that Java recognizes that the two operands are of different types (which it is not happy about), and thus attempts to make them be of the same type. The simplest thing, from Java's point of view is to convert int's to double's, as no loss of precision results. As we saw earlier, if both operands are of type int, the result will also be of type int.
One must be careful in performing divisions to be aware of the types of the operands, as the results may differ depending on their types. Thus 3 / 2 = 1, while 3.0 / 2.0 = 3.0 / 2 = 3 / 2.0 = 1.5.
You've also used objects from other classes -- such as Lines, Text, and FilledRects. In general, Java programs involve the interaction of objects from several classes. Today we'll talk about how you can write your own class.
Consider the laundry sorter lab you're working on this week. It's very primitive, given its intended application, because it can only deal with rectangles of different colors. It would be great if it worked with pictures of different clothing items. For now, we'll be satisfied to have it work with t-shirts of different colors.
Demo. T-shirt Sorter
How was this done? In order to help us focus on the t-shirt, rather than the sorting, let's consider a simpler program that simply displays a t-shirt and lets us drag it around the window.
Demo. Drag a T-shirt
In this program, we construct a t-shirt in the begin method. Before we can begin to drag the t-shirt with the mouse, we have to determine whether the mouse has been pressed on the t-shirt. This involves figuring out whether the mouse has been pressed in any of the pieces of the t-shirt. Finally, to do the actually dragging, we have to move every component of the t-shirt.
This certainly works. But wouldn't it be wonderful if we could simplify our program to look more like the original box dragging version. That is, wouldn't it be great if it could look like this:
import objectdraw.*; import java.awt.*; public class WhatADrag extends WindowController{ // Starting position for t-shirt private static final int START_LEFT = 200, START_TOP = 100, private Tshirt theShirt; // shirt to be dragged private Location lastPoint; // point where mouse was last seen // whether the shirt has been grabbed by the mouse private boolean shirtGrabbed = false; // make the t-shirt public void begin() { theShirt = new Tshirt(START_LEFT, START_TOP, canvas); } // Save starting point and whether point was in the shirt public void onMousePress(Location point) { lastPoint = point; shirtGrabbed = shirt.contains(point); } // if mouse is in shirt, then drag the shirt public void onMouseDrag(Location point) { if ( shirtGrabbed ) { shirt.move( point.getX() - lastPoint.getX(), point.getY() - lastPoint.getY() ); lastPoint = point; } } }The bad news is that Java doesn't provide for us the ability to construct and move t-shirts. Nor does our library. But the good news is that with a little bit of work, we can do this! All we need to do is learn how to define a new class of objects.
public class Name { constant and variable declarations methods }Our earlier classes always extended WindowController, which provides us the opportunity to handle mouse actions. With the new classes we will be defining, that will generally not be relevant.
In the case of t-shirts, this might look something like:
public class Tshirt { constant and variable declarations public void move(double xOffset, double yOffset ) { ... } public boolean contains( Location point) { ... } }
When we define a class:
public void move( double xOffset, double yOffset)The first keyword occuring in a method declaration is one of public or private. (The keyword protected is also allowed, but we won't be using it in this course.) These keywords determine the visibility of a method. If a method is declared to be public then it can be called from methods in other classes. For example, the onMouseDrag method of the Laundry class sent the move method to the FramedRect representing the laundry item. This would not have been possible if that first keyword had been private.
All of our instance variables are private, because they should only be used inside of the class where they are declared. Occasionally we will have constants that need to be visible outside of the class that contains them. In that case we will declare them as public static final ....
The next keyword in the declaration of move above is void. This (not very intuitively) indicates that it is a mutator method. That is, it simply performs an action rather than returning a value which can be used in an expression.
The parameters of move are of type double. These give the distance that the object should move in the x and y directions, respectively. Recall that the way we use the move method is as follows:
anObject.move(10, 20);
Another example of a method is
public boolean contains( Location point )This differs from the move method by having a return type of boolean. That is, the results of sending this method to an object will be a value of either true or false. As a result, it is used in a context expecting a boolean result, such as:
if (anObject.contains(lastPoint)) ...
In method headers/signatures, parameters serve as declarations of variables. Thus the occurrence of "Location point" between parentheses, serves to declare the variable point as a parameter of type Location. Parameters are used to pass along information to a method. Thus the body of method contains can use the variable point to represent the current location of the mouse. If we were to write the method move, the parameters xoff and yoff represent the amount we wish to move the object.
Of course we have already been using parameters when overriding mouse event methods such as onMouseClick when extending WindowController. The parameter point, which was declared to have type Location in the method header, was used as the current location of the mouse in writing the bodies of these methods.
We have also been declaring and using instance variables for some time - they are simply the variables declared at the beginning of classes. Thus we need only discuss the form of constructors to finish.
The form of the constructor will be very similar to that of methods:
public ClassName( params here ... )The name of the constructor will always be the same as the name of the class being defined. Thus if we were defining a class named Tshirt, the constructor would have the same name. Constructors are generally public, and may have as many parameters as are necessary to provide them with the information necessary to initialize instance variables. However, constructors differ from methods by omitting a return type before the constructor's name.
As an example, the constructor for class Tshirt might have parameters corresponding to the starting location of the upper left hand corner of the T-shirt, as well as information on what canvas the T-shirt should be drawn. Thus its declaration might look like:
public Tshirt(double x, double y, DrawingCanvas aCanvas)Again, notice there is no type between the keyword public and the name of the class, Tshirt. If this were a method declaration we would need a void to occur between these two if it were a mutator method, and a type name if it was an accessor method that returned a value of that type.
The type of the third parameter of the Tshirt constructor is new. DrawingCanvas is the type of the variable canvas that we have been using when creating graphic objects. It is the type of a surface that can be used for drawing graphic objects.
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 T-shirt 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); } }
The constructor takes the parameters x, y, and canvas, and creates the rectangles and ovals which form the T-shirt. The methods move, moveTo, and contains have exactly the same declarations as the methods of the same name of our other graphic objects.
The method move is straight-forward. 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.
In summary, 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. But what does this stand for? It stands for the object executing the current method.