Introduction to Programming in Java
|
A Java program consists of a series of instructions called statements. Statements are built out of mathematical expressions and words that Java understands. Each statement ends in a semicolon, and statements can be grouped together within curly brackets {}. The computer will execute the statements one after another, unless the statements themselves change the order of execution.
You write a computer program using an ordinary text editor. Once it is finished, it has to be translated into a form that the computer can understand. This is the job of the compiler. This happens in different ways for different languages. The present document does not deal with compiling and running Java programs. The Triana programming environment is being supplied with the programs to take care of those details. See the document on Using Triana for Gravity from the ground up.
Here we deal with the way to write the programs. This will allow you to read and understand the programs that are supplied with Triana. It will also allow you to modify them or create totally new ones. This is not a complete introduction to the Java language; there are many books and tutorials on the web that are more complete than the present one. Here we just develop the foundations of the language to the extent that you will need it for the book Gravity from the ground up. We will use examples from some of the book's programs, especially the first one, CannonTrajectory, in order to illustrate the lessons here.
When reading these programs, you will see that they contain extensive comments to explain the reasons for the statements. Java permits the insertion of comments to improve the understandability of programs. There are two ways to put comments into Java. The first way is to follow a statement with // on the same line. The result is that Java will ignore everything else on that line, until the next line begins. The second, and more powerful, way to insert comments is to use a comment block. Java ignores any block of text that begins with /* and ends with */. If it encounters a /* on processing a program, it begins to ignore the subsequent characters until it encounters a */, which it takes to be the end of the comment block.
One advanced aspect of Java is that it is an object-oriented language. This is important for more sophisticated programmers, but it does not affect what we need to know for the computer programs in the book, so we do not address object-oriented issues here. Java was originally based on the computer language called C and it resembles it closely when dealing with simple expressions, as in this introduction. If you are familiar with C you will have little more to learn in order to understand our programs.
Java was developed by Sun
Microsystems, and its tools are still under
development. Although it is copyright, it is available free of charge
from
Sun, and there are even other
versions created by other
companies that obey the same rules. Any Java system should be able to
execute
Triana and the programs of this book. The download pages from the website for this book
contain the necessary links for you to obtain Java, but you are also
encouraged to go onto the web and look for other links: there is plenty
of Java information and resource around!
Computer calculations therefore do not in general arrive at exact results, but rather they should produce the floating-point number that is closest to the true result. This means every calculation is affected by round-off error, and the programmer needs to be sure that this error is tolerable. In our programs, this will never be an issue.
The third data type that we will use in our programs is the logical type, which has only two values: "true" or "false".In Java this is called a boolean data type. Booleans are used for logical tests, wherever in a program a decision has to be made that may depend on values of some other numbers. We shall encounter such decisions when, for example, we use loops, which are groups of statements that are repeated a number of times. They continue to be repeated until a certain boolean value evaluates to "false".
You can store data in the computer's memory by defining a variable,
which is a name that the computer associates with a storage location.
You
have to tell the computer what kind of data will be stored in the
location.
For example, to store an integer under the name "j",
you first have to tell Java about it by writing:
int j;
This is our first example of a statement. It uses the Java
keyword
"int" that tells Java that what follows
will
be the name of a variable that holds an integer. This statement is
called
a declaration. You could include a list of variables, separated
by commas:
int j, k, myInteger;
This list contains an example of a longer name, following a Java
convention
(not required by the language, just practiced by programmers) that
compound
variable names begin with lower case, and further words begin with an
upper-case
letter. This illustrates another Java rule, that names of variables are
case-sensitive. The variable names myinteger,
myInteger,
MyInteger,
myinTeger,
and MYINTEGER all could be used in the
same
program for different variables. (But don't do this unless you have a
good
reason to: it will make your program hard to read and understand!)
All variables must be declared before they are used.
Declaration
statements can appear anywhere in the program before their variables
are
first used. The declarations for doubles and booleans are similar to
those
for ints:
double xPosition;
boolean decider;
In many cases it is desirable to store several numbers of the same
type
together. This is done by using an array. An array is the
computer
equivalent of what in mathematics are called vectors and matrices. A
vector
consists of a sequence of components, all having the same name,
distinguished
from one another by a label. Thus, the velocity of a particle is given
by the vector v with components vx, vy,
and
vz. Alternatively, we could number the labels and call the
components
v1, v2, and v3. In Java, the
components
could be stored as three doubles with the same name, and with the index
held in square brackets: v[1], v[2],
and v[3]. This is called a
one-dimensional
array. To declare that a variable is a one-dimensional double array,
one
would use the statement:
double[] doubleArray;
This does not say how many components it has. We will learn how to
do that below.
An important feature of Java is that the numbering of indices for arrays starts at zero. The first three components of doubleArray are, therefore, doubleArray[0], doubleArray[1], and doubleArray[2].
Arrays can have more than one index. A two-dimensional array
is the computer analog of a matrix, which requires two indices.
In Java the indices are placed in separate brackets. Thus a
two-dimensional
array of integers could be declared as
int[][] population;
and its elements accessed as population[0][2]
or population[3][5], etc. Again, each
index
begins numbering at 0.
Java has many more operators that do specialized things. For our programs, here are the most important ones.
This is a good place also to comment on how to write statements in Java so that they are readable. In general, Java does not mind if you put in extra spaces or line-breaks in the middle of a statement. It looks for the ending semicolon to determine where the statement ends, and it ignores excess spaces of any kind. So you can write your statements to contain tabs, spaces, and line-returns if that helps make the program readable. Programmers typically make indents before the beginnings of statements in order to line them up.
This use of the = sign is quite
different
from its use in ordinary algebra. The statement a
= b
in an algebraic calculation means that the values in the variables a
and b are the same. The difference
between
this and the assignment statement is most vividly illustrated with the
perfectly common statement in Java:
j = j + 2;
which instructs Java to evaluate the right-hand-side using the current
value of j, and then assign the result
to
j.
The effect of this statement, which would be meaningless in arithmetic,
is simply to change the value of j by
2.
Notice that the expression j++ has the
same
effect on j as the expression j
= j + 1. This happens in many programs that repeatedly
update
the value of a variable. For example, in CannonTrajectory
the variable x holds the horizontal
position
of a cannonball at any particular time during its flight. To get the
value
at the next moment of time (an interval dt
later), the program uses the statement:
x = x + u*dt;
This multiplies the time-interval dt by the horizontal speed u
to get the distance increase u*dt;
then it
adds this to the current value of the horizontal position to get the
new
value, which is stored right away in x,
wiping
out the old value. After this statement, x
contains the new position.
Java, in common with C, has other assignment operators that cover
just
the situation we have described. For example, the assignment
j += 2;
has the same effect as j = j + 2. It
is used because it allows Java to instruct the computer to perform this
operation more efficiently. There are assignments for other operators:
-=
subtracts the right-hand-side from the variable named on the left and
then
assigns that to the variable; *=,
/=,
and %= perform analogous tasks.
It is possible in Java to make assignments in declaration
statements,
to initialize variable values. Thus, in CannonTrajectory
there is the statement
double g = 9.8;
to do two things at once: declare that g
is a double variable, and set its value to 9.8. (This constant holds
the
acceleration of gravity on the Earth.)
Now that we have assignment statements, we can also see how to tell
Java that an array should have a certain size. In CannonTrajectory
the array horizontalDistance is used
to hold
some of the position information of a trajectory. It needs to hold 1000
numbers, so the declaration is
double[] horizontalDistance = new double[1000];
This is our first encounter with the Java keyword "new".
It sets up storage in the computer's memory for the array we have
defined,
and it places default values of zero into the storage locations. The "[1000]"
tells Java to create one thousand locations. Note that these will be
accessible
by index values ranging from 0 to 999: the first element of the array
is
horizontalDistance[0]
and the last is horizontalDistance[999].
This allocation of storage must be done before the array is used in
arithmetic.
It is not necessary to combine the allocation with the declaration.
Thus
it would be possible in CannonTrajectory to declare the array at one
point,
and then later in the program allocate storage:
double[] horizontalDistance;
...
horizontalDistance = new double[1000];
Until the second statement is encountered, horizontalDistance
is just an abstract name to Java. After this statement, it is possible
to assign values to the array elements.
Multidimensional arrays have similar allocation statements. Thus, it
is possible to write
int[][] population;
population = new int[10][3];
This declares the name population to represent a two-dimensional array,
and then sets aside 30 storage locations (10 rows of 3 elements each,
equivalent
to a non-square matrix with 10 rows and three columns). These
statements
could be combined of course:
int[][] population = new int[10][3];
would do the same.
A name should be declared only once, i.e. the statement int[][]
population should occur only once. But allocation of
storage
can be done over and over again, each time wiping out what was
previously
allocated. Thus, later in the program one could write
population = new int[7][7];
and this would have the effect of losing all the data stored in the
previous 10x3 storage locations and creating a different set of 7x7
storage
locations for the array.
Raising to a power is done in Java by the mathematics library function Math.pow, which takes two arguments. Thus, we can compute (2.14)0.25 by evaluating Math.pow(2.14, 0.25). Note, however, that in Java as in most other languages it is often better (from the point of view of computer execution time) to avoid using Math.pow if there are simple alternatives. Thus, a computer will compute the square of x faster if you write x*x rather than use Math.pow(x,2). Even the cube may be faster as x*x*x. The programs in the book use constructions like this.
The arguments of Math.sin, Math.cos, Math.tan, Math.exp, Math.sqrt, and Math.pow should all be doubles. You are allowed to give integer values, however, and Java will convert them to doubles before doing the evaluation. They all return values that are doubles, not ints.
Other useful mathematics library functions are Math.abs
(the absolute value), Math.asin (the
arcsine
or inverse sine function, which returns its answer as an angle in
radians),
Math.acos
and Math.atan (inverse cosine and
inverse
tangent, respectively), Math.log (the
natural
logarithm), Math.toDegrees (converting
radians
to degrees), and Math.toRadians
(converting
degrees to radians). In CannonTrajectory
we could have replaced the declaration statement for theta given above
by
double theta = Math.toRadians( angle );
There are comparison functions, like Math.max(a,b) which takes two arguments (called a and b here) and returns the larger of the two. Similarly you can use Math.min to find the smaller of the two arguments. You can round a double to the nearest integer with Math.rint. You can find the nearest integer less than a double with Math.floor, and the nearest integer greater than a double with Math.ceil.
Two other functions needs special comment. The first is Math.atan2. This takes two arguments, say x and y. The value of Math.atan2(y,x) is the angle that a line drawn from the origin of a rectangular coordinate system to the point (x,y) would make with the x-axis. (Notice the order of its arguments.) It thus converts rectangular coordinates to polar, or at least it finds the polar coordinate angle associated with the points. The effect is almost the same as Math.atan(y/x), which also returns the angle made by this line, since the tangent of that angle is the ratio y/x. However, Math.atan cannot tell the difference between the first and third quadrants, since the ratio, say, -1/-1 is the same as +1/+1. Nor will Math.atan work well if y=0, since division by 0 is not allowed. But Math.atan2 keeps the x and y arguments separated, so it can tell which quadrant the answer is in, and it can handle the case y=0. We use Math.atan2 extensively in the program Orbit.
The final function we will need is Math.random.
This takes no argument at all, but it must still be written with empty
round brackets:
Math.random()
evaluates to a "random" number between 0 and 1. Each time it is called
it evaluates to a different number, so repeated calls to it can produce
a list of random numbers that can be used in programs that study
probability.
The numbers are only "random" (which means pseudo-random) because
nothing
in a computer is truly random; everything is determined by the program
and its environment. Java implements a method that will return numbers
that are uniformly distributed in the interval between 0 and 1 and
contain
with no significant correlations with one another.
Often you may have two alternative courses of action. You might like
to keep the number of positive values of x
in a variable countPositive and the
number
of negative or zero values in countNegative.
Then you can write
if ( x > 0 ) countPositive++;
else countNegative++;
The Java keyword "else" gives the
alternative
action that is taken only if the test returns false.
This construction will guarantee that something happens each time,
either
the first statement or the second. Note that, without an else
clause, the statement following if
will be
executed regardless of the outcome of the test.
In these examples, the individual statements following "if"
or "else" can be replaced by groups of
statements
enclosed in curly brackets. This allows quite complicated alternative
actions
to be chosen. For example one could write
if ( x
> 0 ) {
countPositive++;
positiveSum += x;
}
else {
countNegative++;
negativeSum += x;
}
where the variables positiveSum and negativeSum
are assumed to have been declared earlier and will be used to
accumulate
the sum of all the values of x when it
is
positive or negative, respectively. Notice in this expression that
there
is a line-break after the opening { and indents for the statements
until
the closing }. This helps readability, and does not affect the program
since Java ignores all these extra spaces.
In CannonTrajectory the heart of
the program
is the following set of statements:
for ( j = 1; (( h >= 0.0 ) && ( j < 1000 )); j++ ) {
x = x + u*dt;
w = v - g*dt;
h = h + (w + v)/2*dt;
verticalDistance[j] = h;
horizontalDistance[j] = x;
v = w;
}
(We have removed the comments from the code in order to show the
statements
only.) The loop is set up by the "for"
statement.
this is followed by a group of statements enclosed in curly brackets {..}.
These statements are the ones that will be executed repeatedly. All we
need to examine here is how the "for"
statement
controls the execution.
It will be easier to study this in an example with a simpler test
than
the one in CannonTrajectory:
for( j = 1; ( j < 10 ); j++ ) { ... }
This contains three parts, separated by semicolons within the round
brackets (..).
Writing the contents of the loop block of statements requires attention as well. In the example from CannonTrajectory above, x is changed by the first statement each time, and h is changed by the third. The second and 6th statements together result in a change in v each time, as explained in the help file for the program CannonTrajectory. The changes in h and x are saved in the arrays verticalDistance and horizontalDistance, each time using a different element of the array for the current value of the variable. In this way, the loop generates a sequence of numbers that are stored and are available in these arrays after the loop finishes. We won't go into the details of why we want the particular values generated here; this is discussed in CannonTrajectory.
Normally a loop finishes when the test condition returns false. But there are times when one wants the loop to finish because some calculation inside the loop has reached a desired result. This often happens if the loop is designed to improve the accuracy of some calculation, and there is a test inside the loop to determine if the accuracy is sufficient. To exit from the loop at any time you can use the Java statement "break". This transfers the execution to the first statement after the loop. There is an example of this in the program Orbit.
Other loop constructions are possible in Java, using keywords other
than "for". You can begin a loop with a
"while"
keyword, which is followed by a logical test; the loop repeats while
the
test evaluates to true. An example of
this
is found in Atmosphere
and related programs. Alternatively, you can use a "do-while"
construction in which the while
keyword and
its test appear at the end of the loop rather than the beginning.