A method declaration is similar to a field declaration. Here is an example of a method declaration that computes the sum of two floats:
.method public computeSum (FF)F .limit locals 3 .limit stack 2 fload_1 ; Push the first argument fload_2 ; Push the second argument fadd ; Add them together freturn ; Return the result
The .method directive is structurally identical to the .field directive. It begins with a list of modifiers, followed by the name and the descriptor. The descriptor is a little different for methods. It's written as
where the list of typei is the types of the arguments and typereturn is the type of the return. Each of these types is one of the types in Table 2.2 for fields. There aren't any spaces between the arguments. Some examples are shown in Table 2.4.
Methods may have modifiers, just like fields. The available modifiers are different for methods. A summary of the method modifier keywords is given in Table 2.5 . The .method declaration continues until a .end method directive is found. In between .method and .end method are the instructions that make up the method. There are about 200 different instructions to do different things in the JVM. The instructions are the subject of the next few chapters.
In the computeSum example, the fload_1 and fload_2 instructions push values from local variables onto the stack. The fadd instruction adds the two float values on top of the operand stack, replacing them with the sum. The freturn returns the top of the stack.
Two special directives apply within method bodies: .limit stack and .limit locals. These tell the JVM how much space it should allocate for the method when it is executed: .limit stack limits the total height of the stack at any point, and .limit locals limits the size of the local variable array. Each may be as high as 65,536, but few programs require anywhere near that much space.
The number in the directive is the number of slots to reserve. A slot is big enough to hold an int, float, or reference value. It takes two slots to hold a long or double.
If the .limit directives aren't given, the Oolong assembler takes its best guess. The assembler errs on the side of caution rather than efficiency.
These limits aren't just suggestions. The JVM implementation will enforce them. If you try to use more stack space or more local variables than you requested, the virtual machine will refuse to execute the method. It can tell even without executing the code how many variables are used, since all of the instructions that affect the local variable array have the number of the slot incorporated into the instruction as an argument. (That is, the number of slots that are used in each instruction is fixed in the instruction itself, not computed when the program is run.) Thus, if the program contains the instruction
it knows that at least nine variables are used (variables 0 through 7 plus one more, since the lload instruction affects both variables 7 and 8).
Measuring the maximum height of the stack just from looking at the code is a little harder, but it can be done. The enforcement is the job of the verification algorithm. See chapter 6 for more information about the verification algorithm.
You can have multiple methods with the same name as long as they have different descriptors:
;; A method to print a float value .method println(F)V ;; A method to print a String value .method println(Ljava/lang/String;)V ;; A method to print a long value .method println(J)V
This is called method overloading. In Oolong, when a method is called you must specify the entire descriptor, including the return type. This means that you can have two methods that differ only in the return type:
.method computeResult ()I ; Return the result as an int .method computeResult ()D ; Return the result as a double
However, you won't be able to use the resulting class from Java programs, so the practice is discouraged.
In Java, two methods may not differ only in their return types. If you have two methods with the same name and identical arguments and different returns, it's an error. That's necessary because in Java methods are specified only by name. The Java compiler must infer which overloaded method is intended by looking at the types of the arguments. It isn't possible to infer using the return type, because it isn't always obvious from context. From this code
float f = (float) computeResult(); // Which method?
it is impossible to determine which version of computeResult is intended. Rather than creating some arcane rules, the Java designers simply ruled this illegal Java, even though it is legal for the JVM.