|How to Think Like a Computer Scientist|
source ref: ebookit.html
The modulus operator works on integers (and integer expressions) and yields the remainder when the first operand is divided by the second. In Java, the modulus operator is a percent sign, %. The syntax is exactly the same as for other operators:
The first operator, integer division, yields 2. The second operator yields 1. Thus, 7 divided by 3 is 2 with 1 left over.
The modulus operator turns out to be suprisingly useful. For example, you can check whether one number is divisible by another: if x % y is zero, then x is divisible by y.
Also, you can use the modulus operator to extract the rightmost digit or digits from a number. For example, x % 10 yields the rightmost digit of x (in base 10). Similarly x % 100 yields the last two digits.
In order to write useful programs, we almost always need the ability to check certain conditions and change the behavior of the program accordingly. Conditional statements give us this ability. The simplest form is the if statement:
The expression in parentheses is called the condition. If it is true, then the statements in brackets get executed. If the condition is not true, nothing happens.
The condition can contain any of the comparison operators, which are:
Although these operations are probably familiar to you, the syntax Java uses is a little different from mathematical symbols for equal, not equal and less than or equal to. A common error is to use a single = instead of a double ==. Remember that = is the assignment operator, and == is a comparison operator. Also, there is no such thing as =< or =>.
The two sides of a condition operator have to be the same type. You can only compare ints to ints and doubles to doubles. Unfortunately, at this point you can't compare Strings at all! There is a way to compare Strings, but we won't get to it for a couple of chapters.
A second form of conditional execution is alternative execution, in which there are two possibilities, and the condition determines which one gets executed. The syntax looks like:
If the remainder when x is divided by 2 is zero, then we know that x is even, and this code prints a message to that effect. If the condition is false, the second set of statements is executed. Since the condition must be true or false, there are no conditions under which neither of the statements is executed, or both.
As an aside, if you think you might want to check the parity (evenness or oddness) of numbers often, you might want to "wrap" this code up in a method, as follows:
Now you have a method named printParity that will print an appropriate message for any integer you care to provide. In main you would invoke this method as follows:
Always remember that when you invoke a method, you do not have to declare the types of the arguments you provide. Java can figure out what type they are. You should resist the temptation to write things like:
Sometimes you want to check for a number of related conditions and choose one of several actions. One way to do this is by chaining a series of ifs and elses:
These chains can be as long as you want, although they can be difficult to read if they get out of hand. One way to make them easier to read is to use standard indentation, as demonstrated in these examples. If you keep all the statements and squiggly-brackets lined up, you are less likely to make syntax errors and you can find them more quickly if you do.
In addition to chaining, you can also nest one conditional within another. We could have written the previous example as:
There is now an outer conditional that contains two branches. The first branch contains a simple print statement, but the second branch contains another conditional statement, which has two branches of its own. Fortunately, those two branches are both print statements, although they could have been conditional statements as well.
Notice again that indentation helps make the structure apparent, but nevertheless, nested conditionals get difficult to read very quickly. In general, it is a good idea to avoid them when you can.
On the other hand, this kind of nested structure is common, and we will see it again, so you better get used to it.
The return statement allows you to terminate the execution of a method before you reach the end. One reason to use it is if you detect an error condition:
This defines a method named printLogarithm that takes a double named x as a parameter. The first thing it does is check whether x is less than or equal to zero, in which case it prints an error message and then uses return to exit the method. The flow of execution immediately returns to the caller and the remaining lines of the method are not executed.
I used a floating-point value on the right side of the condition because there is a floating-point variable on the left.
You might wonder how you can get away with an expression like "The log of x is " + result, since one of the operands is a string and the other is a double. Well, in this case Java is being smart on our behalf, by automatically converting the double to a string before it does the string concatentation.
This kind of feature is an example of a common problem in designing a programming language, which is that there is a conflict between formalism, which is the requirement that formal languages should have simple rules with few exceptions, and convenience, which is the requirement that programming languages be easy to use in practice.
More often than not, convenience wins, which is usually good for expert programmers (who are spared from rigorous but unwieldy formalism), but bad for beginning programmers, who are often baffled by the complexity of the rules and the number of exceptions. In this book I have tried to simplify things by emphasizing the rules and omitting many of the exceptions.
Nevertheless, it is handy to know that whenever you try to "add" two expressions, if one of them is a string, then Java will convert the other to a string and then perform string concatenation. What do you think happens if you perform an operation between an integer and a floating-point value?
At this point it would be fun to write some programs that generate graphical displays rather than just characters and strings. In order to do that, we have to set up some pretty complicated code, most of which will not make sense to you at this point. Here's what it looks like:
It looks pretty horrible, I know, but I mean it when I say you can ignore most of it. Here are the few things you do have to know about this code:
Java provides a collection of commands for drawing various graphical objects (lines, circles, etc.) on the screen. The example in the previous section includes one, the drawOval command.
In order to draw on the screen, you need a Graphics object, and you need to invoke a method on that object. The Graphics object is provided by the system (it is passed as an argument to paint, and then passed along to paintBox). Its name, by convention, is g, although we could have called it anything, since the names of parameters are arbitrary (like phil).
So far, we have talked about invoking methods, but we have not talked about "invoking a method on an object." The syntax is similar to invoking a method from another class:
The name of the object comes before the dot; the name of the method comes after, followed by the arguments for that method. In this case, the setColor method takes a single argument, which is a color. Color.black is a special value provided by the Color class, just as Math.PI is provided by the Math class. Color, you will be happy to hear, provides a moderate palette of other colors, including:
The second method, drawOval, takes four integers as arguments. These arguments specify a bounding box, which is the rectangle in which the oval will be drawn (as shown in the figure). Bounding boxes are always oriented horizontally or vertically; they are never at a funny angle.
If you think about it, you will see that there are really many ways you might specify the location and size of a rectangle. You could give the location of the center or any of the corners, along with the height and width. Or, you could give the location of opposing corners. The choice is arbitrary, but in any case it will require the same number of parameters, four.
By convention, the usual way to specify a bounding box is to give the location of the upper-left corner and the width and height. The usual way to specify a location is to use a coordinate system.
You are probably familiar with Cartesian coordinates in two dimensions, in which each location is identified by an x-coordinate (distance along the x-axis) and a y-coordinate. By convention, Cartesian coordinates increase to the right and up, as shown in the figure.
Annoyingly, it is conventional for computer graphics systems to use a variation on Cartesian coordinates in which the origin is in the upper-left corner of the screen or window, and the direction of the positive y-axis is down. Java follows this convention.
The unit of measure is called a pixel; a typical screen is about 1000 pixels wide. Coordinates are always integers. If you want to use a floating-point value as a coordinate, you have to round it off to an integer.
The simplest way to convert a floating-point value to an integer is to use a typecast. Typecasting is so called because it allows you to take a value that belongs to one type and "cast" it into another type (in the sense of molding or reforming, not throwing).
Unfortunately, the syntax for typecasting is ugly and the rules for when typecasting is legal (or necessary) are complicated. So we will only use typecasting in a couple of specific cases.
When you see the name of a type in parentheses, you should think of it as an operator that converts the next expression to that type. In the following example, the value of PI gets converted to an integer first, because typecasting takes precedence over multiplication. Thus the result of this assignment is 60, not 62.
Converting to an integer always rounds down, even if the fraction part is 0.99999999.
These two properties (precedence and rounding) can make typecasting awkward. An alternative is to use the round method from the Math class.
In this case the mutiplication happens first, before the method is invoked. The round method returns an integer, so there is no need to typecast the result. This code yields 63 (rounded up from 62.8319).
In the last few sections, I used the phrase "by convention" several times to indicate design decisions that are arbitrary, in the sense that there are no significant reasons to do things one way or another, but dictated by convention.
In these cases, it is to your advantage to be familiar with convention and use it, since it will make your programs easier for others to understand, but I also think it is important to distinguish between (at least) three kinds of rules:
As we go along, I will try to indicate which category various things fall into, but you might want to give it some thought from time to time.
While I am on the topic, you have probably figured out by now that the names of classes always begin with a capital letter, but variables and methods begin with lower case. If a name includes more than one word, you usually capitalize the first letter of each word, as in newLine and printParity.
After all that preparation, we are finally ready to do some graphics before the chapter ends. Starting with the basic paintBox method from a few sections ago, we'll draw a simple shape like Mickey Mouse, and then draw a fractal shape.
This version draws a single circle that takes up the whole window. To draw ears, we have to define the bounding boxes for two more circles, as shown in the figure:
Ok, I admit it doesn't look very much like Mickey Mouse, but it is easy to program. Here's what the code looks like:
As shown in the figure, the coordinates of the upper-left corner of the bounding box for the left ear are (x, y). The coordinates for the right ear are (x+width/2, y). In both cases, the width and height of the ears are half the width and height of the original bounding box.
Another drawing command with the same parameters as drawOval is
Here I am using a standard format for documenting the name and parameters of methods. This information is sometimes called the method's interface or prototype. Looking at this prototype, you can tell what types the parameters are and (based on their names) infer what they do. Here's another example:
The use of parameter names x1, x2, y1 and y2 suggests that drawLine draws a line from the point (x1, y1) to the point (x2, y2).
One other command you might want to try is
The first four parameters specify the bounding box of the rectangle; the remaining two parameters indicate how rounded the corners should be, specifying the horizontal and vertical diameter of the arcs at the corners.
There are also "fill" versions of these commands, that not only draw the outline of a shape, but also fill it in. The interfaces are identical; only the names have been changed:
There is no such thing as fillLine---it just wouldn't make sense.
I mentioned in the last chapter that it is legal for one method to call another, and we have seen several examples of that. I neglected to mention that it is also legal for a method to invoke itself. It may not be obvious why that is a good thing, but it turns out not only to be useful; it is also one of the most magical and interesting things a program can do.
For example, look at the following method:
The name of the method is countdown and it takes a single integer as a parameter. If the parameter is zero, it prints the word "Blastoff." Otherwise, it prints the number and then invokes a method named countdown---itself--passing an n-1 as an argument.
What happens if we invoke this method, in main, like this:
The execution of countdown begins with n=3, and since n is not zero, it prints the value 3, and then invokes itself...
The execution of countdown begins with n=2, and since n is not zero, it prints the value 2, and then invokes itself...
The execution of countdown begins with n=1, and since n is not zero, it prints the value 1, and then invokes itself...
The execution of countdown begins with n=0, and since n is zero, it prints the word "Blastoff!" and then returns.
The countdown that got n=1 returns.
The countdown that got n=2 returns.
The countdown that got n=3 returns.
And then you're back in main (what a trip). So the total output looks like:
As a second example, let's look again at the methods newLine and threeLine.
Although these work, they would not be much help if I wanted to print 2 newlines, or 106. A better alternative would be
This program is very similar; as long as n is greater than zero, it prints one newline, and then invokes itself to print n-1 additional newlines. Thus, the total number of newlines that get printed is 1 + (n-1), which usually comes out to roughly n.
The process of a method invoking itself is called recursion, and such methods are said to be recursive.
If you write recursive graphical methods, you often get interesting shapes called fractals. For example, to draw a fractal Mickey Mouse, we can modify paintBox so that it calls itself recursively:
Only a couple of changes are required. First, instead of invoking drawOval to draw the ears, we invoke paintBox to draw recursive ears; that is, ears that have ears that have ears of their own, and so on. Also, instead of invoking drawOval on the Graphics object g, we just invoke paintBox and pass g as an argument.
Finally, and most importantly, notice that I added a line at the beginning of the method that checks whether the height is equal to zero, and if it is, it returns immediately without drawing any circles, and without making any recursive calls. This is necessary because otherwise we would just draw smaller and smaller circles and the program would never terminate. Actually, eventually, it would crash. This is known as infinite recursion, and it is generally not considered a good idea.
The output of this program looks like this:
That might not seem too exciting yet, but with only a few modifications, you can get this:
Can you figure out how?