Introduction to Programming in Java

version 1.0
© 2003 Bernard Schutz

The computer programs for Gravity from the ground up are written in Java, a versatile and powerful computer language that is available for most computer platforms free of charge. If you have never written a computer program, or if you want to learn how Java differs from the language you are used to, start with this tutorial.


Contents


Java and your computer

There are dozens of programming languages in use today, but most are similar in their basic features. Therefore, studying one of them, like Java, opens the door to most of the others.What is more, Java is free and comes with many pre-programmed tools, so once you learn it you have a powerful programming system at your fingertips.

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!


Data types, and defining variables and arrays

Java distinguishes between several kinds of data: we will show how to use integers, floating-point numbers, and logical (boolean) values, for example. The differences between them relate to what values they are allowed to represent. As the name implies, an integer can have only values drawn from the set (..., -2, -1, 0, 1, 2, ...). Since integers in Java are stored using two bytes, or 16 bits, there are 216 different possible values. Given that there are positive and negative values, the largest absolute value is 215 = 32768. Floating-point numbers are numbers that, in standard arithmetic, would be written like 2.168 x 10-19 ; in Java this would be written as 2.168e-19. The ones that Java uses by default are called doubles, and use 8 bytes for each number. This allows 15 bits to be used for the exponent alone, so numbers can be represented between 10-16382 and 10+16383.  Of course, the mantissa can be represented only as accurately as the remaining 64-15 = 49 bits allow. (The word double is short-hand for "double-precision floating point number". Java and most other languages also offer floats, which use only 4 bytes of storage and are therefore single-precision numbers, but in Java -- unlike most other languages -- double-precision is the default.)

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.



 

Arithmetic and operators

In Java you write arithmetic expressions in fairly standard notation. The standard operators are "+" for addition, "-" for subtraction, "*" for multiplication, and "/" for division. You write in the usual algebraic way, and can group terms together with round brackets (..) to force the interior to be evaluated before the result is used. As in standard algebra, some operations have a higher priority and are executed first. Thus, the expression 5 + 8 * 4 requires that 8 and 4 be multiplied first and the result added to 5; the operator "*" has a higher precedence than "+". If you want 5 and 8 to be added before multiplying by 4 you have to write (5 + 8) * 4. There is no operator in Java for "raising to a power"; we will see how to do that later.

Java has many more operators that do specialized things. For our programs, here are the most important ones.

Since there are different kinds of arithmetic numbers, one should be a little sensitive to mixing them in arithmetic expressions. Thus, the expression 1.0 + 3e4 is an instruction to add two doubles, while 1 + 3e4 is an instruction to add an int to a double. Java will behave correctly in both cases, producing a double with the value 30001.0 in each case. But if you wrote 1 + 30000, then Java would produce an int with the value 30001. We saw in the list above how different the result 5/2 and 5.0/2 can be: the first gives 2, the second 2.5. In a mixed expression, division defaults to floating-point division, so that 5.0/2 and 5/2.0 both give 2.5.

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.


Assigning values to variables

To store a number into a variable, one uses an assignment statement. In Java and most other languages, the assignment is accomplished by an operator called "=", which does not mean the same as the symbol "=" would mean in an algebraic equation. Thus, the Java statement
                 weight = 15;
places the value 15 into the storage for the value of "weight". It acts right-to-left, in the sense that the right-hand-side of the equals sign is evaluated as an arithmetic expression, and then the result is assigned to the storage location of the variable named on the left-hand side.

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.


Mathematical functions

We will, of course, go beyond simple arithmetic in these programs and use some mathematical functions. In Java, the standard functions (such as one finds on a scientific calculator, like sine and cosine) are contained in a library called Math. Its name must be used as a prefix to the function name, separated by a dot. Thus
                 Math.sin( x )
is the way to get Java to evaluate the sine function on the argument x. In Java as in almost all other languages, the trignonometric functions expect their arguments to be given in radians, not degrees. Other functions include the cosine function Math.cos(), the exponential function Math.exp() (so that ex is Math.exp(x)), the tangent function Math.tan(), and the square root function Math.sqrt(). The value of the important constant p is part of this library, so that
                 Math.PI
is the value of p expressed as a double (i.e. to about 15 significant figures). The capitalization of the letters PI is important: there is no constant called Math.pi or Math.Pi. The program CannonTrajectory contains the two statements
                 double theta = angle * Math.PI / 180.0;
                 double u = speed * Math.cos( theta );
These declare and initialize the two variables. The first line converts an angle given in degrees to one given in radians.

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.


If statements

Normally Java executes the statements of a program in the order in which they appear in the program.But there are times when you would like a program to do different things depending on some numbers that were not known in advance, but were only calculated in the program. You might, for example, want to count how often a particular variable exceeds a certain value. You can test it using the > operator, but to control the execution of the program as a result of the test, you need the if statement. For example, to add 1 to a variable count if a variable x is greater than zero, write
                 if ( x > 0 ) count++;
The keyword if is followed by a boolean expression; if this expression evaluates to true, then the statement that follows it is executed. If not, Java simply goes to the next statement. The boolean condition must always be included in round brackets (..).

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.
 


Loops

A very important program construction for our programs is the loop, which allows us to execute a group of statements over and over again. We need loops in order to simulate the motion of projectiles and planets; the procedure to advance the position of the planet from one time to the next is always the same, so we don't want to write the statements out again and again for each step, particularly if the calculation may require thousands of steps! Instead, we write out the statements only once, and group them inside a loop. The loop has a special set of instructions that control how often it is executed.

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 (..).

The loop in our simple example will start with j = 1 and then execute repeatedly until j reaches 10. When j = 10, the loop will not execute. Therefore it will execute a total of 9 times.

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.