After that necessary detour into the workings of the hardware, we can now resume our regularly scheduled explanation of the creative possibilities of computers. It may sound odd to describe computers as providing grand scope for creative activities: Aren't they monotonous, dull, unintelligent, and extremely limited? Yes, they are. However, they have two redeeming virtues that make them ideal as a canvas of invention: They are extraordinarily fast and amazingly reliable. These characteristics allow the creator of a program to weave intricate chains of thought and have a fantastic number of steps carried out without fail. We'll begin to explore how this is possible after we go over some definitions and objectives for this chapter.
A keyword is a word defined in the Java language, such as if
and while
.
An identifier is a user-defined name; variable names are
identifiers. Identifiers must not conflict with keywords such as if
and while
; for example, you cannot create a variable with
the name while
.
An operator is one of the facilities of Java that is built
into the language rather than being added on later, and therefore can
have a name that does not conform to the rules for identifiers.
Examples are +
, -
, and =
.
A statement is a complete operation understood by the Java
compiler. Each statement is ended with a semicolon (;
).
A token is a part of a program that the compiler
treats as a separate unit. It's analogous to a word in English; a statement
is more like a sentence. For example, String
is a token,
as is (
. On the other hand, x = 5;
is a
statement.
An expression is one of the units of which a statement
is made. It is made up of one or more tokens. In the statement i
= k + 3;
, "k + 3
" is an expression composed of the
three tokens k
, +
, and 3
.
An if
statement is a statement
that causes its controlled statement to be executed if the
condition specified in the if
statement is true
.
A while
statement is a statement
that causes its controlled statement to be executed while a
specified condition is true
.
A block is a group of statements that are considered
one logical statement. A block is delimited by the "curly braces", {
and }
; the first of these symbols starts a block, and the
second one ends the block. A block can be used anywhere that a
statement can be used, and is treated in exactly the same way as if it
were one statement.
By the end of this chapter, you should:
if
and while
to
control the execution of a Java program.1
The most impressive attribute of modern computers is, of course, their speed; as we have already seen, this is measured in MIPS (millions of instructions per second).
Of course, raw speed is not very valuable if we can't rely on the results we get. ENIAC, one of the first electronic computers, had a failure every few hours on the average; since the problems it was used to solve took about that much time to run, the likelihood that the results were correct wasn't very high. Particularly critical calculations were often run several times, and if the users got the same answer twice, they figured it was probably correct. By contrast, modern computers are almost incomprehensibly reliable. With almost any other machine, a failure rate of one in every million operations would be considered phenomenally low, but a computer with such a failure rate would make dozens or hundreds of errors per second.2
On the other hand, if computers are so reliable, why are they blamed for so much that goes wrong with modern life? Who among us has not been the victim of an erroneous credit report, or a bill sent to the wrong address, or been put on hold for a long time because "the computer is down"? The answer is fairly simple: It's almost certainly not the computer that is really at fault. It may be the software, other equipment such as telephone lines, tape or disk drives, or any of the myriad "peripheral devices" that the computer uses to store and retrieve information and interact with the outside world. Usually, it's the software; when customer service representatives tell you that they can't do something obviously reasonable, you can bet that the problem is the software. For example, I once belonged to a 401K plan whose administrators provided statements only every three months, about three months after the end of the quarter; in other words, in July I found out how much my account had been worth at the end of March. To estimate how much I had in the meantime, I had to look up the share values in the newspaper and multiply by the number of shares. Of course, the mutual fund that issued the shares could tell its shareholders their account balances at any time of the day or night; however, the company that administered the 401K plan didn't bother to provide such a service, as it would have required doing some work.3 Needless to say, whenever I hear that "the computer can't do that" as an excuse for such poor service, I reply "Then you need some different programmers."
All of this emphasis on computation, however, should not blind us to the fact that computers are not solely arithmetic engines. The most common application for which PCs are used is word processing, which is hardly a hotbed of arithmetical calculation. While we have so far considered only numeric data, this is a good illustration of the fact that computers also deal with another kind of information, which is commonly referred to by the imaginative term non-numeric variables. Numeric variables are those suited for use in calculations, such as in totalling a set of weights. On the other hand, non-numeric data are items that are not used in calculations like adding, multiplying, or subtracting: Examples are names, addresses, telephone numbers, Social Security numbers, bank account numbers, or drivers license numbers. Note that just because something is called a number, or even is composed entirely of the digits 0-9, does not make it numeric data by our standards. The question is how the item is used. No one adds, multiplies, or subtracts drivers license numbers, for example; they serve solely as identifiers and could just as easily have letters in them, as indeed some do.
For the present, though, let's stick with numeric variables. We've
already discussed the short
, but in the rest of this book
we'll be using its big brother, the int
, which is 4 bytes
long and therefore can represent numbers from -2147483648 (-2^31) to
2147483647 (2^31-1). What can we do with variables of this type?
To do anything with them, we have to write a Java program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into a form called "byte-code instructions", or just "byte codes" for short. This raises a couple of interesting points:
Before we can answer these questions, we have to consider a more general notion, that of a "virtual computer". We'll discuss this immediately after adding some more terms to our vocabulary.
Unlike many words in the vocabulary of computing, virtual has more or less retained its standard English definition: "That is so in essence or effect, although not formally or actually; admitting of being called by the name so far as the effect or result is concerned."4 In other words, a virtual computer would be something that acts just like a computer, but really isn't one. Who would want such a thing?
Apparently everyone, since virtual computer is just another name for what we have been calling software. This may seem a rash statement, but it really isn't. One of the most important mathematical discoveries (inventions?) of the twentieth century was Alan Turing's demonstration that it was possible to create a fairly simple computing device (called a Turing machine for some reason) that could imitate any other computing device. This machine works in the following way: You provide it with a description of the other computer you want it to imitate, and it follows those directions. Suppose we want a computer that calculates only trigonometric functions. Then we could theoretically write a set of instructions as to how such a computer would behave, feed it into a Turing machine, and have the Turing machine imitate the behavior of this theoretical "trigonometric computer".
This is undoubtedly interesting, but you may be wondering what it has to do with programming. Well, what we're actually doing when we write a Java program is creating instructions to be used by another program called the Java interpreter, describing the actions that would be taken by a hypothetical specialized computer that would solve the problem our program is designed to solve. When we run the program, the Java interpreter simulates these actions. In other words, our program is the definition of a particular virtual computer.
Of course, the Java interpreter can't really simulate any possible program, because there are limits in the amount of memory or disk space it has available, as well as limits on the speed of its execution. However, for problems that can be solved within those limits, it is a general-purpose computing facility that can be tailored to a particular problem by programming.
In the particular case of Java, there are actually two levels of virtuality; whereas your Java program is executed by the Java interpreter, the Java interpreter itself is executed by a real, physical computer component. As we've seen in the previous chapter, that component, the CPU (for Central Processing Unit), is the active ingredient inside a computer; the RAM and disk are merely the tools that it uses to store and retrieve data. In other words, the Java interpreter is a "virtual computer" that is simulated by the CPU, whereas a Java byte-code program is a "virtual computer" that can be simulated by the Java interpreter.
So now we can answer the question of what a byte-code instruction is: It's the primitive unit of computation in the Java interpreter, just as a machine instruction is the primitive unit of computation in a physical CPU.
Now we're ready to answer those questions that we put on hold for our little excursion into virtual reality. First, let's discuss byte codes in some more detail.
Like a physical CPU, the Java interpreter understands a limited set of instructions, each of which does a very simple task such as adding two numbers together or comparing two numbers to see which is greater. These are much simpler than the source-code statements that we write in our Java programs. Therefore, our source-code programs have to go through an intermediate step, called compilation, which converts them from source code to byte codes so that they can be executed by the Java interpreter.
It may not be obvious why we need both an interpreter and a compiler. It wasn't to Susan.
Susan: Can you have just an interpreter and no compiler? Do all interpreted languages then need a compiler?The most basic tasks that the Java compiler performs are:5Steve: Yes, you can have an interpreter without a compiler, but that's not very common because it is so inefficient; you don't want to do all the work to find out what each statement means every time you look at it, so virtually all interpreted languages translate the source code into a form that is easier to interpret at run time.
Susan: The interpreter doesn't work until run time?
Steve: Right.
+
,
-
, etc.) into the equivalent byte codes, including
references to variables assigned in the previous step. Susan had some questions about what the compiler actually does for us.
Susan: A byte-code instruction is in binary?Steve: Yes, or hex.
Susan: So it attaches instructions to the variables?
Steve: Not exactly. We'll see shortly how it translates the named variables that we use in writing source code so they can be used when we're running a program.
This is probably a bit too abstract to be easily grasped, so let's look at an example. Figure littlenumeric shows some sample statements that do arithmetic calculations.
int i;
int j;
int k;
int m;
i = 5;
j = i * 3; // j is now 156k = j - i; // k is now 10
m = (k + j) / 5; // m is now 5
i = i + 1; // i is now 6
To enter such statements in the first place, follow the instructions in the "readme.txt" file on the CD in the back of the book.7 Once we have entered the statements for our program, we use the compiler to translate the programs we write into a form that the Java interpreter can perform; as defined in Chapter prologue.htm, the form we create is called source code, since it is the source of the program logic, whereas the form of our program that the Java interpreter can execute is called a byte-code program.
As I've mentioned previously, there are several types of variables,
the int
being only one of these types. Therefore, the
compiler needs some explanatory material so that it can tell what types
of variables you're using; that's what the first four lines of our
little sample program fragment are for. Each line tells the compiler
that the type of the variable i
, j
, k
,
or m
is int
, which specifies the range of
values it can hold.8 After this
introductory material, we move into the list of operations to be
performed. This is called the executable portion of the
program, as it actually causes the Java interpreter to do something
when the program is executed; the operations to be performed, as
mentioned previously, are called statements. The first one, i
= 5;
, sets the variable i
to the value 5
.
A value such as 5
, which doesn't have a name, but
represents itself in a literal manner, is called (appropriately enough)
a literal value.
This is as good a time as any for me to mention something that
experienced programmers, especially C programmers, take for granted but
has a tendency to confuse novices. This is the choice of the =
sign to indicate the operation of setting a variable to a value, which
is known technically as assignment. As far as I'm concerned, an
assignment operation would be more properly indicated by some symbol
suggesting movement of data, such as 5 => i;
, meaning
"store the value 5 into variable i
". Unfortunately, it's
too late to change the notation for the assignment statement,
as such a statement is called, so you'll just have to get used to it.
The =
in a statement such as i = 5;
means
"set the variable on the left (in this case, i
) to the
value on the right (in this case, 5
)".9
Now that I've warned you about that possible confusion, let's continue
looking at the operations in the program. The next one, j = i *
3;
, specifies that the variable j
is to be set to
the result of multiplying the current value of i
by the
literal value 3
. The one after that, k = j - i;
,
tells the Java interpreter to set k
to the amount
calculated by subtracting i
from j
; that
is, j - i
. The most complicated line in our little
program fragment, m = (k + j) / 5;
, calculates m
as the sum of adding k
and j
and dividing
the result by the literal value 5
. Finally, the line i
= i + 1;
sets i
to the value of i
plus the literal value 1
.
This last may be somewhat puzzling; how can i
be
equal to i + 1
? The answer is that an assignment
statement is not an algebraic equality, no matter how much it
may resemble one. It is a command telling the Java interpreter to
assign a value to a variable. Therefore, what i = i + 1;
actually means is "take the current value of i
, add 1
to it, and store the result back into i
." In other words,
a Java variable is a place to store a value; the variable i
can take on any number of values, but only one at a time; any former
value is lost when a new one is assigned.
This notion of assignment was the topic of quite a few messages with Susan. Let's go to the first round.
Susan: I am confused with the statementWith any luck, that point has been pounded into the ground, so you won't have the same trouble that Susan did. Now let's look at exactly what an assignment statement does. If the value ofi = i + 1;
, when you have stated previously thati = 5;
. So which one is it? How can there be two values fori
?Steve: There can't; that is, not at one time. However,
i
, like any other variable, can take on any number of values, one after another. First, we set it to5
; then we set it to 1 more than it was before (i + 1
), so it ends up as6
.Susan: Well, the example made it look as if the two values of
i
were available to be used by the Java interpreter at the same time. They were both lumped together as executable material.Steve: After the statement
i = 5;
, and before the statementi = i + 1;
, the value ofi
is5
. After the statementi = i + 1;
, the value ofi
is6
. The key here is that a variable such asi
is just our name for some area of memory that can hold only one value at one time. Does that clear it up?Susan: So it is not like algebra? Then
i
is equal to an address of memory and does not really equate with a numerical value? Well, I guess it does when you assign a numerical value to it. Is that it?Steve: Very close. A variable in Java isn't really like an algebraic variable, which has a value that has to be figured out and doesn't change in a given problem. A programming language variable is just a name for a storage location that can contain a value.
i
before the statement i = i + 1;
is 5 (for example), then
that statement will cause the Java interpreter to perform the following
steps:10
i
(5). i
. After the execution of this statement, i
will have the
value 6.
In a moment we're going to dive a little deeper into how the Java interpreter accomplishes its task of manipulating data, such as we are doing here with our arithmetic program. First, though, it's time for a little pep talk for those of you who might be wondering exactly why this apparent digression is necessary. It's because if you don't understand what is going on under the surface, you won't be able to get past the "Sunday driver" stage of programming in Java. A good Java programmer needs to know a fair amount about the internal workings of the language, for reasons which will become very apparent later in the book. For the moment, you'll just have to take my word that working through these intricacies is essential; the payoff for a thorough grounding in these fundamental concepts of computing will be worth the struggle.
I can almost hear the wailing and tooth gnashing out there. Do I expect you to deal with byte codes by yourself? You'll undoubtedly be happy to learn that this isn't necessary, as the compiler and the interpreter take care of these details. On the other hand, if you don't have some idea of how these programs work, you'll be at a disadvantage when you're trying to figure out how to make Java do what you want. Therefore, we're going to spend some time "playing compiler"; that is, I'll examine each statement and indicate what action the compiler might take as a result. I'll simplify the statements a bit to make the explanation simpler; you should still get the idea (I hope). Figure reallylittle illustrates the set of statements that I'll compile.11 However, before we start to analyze how the compiler does its job, we'll need to go over one of the fundamental methods that the Java interpreter and compiler use to organize their data: the stack.
Variables in the program fragment we'll be considering are stored in a data structure called a stack; the name is intended to suggest the notion of stacking clean plates on a spring-loaded holder such as you might see in a cafeteria. The last plate deposited on the stack of plates will be the first one to be removed when a customer needs a fresh plate.
But what does this have to do with programming? I think this will become clearer when we see some examples. First, though, we'll need some new terms to describe the behavior of a stack.
A stack with one entry might look something like Figure stack1.
+--------------------+
TOP | 1234 |
+--------------------+
If we add (or push) another value on to the stack, say 999, the result would look like Figure stack2.
+--------------------+
TOP | 999 |
+--------------------+
2nd | 1234 |
+--------------------+
If we were to push one more item, this time with the value 1666, the result would look like Figure stack3.
+--------------------+
TOP | 1666 |
+--------------------+
2nd | 999 |
+--------------------+
3rd | 1234 |
+--------------------+
Now, if we retrieve (or pop) a value, we'll get the one on top; namely, 1666. Then the stack will look like it did in Figure stack2. The next value to be popped off the stack will be the 999, leaving us with the situation in Figure stack1 again. If we continue for one more round, we'll get the value 1234, leaving us with an empty stack.
As we'll see, the actual way that a stack is implemented is a bit different than is suggested by the "stack of plates" analogy, although the effect is exactly the same. Rather than keeping the top of the stack where it is and moving the data (a slow operation), the data are left where they are and the address stored in a stack pointer is changed, which is a much faster operation. In other words, whatever address the stack pointer is pointing to is by definition the "top of the stack".12 The idea of a stack led to some discussions with Susan. Here's the first installment:
Susan: Where is the stack?Steve: The actual memory locations used to hold the items in the stack are just like any other locations in RAM; what makes them part of the stack is how they are used. As always, one memory location can hold only one item at a given time, so the locations used to hold entries on the stack cannot be simultaneously used for something else, like byte codes.
Susan: Where you say "what makes them part of the stack is how they are used." How is that?
Steve: RAM is RAM. It can be used to store programs, data on the stack, or other types of data that we'll get to later. What distinguishes these is how the memory is used, not what it's physically made of.
Susan: Yes, but what is different about the use of the RAM used in the stack?
Steve: It's used to hold the data in the stack, rather than byte codes or other kinds of data.
Susan: Then how does the stack pointer "talk" to the stack to change what it is pointing to?
Steve: It doesn't have to. Whatever the stack pointer is pointing to is by definition the top of the stack; therefore, changing the contents of the stack pointer changes the top of the stack.
Susan: So what makes the stack pointer change?
Steve: The interpreter, as it is executing the program. Each byte code instruction has a defined effect on the stack pointer (pushing one or more values, popping one or more values, doing both, or doing neither.
Susan: What do you mean as the interpreter is executing the program? Do you mean that it doesn't work until run time?
Steve: Yes.
Susan: Okay, then if this is what the interpreter does, what does the compiler do? Does it just compile the program into byte-codes that the interpreter can read?
Steve: Yes.
Susan: Then on stacks, is it like this? The stack stays still, and the pointer to the value's address moves.
Steve: The data stays where it is in memory, and the stack pointer points to different places in memory. Whatever it points to is the top of the stack.
Susan: Where is the stack pointer relative to the stack?
Steve: It isn't relative to the stack. It's a register, which doesn't have a memory address.
Susan: I don't get that.
Steve: A register is a storage area that is on the same chip as the CPU. Programs use registers to hold data items that are actively in use; data in registers can be accessed much faster than if it had to be retrieved from RAM every time it was needed.
Susan: But I thought that the stack was in the registers.
Steve: No, the stack is in RAM. The stack pointer is the register that indicates exactly where the top of the stack is in RAM.
Susan: Put that in the book again, darling. Repeat this many times, and then maybe I will remember it.
Steve: It's worth a try.
Now we're just about ready to start analyzing a small program fragment to see how it is actually represented in Java byte codes. Here are the rules of this "game":
Figure reallylittle shows the program fragment that we're going to compile.
int i;
int j;
i = 5;
j = i + 3;
Now we're ready to start compiling. The
first statement, int i;
, tells me to allocate storage for
an int
variable called i
; since i
is an int
, it takes up 4 bytes of memory, which
corresponds to one "slot" on the stack. Therefore, the "memory map" at
the beginning of our program fragment will look like Figure compile1 so far, with the ?s indicating
that the variable i
hasn't been initialized yet; that
will happen before the program fragment starts.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| |
| +--------------+ |
| 1 | ???????? | i |
| +--------------+ |
| |
+--------------------------------------------+
Susan: So the first thing we do with a variable is to tell the address that its name isThe second statement,i
, but no one is home, right? It has to get ready to accept a value. Could you put a value in it without naming it, just saying that the first entry on the stack has a value of 5? Why does it have to be calledi
first?Susan: What are those local variable slots?
Steve: They're the part of the stack that is used to store local variables.
Susan: So is the stack where the compiler puts the variables?
Steve: Yes, the compiler puts them in the local variable part of the stack.
Susan: Are those the places in the stack that do the arithmetic?
int j;
, tells
me to allocate storage for an int
variable called j
.
Just as with i
, j
takes 4 bytes of storage,
or one slot position on the stack. As before, the ?s indicate that no
value has been assigned to this variable yet; it will also be
initialized to 0 at run time, so the resulting "memory map" as of the
beginning of our program fragment will look like Figure compile2.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| |
| +--------------+ |
| 1 | ???????? | i |
| +--------------+ |
| 2 | ???????? | j |
| +--------------+ |
| |
+--------------------------------------------+
The next line is blank, so we skip it. This
brings us to the statement i = 5;
which is an executable
statement, so we need to generate one or more byte codes to execute it.
Figure compile3 shows what the
code for this program fragment looks like so far, with the statement
being compiled listed as a comment before the byte codes that are
responsible for executing it.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | ???????? | i |
| +--------------+ |
| 2 | ???????? | j |
| +--------------+ |
| |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
+-----------------------------------------------------------+
I'm sure you're wondering what those iconst_5
and istore_1
instructions mean. Susan certainly did.
Susan: What is "iconst"?As soon as we compile the rest of the code, we'll see how the Java interpreter performs arithmetic operations.
Figure compile4 shows what the "memory map" looks like now.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000000 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Here's the next installment of the byte-code discussion with Susan:
Susan: Soiload_1
is putting i into the local variable slot #1.Susan: Then
iload_1
is a retrieving function?Susan: What are the exact functions of
iadd
andistore
and how do they do that?
Having examined what the compiler does at compile time with the preceding little program fragment, the next question is what happens when the compiled program is interpreted. When we start out, the stack and code areas of memory we're concerned with will look like Figure execute1.13
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000000 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation (empty) |
| Area |
| |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Susan: What is the "expression evaluation area"?Now let's see exactly how the evaluation of our statements works. Starting out with an empty expression evaluation area, we execute theSteve: The part of the stack used to store intermediate values during execution of the byte codes.
Susan: I need some more explanation of this.
Susan: So the expression evaluation area is like your desktop? A place to work?
Susan: How do I know which byte codes push and which ones pop?
iconst_5
byte code, which pushes the
constant int
value 5
onto the stack. This
leaves us with the situation in Figure execute2.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000000 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation |
| Area |
| |
| +--------------+ |
| 1 | 00000005 | 5 |
| +--------------+ |
+--------------------------------------------+
Code
+------------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+------------------------------------------------------------+
The next step is to execute the istore_1
byte code, which pops the value from the top of the stack into local
variable 1 (i
); that is, it sets i
to the
value on the top of the stack, and removes that value from the stack.
This leaves us with the situation in Figure execute3. Note that the value of i
has been changed to 5
, and the expression evaluation area
of the stack is empty.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation (empty) |
| Area |
| |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Next, we start on the statement j = i + 3;
.
The Java interpreter will perform this operation by reloading the value
of i
(variable 1), pushing a literal 3
onto
the stack, adding the top two elements of the stack, and finally
storing the result into variable 2 (j
). Let's go over that
step by step, starting with the byte code iload_1
, which
pushes the value of local variable 1 (i
) onto the stack.
This leaves us with the situation in Figure execute4.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation |
| Area |
| |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Next, we execute the byte code iconst_3
,
which pushes the value 3
onto the stack. This leaves the
situation shown in Figure execute5.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation |
| Area |
| |
| +--------------+ |
| 1 | 00000003 | 3 |
| +--------------+ |
| 2 | 00000005 | i |
| +--------------+ |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Next, we execute the iadd
byte
code, which pops the top two entries in the stack, adds them together,
and pushes the sum. This leaves the situation shown in Figure execute6.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
| 2 | 00000000 | j |
| +--------------+ |
| |
| Expression |
| Evaluation |
| Area |
| |
| +--------------+ |
| 1 | 00000008 | i+3 |
| +--------------+ |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Finally, we execute the istore_2
byte code, which stores the top entry in the stack into local variable
2 (j
). This leaves the situation shown in Figure execute7.
Stack
+--------------------------------------------+
| Local Value Meaning |
| Variable |
| Slots |
| +--------------+ |
| 1 | 00000005 | i |
| +--------------+ |
| 2 | 00000008 | j |
| +--------------+ |
| |
| Expression |
| Evaluation (empty) |
| Area |
+--------------------------------------------+
Code
+-----------------------------------------------------------+
| Address Byte Code Description |
| |
| // i = 5; |
| 00000000 iconst_5 Push 5 |
| 00000001 istore_1 Pop into i |
| |
| // j = i + 3; |
| 00000002 iload_1 Push i |
| 00000003 iconst_3 Push 3 |
| 00000004 iadd Pop top two stack |
| entries, push the sum |
| 00000005 istore_2 Pop into j |
| |
+-----------------------------------------------------------+
Here's the next installment of my discussion with Susan on this topic:
Susan: Now this may sound like a very dumb question, but please tell me where5
comes from? I mean if you are going to move the value of5
onto the stack, where is5
hiding to take it from and to put it in the stack? Is it stored somewhere in memory that has to be moved, or is it simply a function of the user just typing in that value?
This brings us to the subject of two new
variable types and the values they can contain. These are the char
and its relative, the String
. What are these good
for, and how do they work?14 A
variable of type char
corresponds to 1 character of text,
which in Java occupies 2 bytes (16 bits) of storage. Since a char
has 16 bits, it can hold any of 65536 (2^16) values, which is the same
number of values that a short
can hold. So what's the
difference between these two types?
The main purpose of a char
is to represent an
individual letter, digit, punctuation mark, "special character" (e.g.,
$, @, #, %, and so on), or one of the other "printable" and displayable
units from which words, sentences, and other textual data such as this
paragraph are composed.15 We don't need
anywhere near 65,536 possibilities to represent any character in
English, as well as a number of European languages; in fact 256
possibilities is enough to handle all of the characters in those
languages, so in most programming languages other than Java a char
is only 1 byte long.
However, the written forms of "ideographic" languages such as
Chinese and Korean consist of far more than 256 characters, so 1 byte
isn't going to do the trick for these languages. While they have been
supported to some extent by schemes that switch among a number of sets
of 256 characters each, such clumsy approaches to the problem made
programs much more complicated and error prone. As the international
market for software is increasing rapidly, it has become more important
to have a convenient method of handling large character sets.
To solve this problem, the Unicode standard, which uses 2 bytes
per character, has been developed. This is the representation that Java
uses for its char
s.16
Even in an ideographic language, one char
isn't good
for much by itself, so we often use groups of them, called String
s,
to represent a significant amount of text. Just as with numeric values,
these variables can be set to literal values, which represent
themselves. Figure basic00 is an
example of how to specify and use each of these types we've just
encountered.
code/basic00/basic0~1.jav
char
acters and String
s
(code\Basic00\Basic00.java) (Figure basic00)By the way, in case the program in Figure basic00 doesn't seem very useful, that's because it isn't; it's just an example of the syntax of defining and using variables and literal values. However, we'll use these constructs to do useful work later, so going over them now isn't a waste of time.
The first construct we have to examine is the line public
class Basic00
, which has three components. The first component
of this line is the keyword public
, which means that
we're defining something that is generally available to any program
that wants to use it. The particular kind of "something" that we're
making available is specified by the second component of this line: the
keyword class
, which tells the Java compiler that we are
beginning the definition of a class
. What is a class
?
That's a very good question, because every program in Java is composed
of the definitions of one or more class
es. This is
obviously a very important part of the language; however, you'll need a
significant amount of background in other aspects of the Java language
before its explanation is likely to mean very much to you, so I'm
deferring that discussion until Chapter inventor.htm.
For now, just take my word that every Java program has to start with a
line telling the compiler the name of the class
that
we're defining.
As this brief explanation suggests, the third component of this
line is Basic00
, which is the name of the particular class
we're defining in this program. We could call it whatever we wanted,
but since it's the first example program in this chapter on the basics
of programming, I named it Basic00
so that you and I
could keep track of it more easily.
Once we have specified the name of the class
that
we're creating, we can define the piece of code that is going to
actually do the work for us. That's the job of the next line:
public static void main( String args[ ] )
This will be the first line of every main
function
that we will examine in this book. Eventually I'll be able to explain
exactly what every part of this line means, but you don't have enough
background yet. Therefore, you'll have to take my word that everything
on that line will eventually mean something.
However, there is one thing I can explain: the meaning of main
.
Java has a rule that execution always starts at the place called main
.
Since this is where we want our program to start executing, we have to
call it by that name; marking the place where we want to start is the
main purpose of the line we're discussing.
You may also be puzzled by the function of the other statements in this program. If so, you're not alone. Let's see the discussion that Susan and I had about that topic.
Susan: Okay, in the example why did you have to writeWhat does this useless but hopefully instructive program do? As is always the case, we have to tell the compiler what the types of our variables are before we can use them. In this case,c2 = c1;
? Why notB
? Why make one thing the same thing as the other? Make it different. Why would you even wantc2=c1;
and not just sayc1
twice, if that is what you want?Steve: It's very hard to think up examples that are both simple enough to explain and realistic enough to make sense. You're right that this example doesn't do anything useful; I'm just trying to introduce what both the
char
type and theString
type look like.Susan: Come to think of it, what does
c1='A';
have to do with the statements1= "Congratulations! ";
? I don't see any relationship between one thing and the other.Steve: This is the same problem as the last one. They have nothing to do with one another; I'm using an admittedly contrived example to show how these variables are used.
Susan: I am glad now that your example of
char
s andString
s (put together) didn't make sense to me. That is progress; it wasn't supposed to.
c1
and c2
are of type char
, whereas s1
,
s2
, and s3
are String
s. After
taking care of these formalities, we can start to use the variables. In
the first executable statement, c1 = 'A';
, we set the char
variable c1
to a literal value, in this case a capital A;
we need to surround this with single quotation marks ('
)
to tell the compiler that we mean the letter A rather than a
variable named A
. In the next line, c2 = c1;
,
we set c2
to the same value as c1
holds,
which of course is 'A'
in this case. The next executable
statement, s1 = "Congratulations! ";
, as you might
expect, sets the String
variable s1
to the
value "Congratulations!
", which is a String
literal.17 A String
literal
is a type of literal that we use to assign values to variables of type String
.
In the statement s1 = "
Congratulations!
";
we use a quotation mark, in this case the double quote ("
),
to tell the compiler where the literal value starts and ends.
You may be wondering why we need two different kinds of quotes in
these two cases. There really isn't a very good reason that I can think
of other than to allow the compiler to tell whether we're using a
compatible type of literal for a char
or a String
variable. As far as I can tell, this distinction is left over from C
and C++, where it made more sense, but for reasons that aren't relevant
here.
As the above discussion suggests, every variable in Java has a type.
While some languages allow the same variable to be used in different
ways at different times, in Java any given variable always has the same
type; for example, a char
variable can't change into an int
.
At first glance, it seems that it would be much easier for programmers
to be able to use variables any way they like; why is Java so
restrictive?
The Java type system, as this feature of a language is called, is specifically designed to minimize the risk of misinterpreting or otherwise misusing a variable. It's entirely too easy in some languages to change the type of a variable without meaning to; the resulting bugs can be very difficult to find, especially in a large program. In Java, the usage of a variable can be checked by the compiler. This static type checking allows the compiler to tell you about many errors that otherwise would not be detected until the program is running (dynamic type checking). This is particularly important in systems that need to run continuously for long periods of time. While you can reboot your machine if your word processor crashes due to a run-time error, this is not acceptable as a solution for errors in the telephone network, for example.
Of course, you probably won't be writing programs demanding the degree of reliability that the telephone network requires any time soon, but strict static type checking is still worthwhile in helping eliminate errors at the earliest possible stage in the development of our programs.
You might get the impression from this discussion that Java is suitable for developing large applications that have to run indefinitely. Is this true?
Most other books I've seen on Java contain a statement something like the following, often at roughly this point in the exposition:
Java is the be-all and end-all of computer languages. It is infinitely superior to every other language, especially C++, which has served its purpose now that its obvious successor, Java, has been invented. Java is suitable for every possible application, and most impossible ones. Furthermore, Java is a very simple language that you can learn in a few minutes, probably in your sleep.18This is considerably at variance with the truth. Java has some advantages over other languages, including C++. It also has some serious drawbacks that make it eminently unsuitable for many types of applications, especially large ones that have to operate for a long time without fail; for example, it would not be a good idea to write programs to control the telephone network in Java. I'll explain some of these drawbacks as we run across them in this book.
As for the claim of how simple Java is: I can't agree with that either. Java is not significantly simpler than other computer languages, and in particular it's not any simpler than the part of C++ that you should use to design new programs (as contrasted with the "dark corners" of C++ that you can stumble into when dealing with old programs). However, you don't have to take my word for the complexity of Java; you should have a pretty good idea of how complex it is by the time you get through this book!
After that info-mercial for the advantages of static type checking
(and deflation of some of the hype about Java), we can resume our
examination of String
s. You may have noticed that there's
a space character at the end of the String
"Congratulations!
"
. That's another reason why we have to use a special character
like "
(the double quote) to mark the beginning and end
of a String
; how else would the compiler know whether
that space is supposed to be part of the String
or not?
The space character is one of the non-printing characters (or non-display
characters) that controls the format of our displayed or printed
information; imagine how hard it would be to read this book without
space characters! While we're on the subject, I should also tell you
about some other characters that have special meaning to the compiler.
They are listed in Figure specialchar.
Name Graphic Use
single quote ' surrounds a single-character value
double quote " surrounds a multi-character value
semicolon ; ends a statement
curly braces { } groups statements together
parentheses ( ) surrounds part of a statement19
backslash \ tells the compiler that the next
character should be treated
differently from the way that
it would normally be treated20
I compiled Figure specialchar at the instigation of guess who.
Susan: How about you line up all your cute littleOur next task will be to see how we get the values of our" ' \ ;
things and just list their meanings? I forget what they are by the time I get to the next one. Your explanations of them are fine, but they are scattered all over the place; I just want one place that has all the explanations.Steve: That's a good idea; I think I will.
String
s
and char
s to show up on the screen.
Most programs need to interact with their users, both to ask them what they want and to present the results when they are available. The computer term for this topic is I/O (short for "input/output"). We'll start by getting information from the keyboard and displaying it on the screen; later, we'll go over the more complex I/O functions that allow us to read and write data on the disk.
The final line of code in Figure basic00,
System.out.println
( s1 + s2 + c2 + s3);
,
displays information on the screen. In this case, the output will be "Congratulations!
You got an A on the test.
". How does this occur exactly?
The first construct on this line is System.out
, which
is a predefined Java variable that is "connected" to the screen. This
variable has several built-in ways to display information; the one
we're using here is called println
, which is short for
"print and move to the next line". The value of the expression inside
the parentheses after println
will be displayed on the
screen (the print
part of println
), and the
cursor will be moved to the beginning of the next line (the ln
part of println
).println
That explains why we'll see some output on the screen, but it
doesn't explain the exact output that we'll see, which depends on
what's inside the parentheses. In this case, that's s1 + s2 + c2
+ s3
. I don't blame you if you have some trouble understanding
how we can "add" String
s and char
s
together; that's not exactly intuitively obvious to the casual
observer.
What the +
means here actually is something like
"add", but more precisely it means "add some text data to the end of
some other text data". In the current case, that whole expression
produces the following sequence of events:
s2
, and add that to the end of
the contents of s1
. Since s1
contains "Congratulations!
" and s2
contains "You got an
", the result
will be the value "Congratulations! You got an
". c1
('A
') to its end, resulting in the
value "Congratulations! You got an A
". s3
(" on the test
") to its end, resulting
in the value "Congratulations! You got an A on the test.
" Finally, the resulting value will be displayed on the screen and the cursor will be moved to the next line.
Susan had an incisive observation about this example program.
Susan: What if the student got a 'B'? Then the second sentence would read "You got an B", which is incorrect.Steve: That's a good point. I think I'll make fixing that problem into an exercise.
So much for (simple) output. Input from the keyboard is almost as simple. Let's modify our little sample to use it, as shown in Figure basic02.
code/basic02/basic0~1.jav
Susan: Are the words such aschar
andpublic
case sensitive? I had capitalized a few of them just out of habit because they begin the sentence and I am not sure if that was the reason the compiler gave me so many error messages. I think after I changed them I reduced a few messages.Susan: What does
RWVar
stand for?Steve: "Reading and writing variables".
Susan: Why is the word
System
used?Steve: Because those variables (
System.out
andSystem.in
) are supplied by the system.
In our examples so far, the program always
executes the same statements in the same order. However, any real
program is going to need to alter its behavior according to the data it
is processing. For example, in a banking application, it might be
necessary to send out a notice to a depositor whenever the balance in a
particular account drops below a certain level; or perhaps the
depositor would just be charged some exorbitant fee in that case.
Either way, the program has to do something different depending on the
balance. In particular, let's suppose that the "Absconders and
Defaulters National Bank" has a minimum balance of $10,000.
Furthermore, let's assume that if you have less than that amount on
deposit, you are charged a $20 "service charge". However, if you are
foolish enough to leave that ridiculous amount of money on deposit,
then they will graciously allow you to get away with not paying them
for the privilege of lending them your money (without interest, of
course). To determine whether or not you should be charged for your
checking account, the bank can use an if
statement,
as shown in Figure if.statement.
code/basic03/basic0~1.jav
if
statement (code\Basic03\Basic03.java) (Figure if.statement)This program starts by displaying the line
Please enter your bank balance:
If the condition is false
(that is, you have at least $10,000 in the bank), the computer skips
the statement that asks you to remit $20; instead, it executes the one
after the else
, which tells you to have a nice day.
That's what else
is for; it specifies what to do
if the condition specified in the if
statement is false
(that is, not true
). If you typed in a number 10000 or
higher, the program would display the line22
Have a nice day!
You don't have to specify an else
if you don't want
to. In that case, if the if
condition isn't true
,
the program just goes to the next statement as though the if
had never been executed.
The while
statement is another way of affecting the
order of program execution. This conditional statement executes the
statement under its control as long as a certain condition is true
.
Such potentially repeated execution is called a loop; a loop
controlled by a while
statement is called,
logically enough, a while
loop. Figure basic04 is a program that uses a while
loop to challenge the user to guess a secret number from 0 to 9, and
keeps asking for guesses until the correct answer is entered.
code/basic04/basic0~1.jav
while
statement (code\Basic04\Basic04.java) (Figure basic04) There are a few wrinkles here that we haven't
seen before. Although the while
statement itself is
fairly straightforward, the meaning of its condition !=
isn't intuitively obvious. However, if you consider the problem we're
trying to solve, you'll probably come to the (correct) conclusion that !=
means "not equal", since we want to keep asking for more guesses while
the Guess
is not equal to our Secret
number.23 Since there is a comparison operator that
tests for "not equal", you might want to know how to test for "equal"
as well. The answer is that you can test whether two int
values are equal by using the ==
operator; we'll see in
the next chapter why we can't use =
for this task.
Would an if
statement with an else
clause would serve as well as the while
? After all, if
is used to select one of two alternatives, and the else
could select the other one. The answer is that this would allow the
user to take only one guess; the while
loop lets the user
try again as many times as needed to get the right answer.
Now you should have enough information to be able to write a simple program of your own, as Susan asked to do at this point.
Susan: Based on what you have presented in the book so far, send me a setup, an exercise for me to try to figure out how to program, and I will give it a try. I guess that is the only way to do it. I can't even figure out a programmable situation on my own. So if you do that, I will do my best with it, and that will help teach me to think. (Can that be?) Now, if you do this, make it simple, and no tricks.Of course, I did give her the exercise she asked for (exercise 1), but also of course, that didn't end the matter. She decided to add her own flourish, which resulted in exercise 2.
It would be convenient to compare two variables of any type by just
using ==
or !=
, the former to tell whether
they are equal and the latter whether they are unequal. Unfortunately,
this won't work. As we'll see in much more detail later, most types of
variables in Java fall in one of two categories: primitive
(sometimes called native) and user-defined. String
s
are an exception, being sort of a cross between these two types, acting
more like the latter. You can indeed compare primitive variables such
as int
s and char
s by using the ==
or !=
operators; however, to compare non-primitive
variables, including String
s, you have to use the equals
facility. In particular, to compare whether two String
s, a
and b
, have the same value, you have to write if
(a.equals(b))
.
Does this seem much clumsier than just writing if (a == b)
,
as you would do to compare two int
s? Many diehard Java
supporters claim that it's much better to have two different ways to
compare variables, depending on whether they are actually part of the
language (primitive) or are added on afterwards (user-defined and String
s).
Personally, I think it would be better to be able to treat all types of
variables in the same way, but maybe I'm missing something here.24
We're ready for the exercises that Susan asked for, along with some others of the same general level of difficulty. To write programs to solve these exercises, see the section titled "Writing and compiling your own programs" in the file "\readme.txt" on the CD in the back of the book.
Once you have followed those instructions to write and run a program, it may work the first time you try them. However, if it doesn't (which is quite likely), you will need more information about what's happening in the program. Fortunately, Java compilers come with a debugger, which you can use to trace execution in your program. To use the debugger for your program, follow the instructions in the section titled "Using the debugger" in the file "\readme.txt" on the CD in the back of the book.
n
, display a message that
says "A table for
(n+1) is ready."
. For
example, if the user types 3, display "A table for 4 is ready."
.
Answers to exercises can be found at the end of the chapter.
Our most recent programming example has contributed another item to
our arsenal of programming weapons; namely, the ability to group
several statements into one logical section of a program. That's the
function of the curly braces, {
and }
.
The first one of these starts such a section, called a block,
and the second one ends the block. Because the two statements after the
while
are part of the same block, they are treated as a
unit; both are executed if the condition in the while
is true
,
and neither is executed if it is false
. A block can be
used anywhere that a statement can be used, and is treated in exactly
the same way as if it were one statement.25
Now we're ready to write a program that vaguely resembles a solution to a real problem. We'll start with a simple, rural type of programming problem.
Imagine that you are at a county fair. The contest for the heaviest
pumpkin is about to get underway, and the judges have asked for your
help in operating the "pumpkin scoreboard". This device has one slot
for the current pumpkin weight (the CurrentWeight
slot),
and another slot for the highest weight so far (the HighestWeight
slot); each slot can hold three digits from 0 to 9 and therefore can
indicate any weight from 0 to 999. The judges want you to maintain an
up-to-date display of the current weight and of the highest weight seen
so far. The weights are expressed to the nearest pound. How would you
go about this task?
Probably the best way to start is by setting the number in both
slots to the first pumpkin weight called out. Then, as each new weight
is called out, you change the number in the CurrentWeight
slot to match the current weight; if it's higher than the number in the
HighestWeight
slot, you change that one to match as well.
Of course, you don't have to do anything to the HighestWeight
slot when a weight less than the previous maximum is called out,
because that pumpkin can't be the winner. How do we know when we are
done? Since a pumpkin entered in this contest has to have a weight of
at least 1 pound, you enter 0 as the weight when the contest is over.
At that point, the number in the HighestWeight
slot is
the weight of the winner.
The procedure you have just imagined performing can be expressed a bit more precisely by the following algorithm:
CurrentWeight
slot to this
value. CurrentWeight
slot to the HighestWeight
slot. CurrentWeight
value is greater than 0
(that is, there are more pumpkins to be weighed), do steps 5a to 5d. CurrentWeight
slot to
this weight. CurrentWeight
slot is
greater than the number in the HighestWeight
slot, copy
the number in the CurrentWeight
slot to the HighestWeight
slot. HighestWeight
slot is the
weight of the winner. Figure pumpkin is the translation
of our little problem into Java. Susan had a question about the
formatting of the output statement System.out.println("Highest
weight " + HighestWeight);
.
Susan: Why do we need both "Highest weight" andHighestWeight
in this line?Steve: Because "Highest weight" is displayed on the screen to tell the user that the following number is supposed to represent the highest weight seen so far. On the other hand,
HighestWeight
is the name of the variable that holds that information, so includingHighestWeight
in the output statement will result in displaying the highest weight we've seen so far on the screen. Of course, the same analysis applies to the next line, which displays the label "Current weight" and the value of the variableCurrentWeight
.
English Java
-------------------------------------------------------------------------------------------------------------------
First, we have to tell the compiler what we're up to in this program.
-------------------------------------------------------------------------------------------------------------------
Tell the compiler |
where to find the |
RWVar input and |
output code | import WAJ.*;
|
We're defining | public class Pump1
aclass
named Pump1 |
|
This is the main | {
part of the program | public static void main( String args[ ] )
|
Start of program | {
|
Define variables | int CurrentWeight;
| int HighestWeight;
|
-------------------------------------------------------------------------------------------------------------------
Here's the start of the "working" code:
-------------------------------------------------------------------------------------------------------------------
|
Ask for the first |
weight | System.out.print("Please enter the first weight: ");
|
Set the number in |
the CurrentWeight |
slot to the value |
entered by the user | CurrentWeight = RWVar.readInt(System.in);
|
Copy the number in |
the CurrentWeight |
slot to the |
HighestWeight slot | HighestWeight = CurrentWeight;
|
Display the current | System.out.println("Current weight " + CurrentWeight);
and highest weights | System.out.println("Highest weight " + HighestWeight);
|
class
| }
Susan had some questions about variable names.
Susan: Tell me again what the differentint
s mean in this figure. I am confused; I just thought anint
held a variable likei
. What is going on when you declareHighestWeight
anint
? So do the "words"HighestWeight
work in the same way asi
?Steve: An
int
is a variable. The name of anint
is made up of one or more characters; the first character must be a letter or an underscore (_
), whereas any character after the first must be either a letter, an underscore, a dollar sign, or a digit from 0 to 9. To define anint
, you write a line that gives the name of the
int
. This is an example:int HighestWeight;
.Susan: OK, but then how does
i
take 4 bytes of memory and how doesHighestWeight
take up 4 bytes of memory? They look so different, how do you know thatHighestWeight
will fit into anint
?Steve: The length of the names that you give variables has nothing to do with the amount of storage that the variables take up. After the compiler gets through with your program, there aren't any variable names; each variable that you define in your source program is represented by the address of some area of storage. If the variable is an
int
, that area of storage is 4 bytes long; if it's achar
(or ashort
), the area of storage is 2 bytes long.
Susan: Then where do the names go? They don't go "into" the
int
?Steve: A variable name doesn't "go" anywhere; it tells the compiler to set aside an area of memory of a particular length that you will refer to by a given name. If you write
int xyz;
you're telling the compiler that you are going to use anint
(that is, 4 bytes of memory) calledxyz
.
Susan: If that is the case, then why bother defining the
int
at all?Steve: So that you (the programmer) can use a name that makes sense to you. If the compiler had to assign names itself, it wouldn't be very likely to give variables names that you would like!
The topic of the import statement
was the
cause of some discussion with Susan. Here's the play by play:
Susan: IsFinally, the closing curly brace,import
a command?Steve: Right; it's a command to the compiler.
Susan: Then what are the words we have been using for the most part called? Are those just called code or just statements? Can you make a list of commands to review?
Steve: The words that are defined in the language, such as
if
,while
,for
, and the like, are called keywords. User-defined names such as variable names are called identifiers.Susan: So
import WAJ.*
is a code to tell the compiler that it is using info from the WAJ library?Steve: Essentially correct; to be more precise, when we
import WAJ.*
, we're telling the compiler to look into theWAJ
directory for definitions that we're going to use.
Susan: Then that
WAJ
file contains the secondary code of byte codes to transformRWVar.readInt
andRWVar.readString
into something workable?Steve: Actually, it's in the
WAJ
directory, not theWAJ
file, but you're on the right track.Susan: So the
import
statement file directs the compiler to that section in the library where that byte code is stored? In other words, it is like telling the compiler to look in section XXX to find the byte code?Steve: Right.
}
, tells the compiler
that it can stop compiling the current block, which in this case is the
one called main
. Without this marker, the compiler would
tell us that we have a missing }
, which of course would
be true.
Susan decided a little later in our collaboration that she wanted to try to reproduce this program just by considering the English description, without looking at my solution. She didn't quite make it without peeking, but the results are illuminating nevertheless.
Susan: What I did was to cover your code with a sheet of paper and just tried to get the next line without looking, and then if I was totally stumped, I would look. Anyway, when I saw thatif
statement, then I knew what the next statement would be but I am still having problems with writing backwards. For example:
if (CurrentWeight > HighestWeight)
HighestWeight = CurrentWeight;
That is so confusing because we just want to say that if the current weight is higher than the highest weight, then the current weight will be the new highest weight, so I want to write
CurrentWeight = HighestWeight
. Anyway, when I really think about it, I know it makes sense to do it the right way; I'm just having a hard time thinking like that. Any suggestions on how to think backward?Steve: What that statement means is "set
HighestWeight
to the current value ofCurrentWeight
". The point here is that=
does not mean "is equal to"; it means "set the variable to the left of the=
to the value of the expression to the right of the=
".Now it's time for some review on what we've covered in this chapter.
Review
We started out by discussing the tremendous reliability of computers; whenever you hear "it's the computer's fault", the overwhelming likelihood is that the software is to blame rather than the hardware. Then we took a look at the fact that, although computers are calculating engines, many of the functions for which we use them don't have much to do with numeric calculations; for example, the most common use of computers is probably word processing, which doesn't use much in the way of addition or subtraction. Nevertheless, we started out our investigation of programming with numeric variables, which are easier to understand than non-numeric ones. To use variables, we need to write a Java program, which consists primarily of a list of operations to be performed by the computer, along with directions that influence how these operations are to be translated into byte codes.
That led us into a discussion of why and how our Java program is translated into byte codes by a compiler. We examined an example program that contained simple source code statements, including some that define variables and others that use those variables and constants to calculate results. We covered the symbols that are used to represent the operations of addition, subtraction, multiplication, division, and assignment, which are
+
,-
,*
,/
, and=
, respectively. Whereas the first four of these should be familiar to you, the last one is a programming notion rather than a mathematical one. This may be confusing because the operation of assignment is expressed by the=
sign, but is not the same as mathematical equality. For example, the statementx = 3;
does not mean "x is equal to 3", but rather "set the variablex
to the value3
". Then we spent some time pretending to be a compiler, to see how a simple Java program looks from that point of view, in order to improve our understanding of what the compiler does with our programs. This exercise involved keeping track of the locations of variables and instructions, and watching the effect of the instructions on the stack and variables. During this exploration of the machine, we got acquainted with the byte-code representation of instructions, which is the actual form of a program that the Java interpreter can understand. After a detailed examination of what the compiler does with our source code at compile time, we followed what would happen at run time (that is, if the sample program were actually executed by the Java interpreter).Then we began to look at two data types that can hold non-numeric data, namely, the
char
and theString
. Thechar
occupies to 2 bytes of storage, corresponding to one character of data. Examples of appropriate values for achar
variable include letters (a-z, A-Z), digits (0-9), and special characters (e.g., ! @ # $ %), as well as "nonprintable" characters such as the "space", which causes output to move to the next character position on the screen.One
char
isn't much information, so we often want to deal with groups of them as a single unit; an example would be a person's name. This is the province of theString
variable type: Variables of this type can handle an indefinitely long group ofchar
s.At the beginning of our sample program for
String
s andchar
s, we encountered the linepublic static void main(String args[ ])
, which indicates where we want to start executing our program. A Java program always starts execution at the place indicated by such a line.As we continued looking at the sample program for
String
s andchar
s, we saw how to assign literal values to both of these types, and noticed that two different types of quotes are used to mark off the literal values: the single quote ('), which is used in pairs to surround a literalchar
value consisting of exactly onechar
, such as'a'
; and the double quote ("), which is used in pairs to surround a literalString
value such as"This is a test"
.This led us to the discussion of the way in which the compiler regulates our access to variables by their type, which is defined at compile time. This is called the type system; Java uses this static type checking to help make Java programs more robust than programs written in languages that use dynamic type checking, where these errors are not detected until run time.
After a short discussion of some of the special characters that have a predefined meaning to the compiler, we took an initial glance at the mechanisms that allow us to get information into and out of the computer, known as I/O. We looked at the
println
function, which provides display on the screen when coupled with the built-in destination calledSystem.out
. Immediately afterwards, we encountered the input functionRWVar.inputString
and its partnerSystem.in
, which team up to give us input from the keyboard.Next, we went over some program organization concepts, including the
if
statement, which allows the program to choose between two alternatives; thewhile
statement, which causes another statement to be executed while some condition is true; and the block, which allows several statements to be grouped together into one logical statement. Blocks are commonly used to enable several statements to be controlled by anif
orwhile
statement.At last we were ready to write a simple program that does something resembling useful work, and we did just that. The starting point for this program, as with all programs, was to define exactly what the program should do; in this case, the task was to keep track of the pumpkin with the highest weight at a county fair. The next step was to define a solution to this problem in precise terms. Then we broke the solution down into steps small enough to be translated directly into Java. Of course, the next step after that was to do that translation. Finally, we went over the Java code, line by line, to see what each line of the program did.
Now that the review is out of the way, we're about ready to continue with some more Java in Chapter morebas.htm. First, though, let's step back a bit and see where we are right now.
Conclusion
We've come a long way from the beginning of this chapter. Starting from basic information on how the hardware works, we've made it through our first actual, runnable program. By now, you should have a much better idea whether you're going to enjoy programming (and this book). Assuming you aren't discouraged on either of these points, let's proceed to gather some more tools, so we can undertake a bigger project.
Answers to Exercises
- Susan's answer to this problem follows, after a short discussion about formatting the output of this program to make it look better. While we're on the topic of formatting, the reason that this program uses two lines to produce the sentence "Please type in the number of guests of your dinner party." is so that the program listing will fit on the page properly. If you prefer, you can combine those into one line that says
System.out.print("Please type in the number of guests of your dinner party. ");
. Of course, this also applies to the next exercise.- Here's that conversation about formatting the output of this program to make it look better:
Steve: By the way, you might want to add a" "
in front of theis
inis ready
, so that the number doesn't run up against theis
. That would make the line look like this:System.out.println("A table for " + n + " is ready. ");
Susan: Okay.
And Figure first.dinner, as promised, is Susan's answer to the first dinner party exercise.
code/basic05/basic0~1.javFirst dinner party program (code\Basic05\Basic05.java) (Figure first.dinner)
Steve: Congratulations on getting your program to work!Figure else.if is an example of an
else
whose controlled block is anif
statement.
if (x < y)
{
System.out.println("x is less than y");
else
{
if (x > y)
System.out.println("x is greater than y");
else
System.out.println("x must be equal to y!");
}
}
else if example (Figure else.if)
As promised, Figure second.dinner is Susan's answer to exercise 2.
code/basic06/basic0~1.javSecond dinner party program (code\Basic06\Basic06.java) (Figure second.dinner)
code/basic07/basic0~1.javName and age program (code\Basic07\Basic07.java) (Figure name.age)
System.out.print("What is your age? ");
age = RWVar.readInt(System.in);
code/basic08/basic0~1.javNovice program (code\Basic08\Basic08.java) (Figure novice)
Susan: Steve, look at this. It even runs!
System.out.println("Please answer with either \"yes\" or \"no\".");
code/basic09/basic0~1.javAllowance program (code\Basic09\Basic09.java) (Figure allowance)
code/basic10/basic1~1.javGrading program (code\Basic10\Basic10.java) (Figure basic10)
Footnotes
- Please note that capitalization counts in Java, so
IF
andWHILE
are not the same asif
andwhile
. You have to use the latter versions.- However, we haven't yet eliminated the possibility of hardware errors, as the floating-point flaw in early versions of the PentiumTM processor illustrates. In rare cases, the result of the divide instruction in those processors was accurate to only about 5 decimal places rather than the normal 16 to 17 decimal places.
- This was apparently against the plan administrator's principles.
- Oxford English Dictionary, first current definition (4).
- The compiler also does a lot of other work for us, which we'll get into later.
- The
//
marks the beginning of a comment, which is a note to you or another programmer; it is ignored by the compiler. For those of you with BASIC experience, this is just like REM (the "remark" keyword in that language); anything after it on a line is ignored.- By the way, blank lines are ignored by the compiler; in fact, you can even run all the statements together on one line if you want to. That won't confuse the compiler. but it will make it much harder for someone reading your code later to understand what you're trying to do. Programs aren't written just for the compiler but also for other people; therefore, it is important to write them so that they can be understood by those other people. One very good reason for this is that more often than you might think, those "other people" turn out to be you, six months later.
- Other kinds of variables can hold different ranges of values; we'll go over them in some detail in future chapters.
- At the risk of boring experienced programmers, let me reiterate that = does not mean "is equal to"; it means "set the variable to the left of the
comparisonfig.=
to the value of the expression to the right of the=
." In fact, there is no equivalent in Java to the mathematical notion of equality. We have only the assignment operator=
and the comparison operator==
, which we will encounter later in this chapter. The latter is used inif
statements to determine whether two expressions have the same value. All of the valid comparison operators are listed in Figure- If you have any programming experience whatever, you may think that I'm spending too much effort on this very simple point. I can report from personal experience that it's not necessarily easy for a complete novice to grasp. Furthermore, without a solid understanding of the difference between an algebraic equality and an assignment statement, that novice will be unable to understand how to write a program.
- As I've mentioned previously, blank lines are ignored by the compiler; you can put them in freely to improve readability.
- Please note that the address that the stack occupies in the following diagrams is arbitrary. The actual address where the stack is located in your program is determined by the interpreter in combination with the operating system.
- The next byte code to be executed will be bold.
- In case you were wondering, the most common pronunciation of
char
has an a like the a in "married", while the ch sounds like "k".- As we will see shortly, not all characters have visible representations; some of these "nonprintable" characters are useful in controlling how our printed or displayed information looks.
- The Unicode standard is actually a "small" version of a standard that uses 32 bits per character, for the day when Unicode doesn't have sufficient capacity; that should take care of any languages that alien civilizations might introduce to our planet.
- Please note that there is a space (blank) character at the end of that
String
literal, after the exclamation point (!). That space is part of the literal value.- I know I left out the part about its curing cancer, but I don't want to go overboard.
- I'll be more specific later, when we have seen some examples.
- For example, if you wanted to insert a
"
in aString
, you would have to use\"
, because just a plain"
would indicate the end of theString
. That is, if you were to set aString
to the literal"This is a \"String\"."
, it would display as:This is a "String".
- Please note that you cannot include the "," in a number in your programs, whether you're writing the program or entering data when it runs. You have to type "10,000" as "10000" or you'll get an error either at compile time or when the program is running.
- You may be wondering why we need parentheses around the expression
Guess != Secret
. The conditional expression has to be in parentheses so that the compiler can tell where it ends and the statement to be controlled by thewhile
begins.- And maybe pigs can fly.
- If you look at someone else's Java program, you're likely to see a different style for lining up the
{}
to indicate where a block begins and ends. As you'll notice, my style puts the{
and}
on separate lines rather than running them together with the code they enclose, to make them stand out, and indents them further than the conditional statement. I find this the clearest, but this is a matter where there is no consensus. The compiler doesn't care how you indent your code or whether you do so at all; it's a stylistic issue.