Method Structure


The easiest way to introduce methods is with an example. Suppose you want to print out the fifth powers of the numbers 5 through 9. The following code, which doesn't use methods, does the job in a clumsy, inelegant way:

 1. public class NoMethods  2. {  3.   public static void main(String[] args)  4.   {  5.     int n = 5;  6.     int n5th = n*n*n*n*n;  7.     System.out.println(n + " >=> " + n5th);  8.     n = 6;  9.     n5th = n*n*n*n*n; 10.     System.out.println(n + " >=> " + n5th); 11.     n = 7; 12.     n5th = n*n*n*n*n; 13.     System.out.println(n + " >=> " + n5th); 14.     n = 8; 15.     n5th = n*n*n*n*n; 16.     System.out.println(n + " >=> " + n5th); 17.     n = 9; 18.     n5th = n*n*n*n*n; 19.     System.out.println(n + " >=> " + n5th); 20.   } 21. }

The application's output is

5 >=> 3125 6 >=> 7776 7 >=> 16807 8 >=> 32768 9 >=> 59049 

When you look at the source code, you might get the feeling that life ought to be better than this. The application has a lot of repetition… and aren't computers supposed to be good at eliminating repetitive tasks? Five of the lines (6, 9, 12, 15, and 18) do almost the same computation, but not quite. Each of them multiplies something by itself 5 times.

It would be great to have a piece of subordinate code that could compute the 5th power of anything. Of course, there would have to be a way to tell the subordinate code what number to work with. The following application does just that, using methods:

 1. public class UsesMethods  2. {  3.   public static void main(String[] args)  4.   {  5.      int n = 5;  6.      int n5th = toThe5th(n);  7.      System.out.println(n + " >=> " + n5th);  8.      n = 6;  9.      n5th = toThe5th(n); 10.      System.out.println(n + " >=> " + n5th); 11.      n = 7; 12.      n5th = toThe5th(n); 13.      System.out.println(n + " >=> " + n5th); 14.      n = 8; 15.      n5th = toThe5th(n); 16.      System.out.println(n + " >=> " + n5th); 17.      n = 9; 18.      n5th = toThe5th(n); 19.      System.out.println(n + " >=> " + n5th); 20.   } 21. 22.   static int toThe5th(int x) 23.   { 24.     int result = x * x * x * x * x; 25.     return result; 26.   } 27. }

The application's output is the same as the output from the previous version.

The code from lines 22-26 constitutes a method. Line 22 is called the method's declaration. It tells the compiler that what is about to follow will be the definition of the method whose name (along with some other information) appears in the declaration line. The definition, or body, of a method immediately follows the declaration, and it must appear within curly brackets.

The general format of a method declaration is

Optional_modifiers Return_type Name(Optional_ arguments)

The only mandatory parts of a declaration are the return type, the name, and the parentheses. In this example, we have one modifier (static), the return type is int, the method's name is toThe5th, and there is one argument (int x) that appears inside the parentheses. Let's look at each of these elements.

You have already been patiently tolerating the unexplained presence of the static modifier in every application we have looked at in this book. It has appeared in the declaration of main, which is a method that appears in every Java application. Understanding static will become much easier after we introduce object-oriented programming in Chapter 7. For now, let's just say that static means "don't be object-oriented." Other modifiers that might appear in a method declaration include access modifiers, which will be presented in Chapter 9.

We'll look at the return type in a moment. Let's move now to the method name. The rules for the name are the same as those for variable names: The first character must be a letter, an underscore, or a dollar sign. The subsequent characters may be any of these, or they may be digits.

As with variable names, you have a broad choice. You should pick the name that does the best possible job of describing what the method does. When the name appears outside the method, as in lines 6, 9, 12, 15, and 18 of this example, the path of program execution detours through the method. This is known as calling or invoking the method, and a line that calls a method is the method's caller.

Note that in the lines where the method is called, the method call (the name followed by something parenthetical) is used in a context where you would expect to see a value. Until now, the right-hand side of an assignment has been either a literal, a variable, or an arithmetic or boolean expression composed of literals, variables, and operators. Now we add something new to the mix. Anywhere the compiler expects a value, you can use a method call. This is because a method call produces a value, called the method's return value. When the computer executes a line of code that includes a method call, the computer takes a detour through the method body in order to compute the return value. When the detour is finished, execution continues where it left off.

The arguments are the method's inputs. In this example, the argument list is int x. This means that the method has one input, whose type is int. Within the body of the method, that input will be called x. When the method runs, the actual value of x will be whatever the caller wants. The caller specifies an input value by putting the value in parentheses in the call line. Lines 6, 9, 12, 15, and 18 all pass n as the method argument, but the value of n is different for each of those lines. In line 6, n is 5, so the call from line 6 will execute with x set to 5. In line 9, n is 6, so the call from line 9 will execute with x set to 6. And so on. This demonstrates the flexibility of methods.

The return type in the method declaration tells the type of the return value. The returning of a value happens in line 25, where we see the return keyword. This causes the path of execution to return to the caller line. The value following return is the return value. In this example, the return value is result, which is the 5th power of the argument.

Argument Lists

The toThe5th method in the previous example took a single argument, so within the parentheses in the method declaration, we saw a single type (int) followed by a single name (x). You can create methods with an arbitrarily number of arguments of arbitrary types. To do this, just write a declaration with the following format:

Mods Ret_type Name(type0 arg0, type1 arg1, type2 arg2 ...)

Note

Note that the numbering of the arguments starts at 0, rather than 1. Whenever you see someone do this, you can be certain that they work in the computer field, where counting from 0 is conventional. We already saw this in Chapter 1, where SimCom's memory addresses started at 0. We will see it again in Chapter 6, "Arrays." It is important to get into the habit of counting from 0 as soon as possible, even though there is a slight inconvenience. When you start counting from 1, the last number you count out is the actual number of things you've counted. When you start from 0, the actual number is 1 more than the last number you've counted out. So in the preceding declaration format, we have 3 arguments, and the largest index is 2.

The following method takes 2 arguments:

static double hypotSquared(double leg0, double leg1) {   return leg0*leg0 + leg1*leg1; }

Recall that in any arithmetic expression, multiplication takes precedence over addition, so the method really does return the square of the hypotenuse.

The MethodLab animated illustration demonstrates the passing of arguments and the returning of return values. To run the program, type java methods.MethodLab. The display presents code that calls a method, as shown in Figure 4.1.

click to expand
Figure 4.1: MethodLab

Click on the Run button to see the animation. The black lines indicate the passing of arguments, which are called a and b in the caller but x and y in the method. The blue line represents returning the return value. When the animation is finished, click Reset to start again. Figure 4.2 shows MethodLab after the animation finishes.

click to expand
Figure 4.2: MethodLab after animating

You can customize MethodLab by typing any integer value you like into the text fields for a and b. You can also enter any numeric formula for the value of z, which becomes the return value. Try the formula in the preceding example. Since this method's arguments are called x and y, the formula should be

x*x + y*y

With this formula, try running MethodLab with a = 3 and b = 4. Try again with a = 12 and b = 5. These combinations are the only small integers that represent triangles with integer hypotenuses.

A method's argument list can be arbitrarily long. At times you might even want a method with no arguments at all. In that case, the method's declaration has an empty pair of parentheses after the name, and the caller passes nothing at all inside its own parentheses:

float f = sayHello(); … static float sayHello() {   System.out.println("Hello");   return 3.14159f; }

The important thing is that when you call a method, the call should have the same number of arguments as the method declaration, and the types of the arguments passed by the caller should be compatible with the types in the method declaration. This is subtly different from saying that the caller's argument types should exactly match the types in the method declaration. Recall from Chapter 3 that a value can be assigned to a variable of a different type, provided the new type is wider than the old type. Figure 4.3 (first shown in Chapter 3) shows the width relationships among the numeric primitive types.

click to expand
Figure 4.3: Numeric type widths

The rule for passing method arguments is similar to the rule for assignment: You can pass an argument whose type is different from the type declared by the method, provided the type declared by the method is wider than the type that you pass. Recall that a type is wider than another type if you can get from the first type to the second type by following the arrows in Figure 4.3.

For example, suppose a method has the following declaration:

static char abcde(long wayToGo) 

This method can be called with a long argument, or with any argument whose type is narrower than long: byte, short, char, or int.

More on Return Types

At times, you might want to create a method that doesn't return anything. The method might print out a message, display a dialog box, or store a value in a file. In cases such as these, it is difficult to think of any value that the method could meaningfully return, and concocting a return value just for the sake of having one would not contribute to the quality of the program. (A basic principle of writing fiction is that every word should contribute to developing the plot or developing the characters. We can invent a similar principle for writing software: Every word of source code should contribute to the operation or the readability of the program.)

Suppose you want a method that prints a number along with a message. Since no return value is needed or relevant, replace the return type in the declaration with the word void:

static void printPretty(int x) {   System.out.println("x = " + x); }

Note the absence of a return statement. A void method runs until it executes its last line. Then it returns automatically. Optionally, you can add a return statement with no value at the end of the method:

static void printPretty(int x) {   System.out.println("x = " + x);   return; }

Here the return statement doesn't contribute to program execution or readability (we already know that the method returns when it hits bottom), but later we will see cases where explicitly saying return can be useful.

A call to a method with a non-void return type can be used anywhere that a variable or literal of the same type can be used: as the right-hand side of an assignment, as an operand in an operation, or even as an argument of another method call. By contrast, a call to a void method has no type. So if method iAmVoid is void, you could not say int z = iAmVoid(); because there would be no value to assign to z. When you call a void method, you just want it to do its thing, so you make the call all by itself, followed by a semicolon, like this:

iAmVoid(); 

If a method changes the state of the program or the computer in any way (other than returning the return value), the change is called a side effect. Clearly, when you call a void method, you do so because you are interested in a side effect. (In this example, the side effect was the printing of the message.) Sometimes you might want to call a non-void method, not because you are interested in the return value, but because you want the side effect. In that case, you can just call the method as if it were void.

For example, the following method both prints out and returns hypotenuse squared:

static int vocalHypotSquared(int a, int b) {   int hSquared = a*a + b*b;   System.out.println("h-squared = " + hSquared);   return hSquared; }

If you just wanted to print out the message, you could call the method like this, ignoring the return value:

vocalHypotSquared(5, 12);

Polymorphism

Polymorphism comes from the Greek for "many forms." It is one of several five-syllable words pertaining to object-oriented programming. In our context, it means that a method can have one name but many forms. In other words, you can define multiple methods with the same name.

At first, this might seem impossible. How can the system know which of the various methods you had in mind? The rule is that if two methods have the same name, their argument lists have to be different. That is, the types that appear in lists must differ; the argument names are not considered here. If a method name appears more than once, we say that the name is overloaded.

For example, the following two methods could appear in the same program:

static int getMass(int n) {   … } static int getMass(double a, char c) {   … }

Here, getMass() is legitimately overloaded. However, the following two methods could not appear in the same program:

static int getMass(int n) {   … } static int getMass(int x) {   … }

The argument names are different, but that doesn't help. Both method versions have the same name, and each version takes a single argument of type int, so the compilation will fail. If the argument types were different (for example, if the argument of getMass() had type long or byte), the code would compile without error.

Methods That Call Methods

Methods can be called from anywhere – even from other methods. In fact, any complicated program is likely to consist of methods that call other methods that call other methods, and so on, to many levels of depth. For example, you might have a method that prints out two values:

static void print2Vals(int val0, int val1) {   System.out.println(val0 + " and " + val1); }

Now what if you want a method that prints out the cubes of its two arguments? You might do it as follows:

static void print2Cubes(int val0, int val1) {   int val0Cubed = val0*val0*val0;   int val1Cubed = val1*val1*val1;   System.out.println(val0Cubed + " and " + val1Cubed); }

Since you already have the print2Vals method, you can rewrite print2Cubes as follows:

static void print2Cubes(int val0, int val1) {   int val0Cubed = val0*val0*val0;   int val1Cubed = val1*val1*val1;   print2Vals(val0Cubed, val1Cubed); }

Now you have a method, (print2Cubes) that calls another method (print2Vals ). You can rewrite print2Cubes to be even more terse, as follows:

static void print2Cubes(int val0, int val1) {   print2Vals(val0*val0*val0, val1*val1*val1); } 

Since the multiplication is repetitious, you can also introduce a new method:

static int nCubed(int n) {   return n*n*n; }

Now print2Cubes becomes

static void print2Cubes(int val0, int val1) {   print2Vals(nCubed(val0), nCubed(val1)); }

Now you have a method, (print2Cubes) that consists of a single call to another method (print2Vals), and that call's arguments are both expressed as method calls (to nCubed). This kind of structure—calls to calls to calls—is perfectly typical of programs.

Passing by Value

In formal computer terminology, we say that Java passes by value. This is just another way to say that methods get copies of their arguments and have no access to the original values. The alternative is called passing by reference, where methods work with the caller's data and not with copies. The latter is a perfectly valid way to design a language; it's just not the way Java does it.

When a caller calls a method and the flow detours into the method's body, the JVM copies the argument values provided by the caller and gives the copies to the method. This means that if the method alters its arguments, the alteration has no effect on the caller.

Consider the following method:

static void print3x(int x) {   x = 3*x;   System.out.println("3 times x = " + x); }

The method triples its argument. You might wonder what happens to the value that the caller passes to the method. For example, what does the following print out?

int z = 10; print3x(z); System.out.println("Now z is " + z);

Does this code print "Now z is 10" or "Now z is 30"? If the method has access to the actual data passed in by the caller, the code should print out "Now z is 30". However, the code actually prints "Now z is 10" because the method triples its own private copy, not touching the

caller's version. After the method returns, the memory used for storing the method's copy is recycled. The method's copy does not survive after the method returns.

Order

A program is, in large part, a collection of methods that call other methods. These other methods call still other methods, and so on.

Within an application, methods can appear in any order. Once again, you are in a situation where your choices can make a program either easier or harder for others to read. It makes sense to put related methods near one another. It is common practice, though by no means universal, to put the main method at the very end, just before the final closing curly bracket. From here on, this book will follow that convention.




Ground-Up Java
Ground-Up Java
ISBN: 0782141900
EAN: 2147483647
Year: 2005
Pages: 157
Authors: Philip Heller

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net