PI
and a radius variable
r
helped, since we don't have to continually write out
the value of Pi.
However, we still had to keep writing the formula over and over.
For example, we have to say r * r
every time we want to
square the radius, and then we have to remember to multiply by Pi.
Let's first address the problem of squaring a number. Instead of
having to write the expression r * r
every time, it would
be nice to have a black box, or a procedure, that would take in
a numerical argument and return the square of that argument.
If we had this, we could write
square(837)instead of
837 * 837
.
This may not save us much typing
but one can argue that the code is more readable, and certainly if
we were taking cubes or higher powers, it would become ridiculous to
start stringing the numbers all out in a line.
In Java, we can create a procedure called square
as follows:
double square(double x) { return (x * x); }
The "double" at the beginning is the type of return value from the
procedure. This is followed by the name of the procedure, and then a
list of formal parameters separated by commas. In this case,
there is only one formal parameter, called x
and its type
is double
.
When the procedure is invoked, the actual values passed in from the procedure call are bound to the formal parameters. The part in curly braces ({}) is the procedure body. The procedure body contains some number of statements that are executed in order. When one of the formal parameters is used inside the body, it's value is the value that was passed in from the calling procedure. The expression in the return statement is evaluated at the end of the execution of the procedure and the resulting value is returned to the calling procedure as the value of the procedure call.
Now let's invoke the procedure. Suppose we execute the line
double area3 = square(3);The value 3 is called the actual parameter to which the formal parameter x will be bound. Java creates a little binding table for use inside the procedure and then evaluates the procedure body using that binding table. This little binding table is called an environment.
So, the actual parameter 3 is passed into the procedure square
and bound to the variable x. Then the body of the procedure is executed.
The return value 9 is computed, returned as the value of
the expression "square(3)", and subsequently assigned to the
variable area3
.
Any expressions can be used in the actual parameter list when making a procedure call, provided that the types match the types of the corresponding formal parameter list. For example,
double x, y; double r = 3; x = square(2+1); // 2 + 1 is evaluated, 3 is passed in y = square(r); // r is evaluated, 3 is passed in i = square("3"); // ERROR: type String doesn't match double parameter
Example: areaOfCircle
Having a square procedure is fine, but what we really
want is a procedure called areaOfCircle
that compute the
area of a circle, given the radius. Then we could write statements
like
double area5 = areaOfCircle(5);
So, how do we write the procedure? Assuming that we have already defined the constant PI, we can write:
double areaOfCircle(double radius) { return (PI * square(radius)); }
Notice that we are using the square procedure to compute the square of the radius.
Let's do some more examples of procedures.
Example: hypotenuse
Suppose we are given a procedure sqrt
that takes one
parameter (a double) and returns its square root:
We don't know what sqrt
is doing inside, but we can use
it anyway because we understand its specification. This is a nice
advantage of procedural abstraction.
Suppose we want a procedure hyp that finds the length of the hypotenuse of a right triangle, given the lengths of the other two sides.
Recalling that the length of the hypotenuse is the squre root of the sum of the squares of the lengths of the sides, we can write:
double hypotenuse(double sideA, double sideB) { return sqrt(square(sideA) + square(sideB)); }
And we can call the procedure as follows:
double hyp = hypotenuse(3,4);
Let's think about the evaluation step by step, using the substitution model:
hypotenuse(3,4) sqrt(square(3) + square(4)) sqrt((3 * 3) + (4 * 4)) sqrt(9 + 16) sqrt(25) 5
Math
. (A
method is a procedure defined as part of a class.)
We'll say more about classes later, but for now you can think of
the math class as a collection of procedures (methods) and constants,
accessed by Math.
name-of-method or
Math.
name-of-constant. Examples include:
Math.sqrt(double x) // returns the square root of x Math.pow(x,y) // returns x raised to the power y Math.PI // an approximation of piSee the Math class documentation for a complete list of the available methods and constants.
Another useful class is the System
class that provides
an output stream for printing textual output
from your program to the screen,
as in the following example.
A Complete Program
Let's write a complete program using the procedures we have created.
The program calculates hypotenuses of some right triangles.
public class Triangles { public static void main(String args[]) { test(3,4); test(9,12); } public static double square(double x) { return (x * x); } public static double hypotenuse(double sideA, double sideB) { return Math.sqrt(square(sideA) + square(sideB)); } public static void test(double a, double b) { System.out.print("A right triangle with sides " + a + " and " + b); System.out.println(" has hypotenuse " + hypotenuse(a,b)); } }Some notes:
main
that will be the entry point of the
program.
System.out.print
method prints its argument to
the screen.
The System.out.println
method is the same, except that
it also goes to the next line after printing.
So, the output of the program would be:
A right triangle with sides 3 and 4 has hypotenuse 5. A right triangle with sides 9 and 12 has hypotenuse 15.
Reduction
Suppose we want to find the diagonal of a rectangle with sides a and b.
The procedure could be written as
double rect_diag(double s) { return Math.sqrt(square(a) + square(b)); }However, we can save ourselves some work by using what we've already done. We can reduce the problem of finding the length of the diagonal of a rectangle to the problem of finding the hypotenuse of a right triangle, as shown in the following diagram.
This would be coded as
double rect_diag(double a, double b) { return hypotenuse(a, b); }We've transformed one problem to another. This is called a reduction. This is useful in many areas of computer science.
Math.abs
wasn't built in. Thus, we want a black box
like this:
The mathematical definition of this function is
double abs(double x) { return ???Wait! How can we do something different depending on the value of x? So far, all of our computations have been uniform -- they do the same thing to every input, regardless of its value.
What we want is for the execution to be conditional on some test. For the test, we can use any boolean expression (also known as a predicate). But how do we make the execution conditional on whether the test is true or false?
For this purpose, Java provides conditional statements. One possible form is
if condition // NO SEMICOLON!! consequent;Here, the consequent is executed iff (if and only if) the condition is true. The other possible form is
if condition consequent1; else consequent2;Here, if the condition is true, then consequent1 is executed; otherwise, consequent2 is executed.
Using this construct, the abs
example could be written as
double abs(double x) { if (x > 0) return x; else if (x == 0) return 0; else return -x; }We can shorten this a bit, but it's important to check that all possible cases are covered!
double abs(double x) { if (x >= 0) return x; else return -x; }Notice that execution continues after the conditional statement, so we could also have written
double abs(double x) { if (x >= 0) return x; return -x; }Note: indentation has no meaning in Java. It is only used for readability.
boolean inside(int x1, int y1, int x2, int y2, int px, int py) { return ((px >= x1) && (px <= x2) && (py >= y1) && (py <= y2)); }Now we can use this procedure as a test in a conditional statement. For example,
if inside(3, 3, 10, 12, 5, 7) // rectangle: 3,3,10,12; point: 5,7 System.out.println("The point is inside the rectangle."); else System.out.println("The point is outside the rectangle.");We can also use the results of procedures within boolean expressions. This is often useful in tests in conditional statements.
if (hypotenuse(3, 4) != 5) System.out.println("Error in hypotenuse test.");
if ... { ---; ---; } else { ---; ---; }
if (y > Math.sqrt(z)) ...;
if !(y < Math.sqrt(z)) ...;
if (exp) return true; else return false;write
return exp;