| |||||
Variables: Named Compartments For Storing Values | |||||
In the last lecture we described a series of very simple programs to display text and the results of computations. For some uses these facilities may be adequate, but not for many. After all, you could get the results much more quickly on a calculator than by writing, compiling, and running a Java application. The computer supplanted the calculator because it was better at very large calculations. Of course, large calculations are hard to write down as a single expression. For almost any application it is helpful to be able to store input values, intermediate results, and output values so that they may be referred to and updated in the course of a computation. The computer, as we know, includes memory in which values can be stored. From the machine's point of view this memory is just a long list of storage positions which are accessed by specifying the number, or address of the desired cell. In higher-level languages, a variable is just a way to refer to a memory cell (or, often, a group of memory cells) by a meaningful name rather than by a numerical address.
| |||||
| Declaring Variables |
As we have said earlier, a program is a lot like a recipe. Now what is the first
thing that you see in a recipe? The ingredient list. Putting the ingredients
at the front allows you to skim the recipe to see roughly what it's going
to be like, and also keeps you from discovering half way through the preparation
that you are missing some crucial component. Like a recipe, in most modern programming languages you begin a given block of a program with the variable declarations, a list of all the variables that will be used in that block. This serves two purposes, similar to those of an ingredients list. First, the reader can often get a rough sense of how a program works just by looking at the list of its variable declarations. Second, the compiler uses the declarations to determine how much storage it will need to ask the operating system for when it runs your program. In order for the system to know how much space to set aside for a given variable, and how it should interpret the bit patterns in the space set aside, it needs to know what kind of value the variable is intended to refer to. Therefore, a variable declaration in Java specifies the type name and then the variable name, as in: which declares a variable named foo which will be used to store an integer.
| ||||
| Grouping Declarations |
The beginning of a block of code can have as many variable declarations as
you want,
and they need not occur in any particular order. If you are declaring more
than one variable of a given type you may put them together in a single
declaration statement by giving the type name followed by a list of variable
names separated by commas, as in:
Should you declare your variables individually or in groups? It's mostly a matter of taste. The style we often use is to group variables into a single declaration if they are somehow logically connected by their meaning in the program, i.e.:
| ||||
The Type of a Variable | |||||
|
As we said above, the compiler needs to know what kind of values a variable will
store so that it
can determine how many bytes of memory it will need to ask the operating system to
set aside for the variable. For example,
an integer takes up four bytes of memory, while a floating point number
takes six (since information must be stored for both the mantissa and the
exponent).
| |||||
| The Integer Types: byte, short, int, and long |
Actually, it's not quite accurate to say that integer variables
take four bytes of storage because, in fact, Java supports four
different types of integer variables.
| ||||
Integer variables can be declared as either int, as above, or
as byte, or short, or long.
As you might expect, the difference
between the four types is how much storage they use and, therefore, the
range of values they can store. A byte variable, naturally, uses
one byte of memory and stores values in the range -128 to 127;
a short uses two bytes of memory (16 bits) and can represent
values in the range -32,768 to 32,767;
an int uses four bytes of memory (32 bits) and can represent
values in the range -2,147,483,648 to 2,147,483,647; and
a long uses eight bytes of memory (64 bits) and can represent a
value in the range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
When you type an integer literal
unadorned, it will be stored and treated
as an
It might seem best just to use the
| |||||
| The Floating Point Types: float and double |
As with the integer types, there are actually two different types
of floating point variables: float and double.
In this case the types differ both in their range and their precision.
| ||||
A variable of type float is called single precision
and uses four bytes (32 bits) of memory.
Of that storage, the first bit determines whether the number is positive or negative,
eight bits are used for the exponent
(which is stored as a power of two, rather than as a power of ten),
and the remaining bits are used to store the mantissa,
This makes a float generally accurate to about seven digits.
The smallest representable magnitude (i.e. positive or negative value)
is 1.40129846432481707e-45, the largest is 3.40282346638528860e+38.
A
While
| |||||
You may be wondering why we said that float values are
accurate to about seven digits and then gave the minimum and maximum
magnitudes to 18 digits. This has to do with the way in which
decimal floating point values are encoded into binary floating point
numbers. While the number of binary digits used for the mantissa
is fixed (23 in the case of a float),
the number of decimal digits represented can vary over a large range.
Consider the fraction 1/256. In decimal this value is 0.00390625
and requires six digits of precision. But in binary it is
0.00000001 and takes only a single digit of precision.
The upshot is that some values can be represented to great accuracy, but
on average you can only rely on about 7 decimal digits. In addition to the various numeric types, there are variable types for holding a variety of other kinds of data.
| |||||
| The Character Type |
The character type, char, is used to hold a single Unicode
character. It is mostly used to hold user responses
to prompts and the like, and when processing text data character
by character. Character literals are much like string literals
except that they are written with single quotes and, naturally, can
include only a single character. That character can be written directly,
as an escape sequence, as an ASCII code, or as a Unicode code. The following
statement declares three character variables all initialized to hold the
exclamation point:
As with string literals, there is a problem writing quotes as character literals.
The double quote is no problem. It is just
| ||||
| The String Type |
Variables of type String (note the capitalization)
are used more commonly than characters.
They can hold names, addresses, or anything that is not appropriate for
storing in a number. Even things you might ordinarily think of as being
numeric values, like phone numbers, social security numbers and the like,
may best be stored in strings as you will have more control over their
formatting. So, for example, we might declare:
| ||||
To be totally precise, String is not a type, it is a class.
But we'll ignore that subtlety for now since it makes little difference
in how it is used.
| |||||
| The Boolean Type |
The boolean type is very simple, it has only two values:
true and false. Boolean values most commonly occur in
Java programs because they are the values computed by the relational
operators and are expected by the conditional statements (both of which will be
explained in the
next lecture).
You might use a variable of type boolean as a flag
to control the behavior of your program, doing one thing if the variable
holds the value true and another if it holds the value false. Such
a variable might be declared as:
| ||||
|
Note that the values of the boolean type are not strings holding the words true and false.
The literal values are written without quotes.
| |||||
Assigning A Value To A Variable | |||||
|
Once a variable has been declared you can use its name anywhere you could
use a literal of the same type: in arithmetic expressions, as an argument
to a method, etc. In each case the variable stands for whatever value is
stored in it. Therefore, they are not much use until you have stored
a value in them.
The Java assignment operator is the equals sign, For example, suppose we have the variable declaration: Then the effect of the statement: is to store the integer value 2 into the variable y. If
this is followed by the statement:
the expression on the right is just the variable y which
evaluates to 2 (since that is what we just stored in it) and that value is
stored into x. To evaluate the statement:
the computer looks up the value of x, multiplies that value
by 2 and adds 3. The result, 7, is stored into z.As a further example of using variables, the following program performs the same calculation of areas as the program in the last lecture, but using variables instead of literals:
The nice thing about this program is that now if we want to calculate the areas for
a different size square and circle, all we need to change is the value assigned
to the variable
| |||||
| Assignment Is Not Equality | It is important to remember that, even though it may look like one, an assignment statement is not an algebraic equation. As we said before, it is executed by evaluating the expression on the right hand side using whatever the current values of the variables in the expression are, and then assigning the resulting value into the variable named on the left. | ||||
So, even though it would be meaningless as an algebraic equation,
the following is a perfectly reasonable assignment statement:
If x is an int with the value 3 before this statement,
then the expression on the right evaluates to 4 which is then assigned back into
x.
| |||||
| Initializing A Variable |
You must always be careful that a variable contains a reasonable value
by the time it is used in an expression. For example, in the last program
the variable squareSide was assigned the value 3 before the variable was used to
compute the areas. In this case we assigned it a fixed value that will be
used every time the program is run. Another option, once we have covered input techniques
below, is to assign an initial value based on something entered by the user.
If you attempt to use the value of a variable before a value has been assigned,
the compiler will generally raise an error.
It will show the offending line and point at the unitialized variable.
For example, if you had the following lines starting at line 10 of the file Then the compiler would generate an error that looked something like:
In many circumstances you can combine the declaration of the variable and assigning its initial value into one step. This can save a bit of typing and make things clearer, as in: We already used this technique in a couple of the examples of different types of variables above. It probably seemed perfectly natural, and you didn't think to question what it meant. The expression used in the assignment is called the initializer, and can be any expression as long as it only uses literals and the variables already declared. Thus we could use the following declaration after, but not before, the last one: Initializations can be used in grouped declarations as well. The following statement declares two integer variables initialized to the values 3 and 4 respectively:
| ||||
Being Careful About Type Ranges | |||||
As we said earlier, the type of a variable determines how much memory it takes up, and, hence,
the range of possible values it can hold.
You need to be conscious of the limited range of each type, and be
careful not to attempt to assign a value to a variable that would
exceed the variable's type's range. The Java compiler will catch
certain obvious mistakes. For example, if you have a variable
foo of type int (which you'll recall accepts
values from -2,147,483,648 to 2,147,483,647) and then include the statement:
then when the compiler reaches that line it will generate a Numeric Overflow
error.On the other hand, if you use the statements: which have the same effect, the compiler will happily accept them. What happens when you run the program? Since it would be too costly (in terms of time) for Java to check every calculation for underflow and overflow conditions, the addition is computed without complaint. However, the result is unexpected: the variable foo is left with the value -2,147,483,648. Generally, when
a value goes too far in one direction to be represented properly in
a given type, it wraps around to the other end of the type's range.Note that some languages (notably, ML, which is studied in CS 131) are entirely type safe and will stop your program with a runtime error whenever you violate a type's range.
| |||||
Type Casting | |||||
|
When an actor plays the same kind of role a few times in a row, he may
find himself the victim of type casting, unable to get any other
sort of role. This seems to be happening to
Ron Silver, a brilliant
character actor (I recommend
Enemies: a Love Story
in particular). Lately
he has been cast as the villain in a series of science fiction movies
(Lifepod,
Time Cop, and
The Arrival
come to mind). If some other kinds of roles don't
come along soon, game shows may be the next step. Of course this has absolutely nothing to do with Java! In Java, type casting refers to the act of replacing a value of one type in an expression with an equivalent value of another type.
| |||||
| Automatic Type Casting |
We have already talked last week about type casting in the context of
integer values being promoted to floating point values if the other
argument to a binary operator is a floating point value. Promoting a
value to a type with a broader range is always safe (since no information
is lost) and there are several contexts in which Java will do it automatically.
Automatic promotion follows these rules:
| ||||
We have already relied on the last type casting rule in several circumstances
where we have used it to simplify the structure of output statements.
As we have said, when you write, for example:
as an abbreviation for: This is not a special version of System.out.println. That method is just printing
the single value it is sent. But in the first case, that single value is the result of the addition:
which, using the type casting rule, involves first converting the number 33 to the string
representation of that number, "33", and then appending that string to the string
"This is the number thirty-three: " to get:
| |||||
All of these promotion rules also apply when a value of the "lower" type is simply
assigned into a variable of the "higher" type. For example, if a numeric value
is assigned to a string variable, it is the string representation
of the numeric value that is actually assigned.
| |||||
| Manual Type Casting |
The rules listed above are the only circumstances in which Java will
automatically cast a value of one type to another type. Sometimes it may
be useful or necessary to cast a value downwards. If that is what is desired,
it must be signaled explicitly by the programmer, since it may be an unsafe
operation that will result in a loss of information. Other times, an upward
cast is desired in a situation where it will not be done automatically
by the system. To signal an explicit cast request, you simply put the name of the type you wish to cast to in parentheses immediately before the expression whose value you want to cast. Here are a few typical examples:
| ||||
|
Note that some books (and even these notes) may get sloppy and refer to casting as
a process of converting a value from one type to another, but this is not accurate.
The original value is unchanged. Rather, a new value
in the new type is put in its place in the overall expression.
In particular, if it is a variable that is being cast, it is the variable's value as used in the expression
that is really cast; the variable retains its original type and value.
| |||||
A Word About Variable Names | |||||
When you pick a name for a variable, that name should be as
helpful and descriptive as possible. While it may seem that long names
are cumbersome to type, in the long run they will save
you time and effort. Good variable names will often obviate
the need to add a lot of comments to a program. Deep in the heart of a
lengthy method, the assignment statement:
is likely not to mean much to the reader. A comment would help: But it would be much clearer if the statement just read:
As with program and class names, a variable name is subject to the rules
for Java identifiers, so it can be any string
of alphabetic characters, digits (other than as the first character), or the
characters
| |||||
More Arithmetic Operators | |||||
|
Last lecture we introduced the basic arithmetic operators. Now we will cover
a few more.
| |||||
| Operator Assignment Shorthand |
Changing the value of a variable so that it is some amount more or less than its
current value turns out to be a very common operation. For that reason,
Java includes a special version of the assignment operator that
simplifies typing and executing such operations. For any variable var
and expression exp, the statement
is equivalent to the statement There are variations of this shorthand for each of the arithmetic operators: +=, -=, *=, /=, and %=.
| ||||
| Increment and Decrement Operators |
C programmers (all these operators were born in C) hate to type.
And the operation of replacing a variable's value with one more or less is
so common (you'll see why in the section on loops
a couple of weeks from now)
that they found even the += method to much for them. Therefore, there is an
even shorter shorthand for these operations: the increment operator
and the decrement operator.
For any variable var, the statement
is equivalent to the statement Similarly, is equivalent to the statement
The increment and decrement operators can actually be put either before
or after the variable name. If they come
after, as above, they are called post-increment and post-decrement.
If they come before, as in
| ||||
|
As statements, there is no difference between the pre- and post- uses of the
operators. But the operators can also be used as part of a larger expression,
in which case there is a big difference. If the pre-increment operator is applied
to a variable as part of a larger expression, then the variable is incremented
and it is the incremented value that is used in the larger expression. If, on
the other hand, the post-increment operator is used, then the variable is
incremented, but it is the value from before the incrementing that is used in
the larger expression. So, pre-increment means "increment me and then take my
value", whereas post-increment means "take my value and then increment me."
The same rules apply to pre- and post-decrement. To make this more concrete, assume we have the declaration: and consider the behavior of the following statements: The variable foo obviously starts with the value 1. The assignment to
bar requires evaluating the expression ++foo. This increments
foo to now hold the value 2, and that is the value of the expression,
which is now assigned to bar. To make the assignment to baz,
the expression foo++ must be evaluated. This increments the value
held by foo to 3, but the value of the overall expression is 2, the
value of foo from before it was incremented. Thus the final values of
foo, bar, and baz are 3, 2, and 2, respectively.
| |||||
Be careful not to get confused and merge the two techniques now available for incrementing
a variable:
and and end up writing something like: What is the result of executing that statement? Suppose foo starts out with
the value 3. In order to determine the value to assign to the variable, we first evaluate the
right hand side of the assignment. Evaluating foo++
increments the value of foo to 4. But as an expression its value is the value of
foo before it was incremented. This value, 3, is then assigned into
foo. So the net effect is as though no statement had been evaluated.
The statement:
has a similar, though less problematic issue. Can you figure out what happens when it executes?
| |||||
Note that the increment and decrement operators are the only operators that
do not cause a byte or short variable's value to be cast to
an int. That is, if a is a byte
variable, then while
will not compile (you must cast the sum back to a byte first),
compiles fine.
| |||||
| Bitwise Logical Operators |
Java includes a large set of operators designed to allow you to examine and
manipulate the contents of integer variables on a bit-by-bit basis, ignoring
the higher-level types they represent. As we will not have any use for such
manipulations in this course, we will not cover them here. If you are interested
in the details, they are covered in most Java reference books.
| ||||
The Math Package | |||||
In addition to the simple arithmetic operators,
Java provides a large set of math functions in the package
java.lang.Math. Like the java.lang.System package,
Math is available automatically, without any special effort on the
programmer's part. The Math package provides the following methods
(which we have grouped together into families of related methods):
| |||||
| Trig Functions |
These methods each take a double value representing an angle
size (in radians) as an argument. They each evaluate to a double
value containing the appropriate result.
These all behave just as you'd expect. So, for example, will assign the value -1.0 to the variable x.
| ||||
| Inverse Trig Functions |
The first three of these methods each takes a double value and returns,
as a double, the size of the corresponding angle in radians.
| ||||
|
While it is generally true that these functions are inverses of the ordinary trig functions, applying a trig function and then applying its inverse to the result will not always yield the original value back due to the potential for possible round-off errors due to the fixed number of bits used to store floating point values.
| |||||
| Exponential Functions |
| ||||
| Inverse Exponential Functions |
| ||||
| Rounding Functions |
These methods all perform some variation of rounding a floating
point value to an integer. However, as you will see, by "integer" we mean in the mathematical
sense, not necessarily the type sense.
| ||||
| Other Functions |
| ||||
| Math Constants |
In addition to all of these functions, the Math package also defines
two useful constants. These are both double variables. However,
they are defined in such a way that you cannot assign new values to them.
| ||||
Formatted Output Using HMCSupport.out | |||||
Because Java was designed mostly with applet construction in mind, Java's designers
did not bother to make the
| |||||
Note that because of the way in which it is implemented, you shoud not mix the
use of System.out and HMCSupport.out in a given program.
The results may be unpredictable.
| |||||
| Importing the Package |
As mentioned earlier, the System package is a sub-package of the package java.lang.
However, all of the sub-packages of the package java.lang are loaded automatically
during compilation. Otherwise, it would be necessary to refer to System.out
by its full name, java.lang.System.out each time you wanted to send that object
a message. Since HMCSupport is a sub-package of HMC, to
use its println method you need to refer to it as HMC.HMCSupport.out.println.
This quickly becomes tiresome. The solution is to explicitly import
the sub-package we want at the beginning of the compilation. This is done by adding
the command:
below the introductory comments and above the rest of the program.
You can add as many at the beginning of every Java program.
| ||||
| Primitive Output |
HMCSupport.out provides the same basic output routines as System.out.
If none of the special methods described below are used, then the behavior of the
print, and println methods is essentially the same as those of
System.out. However, several additional methods are provided that
allow you to control the formatting of output in several ways.
| ||||
| Columnar Output |
It is often useful to be able to specify that a bit of output take up a set
number of spaces on the line, no matter how wide the value being printed
actually is. Consider, for example, the following program, which prints
the multiplication table for the numbers 1, 2, 4, and 6:
The output from this program is: which is not very attractive. To get the columns to line up using the standard output routines we would need to manually add extra spaces as needed, which is cumbersome, and awkward. However, HMCSupport.out
has a method HMCSupport.out.setWidth which allows
you to set the number of spaces the output from each subsequent call to print
should use.
If a value being printed is narrower than the previously specified field width,
then the value will
have enough spaces added before it to make the whole output the correct width.
If the value being printed is wider than the specified field width, then the
field width is ignored and the whole value is printed. Calling this method
affects all further output with HMCSupport.out until it is
invoked again with a new field width. To turn off this control, just call
setWidth with zero as an argument (since all output will be at least
zero characters wide).We can use this method to fix the multiplication table. We will just specify that all the values be printed in fields three characters wide. This will make the columns line up, and will also do away with the need to include spaces explicitely in the output messages.
The output from the new program is: Which is a considerable improvement.
| ||||
| Text Justification |
By default, when you specify a field width using
| ||||
| Pad Characters |
When the output is padded, the default padding character is the space. This can
be changed with a call to HMCSupport.out.setPadChar with a single
character argument. For example, the following program prints the first and
twelfth entries in the table of contents for a non-existent book:
It's output is the two lines:
Make sure you read the program carefully to understand how each line effects the output generated.
| ||||
| Controlling Numeric Output |
Another limitation of System.out is that you cannot control the
number of digits of precision used when printing floating point values.
(As mentioned earlier, this is compunded by the bug in early versions of Java that
caused System.out to print double values as though they were
float values.)
When using
The last additional output control method provided by The following program demonstrates the behavior of these commands:
It's output is:
| ||||
Note that both HMCSupport.out.setFixed and HMCSupport.out.setPrecision
will only effect the formatting of numbers that are sent as the only parameter to a call
to HMCSupport.out.print or HMCSupport.out.println. That is, if we have a
double variable x then in the output from the statement:
these controls will effect the formatting of the value of x printed, but in the
statement:
they will not have any effect. The reason is that in the latter case the value of x
will be converted to an equivalent string and appended to the label text before
the println method ever gets a hold of it. By that time it is too late
for the format of the number to be modified as it is buried inside the larger string.
For the same reason, the rounding error discussed in the last lecture will occur
in the latter example, even though HMCSupport.out is being used
in favor of System.out
| |||||
| Output Is Buffered |
One thing that takes a little while for beginning programmers (in any language) to get used to
is that in modern text-oriented systems the programs that you write don't generally have direct access to
the input and output hardware of the computer. Instead, when you send a message to
System.out to display something on the screen, it in turn makes a request
of the operating system to do the job for it. In most cases, the operating system
doesn't comply immediately, but instead takes the characters that are supposed to be
displayed and puts them in an area of memory called a buffer that has been
set aside for that purpose. At some later time of the system's choosing it will
flush the buffer and display anything in it on the screen.Sometimes it is important to insure that the buffer has been flushed at a certain point. Suppose you are having trouble with a program that keeps crashing, and are trying to figure out at which point the failure is occuring. The quick-and-dirty solution is to add a few debugging output statements to the program that just display an indication that the program got that far. Well, if the program crashes but the buffer has not been flushed, you can't tell how far it got before crashing. Maybe it didn't get as far as a given debugging statement, or maybe it did but the output was still stuck in the buffer when the program crashed.
Most languages therefore provide a command that either forces the operating system
to flush the output buffers immediately, or forces the program to wait until they have
been flushed. In the core Java output routines the method is
| ||||
User Input Using HMCSupport.in | |||||
| |||||
| User Input Makes Programs Interesting |
It is hard to imagine an interesting program that doesn't interact with
the user in some way. After all, unless it is based on the use of random
values generated at run time, it will always do exactly the same thing each
time it is run.
Unfortunately, while the output support in System.out is weak, the
support for getting input from the user provided by System.in
is almost non-existent.
| ||||
Note that, as with the output objects, you should not mix the
use of System.in and HMCSupport.in in a given program.
The results may be unpredictable.
| |||||
| Input Is Buffered |
It is important to understand that, similar to the situation with output buffering,
in general your program is not in direct contact with the keyboard. As the user types,
the operating system copies their input into a buffer in memory. When you use the
input methods to read tokens, they are taken from that buffer. This allows the
user to type ahead of the program's requests. Also, because the user's input is not generally put into the input buffer until they hit the return key, it is possible for the user to back up within
a line and fix errors without the program having to do anything special to accomodate that
task. While there are ways to establish a direct connection with the keyboard, the programming involved makes these techniques beyond the scope of this course..
| ||||
| Basic Input Methods |
There are five basic input methods provided by HMCSupport.in (all of which are methods of no arguments).Each begins its work in the same way. Starting at the position that the input pointer (which keeps track of how much of the buffer has already been processed by previous input commands) was left in the input buffer after the last input, the
input routine first begins
by looking at the character at the pointer to see if it is a whitespace character. If it is, the routine consumes it (i.e., advances the input pointer) and repeats the process consuming all the whitespace characters until it hits a non-whitespace character. It then reads the next token
of input. That is, all the characters up to the next whitespace character.
The behavior of each routine varies at that point:
The following program once again computes the areas of a square and a circle. This
time it uses
| ||||
| Detecting Bad Input |
How can you tell if the user typed a legitimate input value in response to some prompt?
Testing to see if the answer is not zero will only work if zero is not a valid response,
otherwise you can't tell if the user typed zero, or if they typed an invalid response
that caused the input method to return zero. Therefore, HMCSupport.in also provides
a method that enables you to test if the last input attempt succeeded or not.
If you call the method
| ||||
The CLASSPATH Environment Variable | |||||
If you try to use the objects in HMCSupport and get the error
message Undefined variable: HMCSupport
from the compiler, it means that the Java
compiler was unable to find the necessary classes. The Java compiler
looks for classes in a variety of directories. The first place it looks
is in a special subdirectory of the directory in which the Java compiler
resides. After that, the list of places it looks is determined by the
value of the CLASSPATH variable in your Unix
environment.
When you first
set up your account to use Java,
the setup
program should have configured your account so that your
You can confirm that your at the Unix command line. If you get no response, or the wrong response, use emacs add the line: to the end of the file .tcshrc in your home directory.
| |||||
Last modified August 28 for Fall 99 cs5 by fleck@cs.hmc.edu
This page copyright ©1998 by Joshua S. Hodas. It was built with Frontier on a Macintosh . Last rebuilt on Wed, Sep 16, 1998 at 10:32:17 PM. | |
http://www.cs.hmc.edu/~hodas/courses/cs5/week_03/lecture/lecture.html | |