| |||
Methods Avoid Repetition | |||
|
Believe it or not, you now know enough to write just about every program that could be written,
if you don't care about things like graphics and fancy input and such. But writing big
programs in the way we have been writing small ones would be a pretty nasty proposition. The problem
is that there is no way to take advantage of the commonality that often exists between
different parts of a large program. If you had some big calculation to perform
with different values in different parts
of a program, you'd have to repeat the appropriate code in each location. Consider writing a program to print out the lyrics of the song "Ninety-Nine Bottles of Beer on the Wall". It would look something like:
This would go on and on, for ten or more pages. But notice that there is a lot of commonality in the program. For example, the two lines: which print the chorus, occur in exactly the same way in each and every verse. The solution provided by essentially every programming language is to allow you to break your program up into a lot of different subprograms. In most languages these are called functions or procedures. In object-oriented languages such as Java and C++ they are referred to as methods, though here we are using them more in the traditional manner of functions.
| |||
We have already defined one method, main, in all of our programs. There is nothing
particularly special about main, other than that it is the method that is called
automatically when the application is run. From now on our programs will have many methods.
The method main will continue to be the one called when the applciation is started,
but it may in turn call other methods we write to do some of the work. Of course, we have already
been calling other methods (such as HMCSupport.out.println and Math.random)
from within main,
the only thing new is that the methods we will be calling will be ones we write ourselves.So, for example, in the last program, it would make sense to define a method which prints the chorus of the song. The method would consist of the two lines of code, wrapped with the appropriate header information: In order to call the method, we just give write the method name like any other we have used before. Since this is a method with no parameters, we follow the method name with an empty set of parentheses, as in:
Putting this into our previous program, we get the following version:
This change shortens the program by more than a page, and it also has the advantage of making it crystal-clear that each verse shares those exact same two lines.
| |||
Methods Can Receive Information Effecting Their Behavior | |||
|
Of course, it is rare that you will have exactly the same lines duplicated in several
places in a program. It is more likely that you might have the same process
going on in several places, but manipulating different values. For example,
in this program as it stands, there is little additional exact duplication. However, each
verse consists of almost the same code; only the number of bottles changes.
| |||
| Data Comes from Named Parameter Variables |
Methods can pass information
to the methods they call, so that the behavior of those methods depends on the values
they receive. As we have said earlier, the values passed are called parameters
We have already sent parameters to
methods to effect their behavior: each time we call HMCSupport.out.println
we send it the value we want it to print; when we call the methods in the Math
package, we generally send them the values with which we want them to compute.To write a method whose behavior we want to vary based on the values it is sent, instead of writing the definition of the method hard-wired to a particular value, a variable is used in place of that value. Thus the code is parameterized over that value. For example, in the song-printing program, we can take all the code that depends on one particular value of the number of bottles of beer and make it refer to a variable holding the number of bottles of beer (as a string of words), as in:
If we do this throughout the whole program, we then notice that there is a new block of code that is repeated verbatim over and over in the program. Its behavior varies because the value of the variable it depends on changes. Now, this block of code actually overlaps two verses, but that is no problem. We just have to construct the program around it properly. When a method is declared the declaration has to say that the method expects to receive a value. It also says what the method will call the value it receives, and what kind of value it will be. This is called declaring the formal parameters of the method. This declaration appears in the method header, which takes the form:
Since the
| ||
Notice that every formal parameter name is preceded by a type.
Unlike the declarations of variables within a method, the parameters to a method cannot be grouped with a single type declaration. So, for instance, if we are defining the method foo which takes three integer parameters, we must write:
We cannot write:
Since the method that prints a verse (roughly) of the song needs to receive a
Inside the method, the formal parameter can be used just like any variable declared in the method. You can examine or change its value at will. (But note, as we will see below, changing its value will not effect the value of anything outside the method.) Putting the header together with the code for the body of the method we get:
| |||
| Calling A Method With Parameters |
To call the method we must now provide it with a It could be a variable, as in: Or, it could be an arbitrary computation resulting in a String value:
When you call a method, the value, or variable, or expression you send to the method is called the actual parameter since it is the value that will actually be used for the formal parameter when the body of the method is executed.
Putting these ideas together, we can build a much shorter program to print the
beer bottle song. Notice that because our
For now all our method headers will continue to start with
| ||
Actual Parameters To Methods Are Copied | |||
It is important to note that when you pass an actual parameter to a method,
the value of the actual parameter is copied into
the formal parameter, which is a new variable that exists only within the method. Therefore, if a method makes an assignment
to the variable named as the formal parameter, it does not affect the value as it exists in the
method that called this one. For example, consider this simple program:
The output of this program is: Even though we increment the parameter inside the method, that does not effect the variable holding that value in the calling method. This is a very important aspect of the way that Java works. When sending parameters to a function it uses a protocol known as pass-by-value, which means that it is the value of the actual parameter that is sent, not the actual parameter itself.The value of x before call: 4 Value of x at method start: 4 Value of x at method end: 5 The value of x after call: 4
When you think about it, this makes perfect sense. The method since we said that the actual parameter in a method call can be a literal, a variable, or any other expression that has the right type. But since the value being passed is not in a variable, if we did not first copy it into a new variable used in the method, how could we increment it. You can't change the value of a literal!
| |||
Don't Get Confused About Parameter Names | |||
| |||
At the risk of beating a dead horse (apologies to the animal rights activists in the audience!),
it is extremely important to understand the following ideas:
| |||
This may all seem a bit confusing, but really it is no different that what you have been
doing in math for years. Certainly you would have no difficulty writing, or understanding,
the following bit of math prose:
Let us define the function f(x) = x * x. Then, f(2) is 4. If y is 3, f(y) is 9. On the other hand, for x equal to 5, f(x) is 25.When we give the definition of the function f, it is as a schematic computation, whose actual value is later filled in for a given value of the parameter variable x. When we refer to f(2) it is as the result of carrying out the schematic computation on the value 2. When we refer to f(y) it is the result of evaluating that schematic expression for the value 3, since that is the value of the variable y, which the function is being applied to. That is, the value of y is the value that is used for the value of x for as long as we are evaluating the expression that defines the function. Finally, when we refer to f(x) in the last sentence, the value of x, which is 5, is used as the value of x while evaluating the expression. The fact that the value came from a variable named x has nothing to do with the use of the name x in the definition. The x in the definition refers to whatever value the function is being applied to.
| |||
Methods Can Return Information As Their Result | |||
|
So far, all the methods we have defined simply execute some process (possibly taking
parameters to vary their behavior) and then exit. In many cases (in fact, in most cases)
the task of a method is to gather or compute some value and communicate it back to
the method that called it. The method is said to return this value.
| |||
| The Method Header Specifies The Type Of Value Returned |
If a value is to be returned, the header of a method must declare the type of value that
the method will return. This type is given after the public static part of the
header and before the method's name. Putting the type here enables the compiler to insure that the calculations inside the method are correct in that they are at least producing the type of
value you intended. It also allows the compiler to check that calls to the method are written in
such a way that they are prepared for a value of this type. So for example, if the method foo were defined to take an int as a parameter and to return a double
as a result, as in:
then the compiler would know that the call: should generate an error (since we are trying to take a double value and put it in
the int variable y).
Since all of the methods we have defined
so far have not returned values, we have been using the type In this example, the return type is the same as the parameter type, but that is certainly not always true. For example, we can define the following method which takes a long
as a parameter and responds whether that parameter is even or not:
Recall that, even though we have declared the formal parameter to be a long,
you could pass in an int value as the actual parameter in a call to the function without
using an explicit cast. Since the copying of the actual parameter's value to the formal parameter's
storage is essentially the same as an assignment operation, the
automatic casting rules say that an int can be cast up to a long without damage.
Of course this method is more complicated than it needs to be. Think about what the
overall structure of the
| ||
return Is A Flow Control Statement |
Notice that in the first version of the last example there were two uses of the return
statement. Clearly, though, only one of the two would get executed since they
are in opposite branches of a conditional statement. It is important to
understand, though, that only one return statement is ever executed in
a given call to a method. At the moment the statement is executed,
the value specified is returned, and flow returns to the point
where the method was called. Thus, though the original is probably better style,
you could also write this method as:
In this version, if the parameter passed is even, the test succeeds and the first return statement is executed. The execution of the method stops immediately,
and the value true is returned to the point where the method was called.
If the parameter is odd, then the test fails, and the body of the if
(containing the first return statement) is skipped over. At that point the
next statement encountered is the second return statement. It is executed
and the method returns false.
| ||
Common Errors with | |||
| |||
Placing Code After A return |
If there were any additional code after the
second return statement in the last example,
it would never get executed in any circumstance.
The same would be true of any code inside the if block but after the first
return statement. Therefore, the compiler will alert you to such useless code by
generating a message like:
pointing to a line that can never be executed.
| ||
Forgetting To return A Value |
A common error is to define a method to compute some value, but to forget to actually
return the value at the end. For example, we might write:
Surely, the compiler realizes that since we went to all the trouble to compute the value of the square, and even named the method indicatively, that we intend to return that result! But of course the compiler cannot make that assumption. There might be several values computed in the course of a method. The compiler cannot presume to guess which one you consider to be the final result. So, in this case, it will complain with an error like:
Note that if the
| ||
Misusing return In A void Method |
If you define a function as void and attempt to return a value,
the compiler will complain, since no value should be returned. Similarly, if you call
a void method in a way that expects a return value (for example on the
right hand side of an assignment, or inside a larger arithmetic expression) the compiler
will also reject it.
| ||
Proper Use Of return In A void Method |
Nevertheless, the return statement can be used within void
methods. In such cases it is simply used without a value to return, and is instead followed
immediately by the semicolon, as in:
In these uses its purpose is simply to force an immediate exit from the method, instead of the ordinary exit which occurs when the flow reaches the last statement in the method.
| ||
Top-Down Design With Methods | |||
|
Let's look at a slightly larger example of computing with methods. Consider the
problem of computing the number of ways of selecting a group of k objects
from a population of n distinct objects. This value (called n
choose k) is given by the formula:
One possible top-down design for a program that computes these values for the user might be:
| |||
| Methods Capture Repeated Tasks |
At this point in the top-down design it becomes obvious that
it would be better to write a function for computing
factorials than to repeat the necessary for-loop three separate times in
our code. This will have two effects. Obviously, it will save some
typing and paper. However it will also make the program
clearer and more natural. Rather than having to look at a
few-line-long loop and figure out that it computes the factorial
(assuming we don't include a comment at the beginning of each loop saying
what it does), the reader will instead see method invocations that look
something like:
What could be easier to understand?
| ||
| Methods Capture Units of Design |
Even when a block of code occurs only once in a program, it is often a good
candidate for a method. By gathering the code together and assigning it
a name, the program using that code becomes clearer and more well-structured.
In addition, you might later find yourself writing a program in which you need
that same task several times. By starting to write your programs as collections
of methods, you slowly build up a library of methods that you can choose
pieces from at a later date. These selected methods can be dropped into new programs
with little or no modification, since they communicate with the
overall program only through parameter passing and return values, and don't depend
on how variables were named in the overall program.
For example, even though this program will only compute n choose k
once, it seems a natural choice for a method. It will make this program's In the end, it is not uncommon to have a method for nearly each line of each level of the top-down-design for a program. Each block of code then reads like a higher-level outline of a process implemented by the methods at the next lower level. Putting these ideas together, we come up with the following program:
| ||
Organizing Your Methods | |||
|
In some languages, such as C, it is necessary to define a method before any other method that uses is. This makes the job of the compiler easier in that it will always have seen the method definition and its associated types by the time it has to decide whether some use of the method is legitimate. Some languages relax this requirement by allowing you to put what is called a prototype of the method (basically just a copy of the method header) early in the program but leave the actual definition of the method till later.
Java does not have any requirements on the order of methods in a class definition. The compiler
just makes multiple passes through the code to make sure all the types match up properly. This
means that you can organize the methods in any way you prefer. There is no fixed style rule
for this, but one good choice is to organize the methods in order by their level in the top-down design. The method ("main")} comes first, providing a very high-level view of the
program's structure. This is followed by the methods the
| |||
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 Sun, Oct 4, 1998 at 12:15:32 PM. | |
http://www.cs.hmc.edu/~hodas/courses/cs5/week_07/lecture/lecture.html | |