• No results found

Static methods and parameter passing

In document Java (Page 60-69)

Suppose we have two variables, called x and y, of type double, and we want to find their average.

Not a problem - just add them up and divide by 2:

double x=28.0;

double y=17.6;

double average=(x+y)/2.0;

Now suppose we want the average of a and b:

double a=8.0;

double b=107.6;

double average2=(a+b)/2.0;

But maybe we want to find the average of several hundred pairs of values. There are two issues - we would have almost the same piece of code (adding and dividing by 2) duplicated hundreds of times.

But we also need the code to apply to different variables.

The solution is like this:

class Test {

Follow through what happens:

1. Execution starts at main

2. Variables x and y are declared and assigned initial values.

3. Variable z is declared

4. We have an assignment z = average(...

5. This invokes the method named average. Execution switches to the block of code starting 'static double average..'

6. The values of x and y in main are copied to d1 and d2, respectively, in the 'average' code block.

7. In average, a variable 'a' is declared, and there is a calculation to work out the average of d1 and d2.

8. The code block returns the value 'a'.

9. Execution returns to main, at the point it came from. The value returned from 'average' is assigned to z

10. z is output - so we can check it has worked.

In 'static double average..', the word double means that the type of the returned value is double.

The execution path

Execution starts at main. When the invocation of 'average' is reached, execution switches to it. At the 'return' statement, execution switches back to where it came from (here, in 'main'), and carries on from there.

The data path

The value of x is copied to d1. The value of y is copied to d2.

The return value, 'a', is assigned to z.

The parameters in the method header (here, d1 and 2) are sometimes called the formal

parameters. The parameters in the method invocation ( x and y) are the actual parameters.

So the values of the actual parameters are copied to the formal parameters.

Exercise

Modify this so that it finds the average of 3 values. Test it.

How it works

The method invocation process works using a data structure called a stack. A stack is simply a pile of data. Data items are added to the stacked ('pushed') by placing them on top of the stack. Data is removed (popped) by taking the top item off the stack. The effect is that a stack is a last in, first out (LIFO) structure. The item that comes out of it will be the last thing that was put in it.

When a method is invoked, the current address (location of the program instruction) is pushed onto the stack. At the end of a method (the return statement) a value is popped off the stack, and execution proceeds from there - in other words, it returns to where it came from.

The issue is that inside a method, we might have an invocation of another method, and so on. In fact

What if a method invokes itself? This would require an infinitely big stack. Your program will end, with the error message 'Stack overflow'. See the section on recursion.

The stack is also used to pass parameters. At the invocation, the return address is pushed onto the stack. Then copies of the actual parameters (x and y) are pushed onto the stack. At the start of the method, the correct number of values are popped off the stack (here 2) and placed in the formal parameters (d1 and d2). Just 1 value, the return address, is left on the stack, ready for the return statement.

Another example - factorial

The factorial of a number n (usually written n!) is 1 x 2 x 3 x.. n. So for example 3! is 1 x 2 x 3 = 6.

We could have a method to calculate factorials:

class Test {

Check how the factorial method works.

In main, we use a variable n. We also use n as the name of the formal parameter in the factorial method. Does it matter? Will the values of the two n's get mixed up? No.

The name of the actual parameter is n, same as the formal parameter. Does that matter? No.

Does a formal parameter have to have the same name as the actual parameter? No. Can it? Yes.

Exercise

A nth. triangular number is the sum of all integers from 1 to n. So the fourth is 1+2+3+4 =10.

Write a program which has a static method to find the nth triangular number, and which outputs the first 10 triangular numbers.

The Math class

Suppose we want to use the sine function. How to do that? We could write our own static method, calculating a sine as a power series expansion, but it might be tricky. We don't have to, its already there.

The Math class has a collection of static methods for all the usual maths functions. We use it by invoking Math.sin() (taking its argument=parameter in radians). The class also has Math.PI as a constant. So for example

System.out.println(Math.PI);

System.out.println(Math.sin(Math.PI/6));

double angle = 2.0; //radians double sin = Math.sin(angle);

double cos = Math.cos(angle);

System.out.println(sin*sin+cos*cos);

which outputs:

3.141592653589793 0.49999999999999994 1.0

Remember that floating point arithmetic is not totally accurate.

Exercise

Write a program which inputs the radius of a circle and outputs its area.

Scope

Instead of finding the average of x and y as (x+y)/2, we could calculate it as x/2+y/2. For example:

public class Test {

static double average1(double x, double y) {

This works as expected.

But both methods declare a local variable 'result' with the same name. Will this name clash be a problem? No. Why not? Because they are in different scope.

Suppose we go back to one method, and refer to 'result' in 'main':

public class Test {

static double average1(double x, double y) {

This is a problem - we get a compile-time error, and the compiler complains that 'it cannot find the symbol result'.

Another example:

scope of 'result'

'result' is out of scope

static double average1(double x, double y) { if (x > 5) {

double result = (x + y) / 2;

}

if (x > 7) {

double result = (x + y) / 2;

}

return 3.5;

}

Here we have declared 'result' twice in the same method. This is not a problem. We have two blocks, enclosed in { }, and scope is limited to that block. The two 'result's are in different scope.

Why was Java (and Pascal and C and C++ and most versions of Basic and many other languages) designed like this? It means that when choosing a local variable name, you do not need to worry if you have used the same name elsewhere. With an application with many thousand lines of code, written by a team, and using variable names like x and y and result and count and sum, that would otherwise be a big problem. With local variables having limited scope it is not a problem.

Recursion

Recursion is a programming technique in which a method invokes itself.

Recursive factorial

Recall that n factorial n! = 1 x 2 x 3 x..n

An equivalent way of defining this is to say that if n==1, n!=1

else n! = n x (n-1)!

Look at that carefully.

Here it is in Java:

static long factorial(int n) { if (n == 1) {

return 1;

} else {

return n * factorial(n - 1);

} }

This is recursive, since the factorial method refers to the factorial method.

Exercise

Use this to output 2! to 10!

About recursion

Recursive methods are often very short, very elegant and very close to a mathematical definition (as in this case).

Once you get the idea, they are very easy to write.

A recursive method can always be 'unrolled' into an iterative (looping) method. The previous section had a factorial method with a loop.

They are sometimes highly inefficient.

Fibonacci sequence

The famous Fibonacci sequence is 1,1,2,3,5,8,13.. We start off 1,1 then add the last two numbers.

So the definition is if n=1 or 2, fib(n)=1

else fib(n)=fib(n-1)+fib(n-2) and in Java:

class Test {

There is a large and significant branch of mathematics related to the Fibonacci sequence. Google it.

An entire research journal is devoted to it.

The time complexity of recursive methods

When we compare different algorithms, we look at how much memory they use (space complexity) and how much time (time complexity). Ideally we want algorithms which are small and fast.

For our recursive Fibonacci - if we work out fib(20), how many times will the method be invoked?

Spend some time trying to work that out.

We can modify the code to count it for us:

The answer is 13529. Why?

fib 20 -> fib 19+ fib 18

fib 17 + fib 16

fib 15 + fib 14 fib 16 + fib 15

Iterative Fibonacci

Here is an iterative version:

class Test {

static int count = 0;

static long fib(int n) { count++;

long last1 = 1, last2 = 0, next = 0;

for (int i = 0; i < n; i++) { next = last1 + last2;

last2 = last1;

last1 = next;

}

return next;

}

public static void main(String[] args) { fib(20);

System.out.println(count);

} }

In this, fib is invoked just once. So this algorithm is 13000 times faster when n=20. This is a good illustration of the fact that the choice of a different algorithm can sometimes produce code whichis very much faster.

It can be proved that any recursive procedure can be re-written as an iterative one.

But sometimes recursion is efficient. Remember it!

In document Java (Page 60-69)