A method specifies a sequence of actions to be performed by a processor. A method declaration consists of a heading and a body. The heading specifies the name, return type, and parameters of the method. The body is composed of statements that describe the actions to be performed when the method is executed.
A method header specifies the type of value returned by the method (if the method returns a value), the name of the method, and a list of method parameters. The parameter list is a possibly empty sequence of automatic variable declarations, separated by commas and enclosed in parentheses. Examples are:
int firstInstance (Object obj, List list) int length ()
Both these headings specify that the type of the value returned by the method is int
. The first has two parameters, an Object
and a List
. The second has no parameters.
If the method does not return a value, it is specified as void
. For instance:
void setLength (int length)
The signature of a method consists of the method name and the number and types of the parameters. The signatures of the methods given above are
firstInstance(Object, List) length() setLength(int)
A class may not declare two methods with the same signature.
A method header may also contain a
throws clause following the parameter list. This consists of the key word throws
followed by a comma separated list of exception class names:
public void appendToFile (File f, char c) throws IOException, FileNotFoundException
If it is possible for a checked exception to be thrown by the method, and the method does not catch this exception, a throws clause naming the exception must be included in the heading. (A
checked exception is an instance of the class Exception
that is not also a RuntimeException
.) For a detailed explanation, see the section on checked and unchecked exceptions or section 18.2 of Niño-Hosch.
A method body is a block: that is, a possibly empty sequence of statements and local variable declarations enclosed in braces. The heading and body make up a method declaration. For instance:
int firstInstance (Object obj, List list) { int index; index = 0; while (index < list.size() && !obj.equals(list.get(index))) index = index+1; if (index == list.size()) return -1; else return index; }
A method invocation consists of the method name followed by an argument list. The method name can be a simple or qualified name. A simple name invokes a method defined in the same class as the invocation. A qualified name identifies an object or class that defines the method.
The argument list is a possibly empty sequence of expressions, separated by commas and enclosed in parenthesis.
Consider the following examples.
length()
- invoke the method length, defined in the same class as the invocation.
target.setLength(max-min+1)
- invoke the method setLength of the object target.
Require.condition(count>=0)
- invoke the static method condition defined in the class Require.
super.reset()
- invoke the method reset defined in the parent class of the class containing the invocation.
student.getSchedule().isOn(course)
- invoke the method getSchedule
of the object student. This call returns a reference to an object, whose isOn method is then invoked.
If a method returns a value, an invocation of the method is an expression:
int size = length();
A method invocation followed by a semicolon is a statement:
target.setLength(max-min+1);
An invocation of a method specified as void
can only by used as a statement.
When a method is invoked, the arguments are evaluated and assigned to the parameters. Then the body of the method is executed.
The arguments in the invocation must match the parameters in the method definition in type and number. For example, an invocation of the method setLength
specified above must have a single integer argument. An invocation of firstInstance
must have two arguments: the first, a reference to an Object
(or subclass of Object
), and the second a reference to a List
(or subclass of List
).
Arguments are associated with parameters by a mechanism sometimes referred to as "call by value." When a method is called, a variable is created for each parameter. The arguments are evaluated, and the argument values are assigned to the parameter variables. Thus the parameters are initialized with the values of the arguments.
Consider the following example, which unsuccessfully attempts to interchange the values of two integer variables.
void swap (int x, int y) { int temp; temp = x; x = y; y = temp; }
Suppose that a
and b
are int
variables, containing the values 3 and 4 respectively,
and that the method swap
is invoked with a
and b
as arguments:
swap(a,b);
Invoking the method causes new variables x
and y
to be created: x
is initialized to 3 (the value of a)
, and y
is initialized to 4 (the value of b)
.
Executing the method interchanges the values of the variables x
and y
, but does not effect the variables a
and b
. (Hmmm. Can you write a method that will interchange the values of two variables?)
Note that the names of the variables don't matter. If the parameters were named a
and b
, they would still be different variables from those used as arguments.
If the parameter is a reference type, then the argument-parameter binding behaves in a manner similar to that sometimes described as "call by reference." Consider the class Point
, with instance variables x
and y
:
public class Point { public int x; public int y; }
The following method requires a
reference-to-Point
value as argument:
void reflect (Point p) { int temp; temp = p.x; p.x = p.y; p.y = temp; }
If center
is a
reference-to-Point
variable, then when reflect
is invoked with the call
reflect(center);
the parameter p
will reference the same object as the variable center
:
A local variable is a variable declared inside a method. A local variable's declaration consists of a type and identifier followed by a semicolon. For instance,
int count; List schedule; Observer watcher; double[] vector;
A declaration can contain a variable initializer, which is an assignment operator followed by an expression:
int count = 0; List schedule = null; int size = max-1; Student valedictorian = school.getRanked(0);
(For details on array initializers, see the section on arrays.)
Local variables and parameters are called automatic variables. They are (effectively) created when the method containing their declaration is invoked, and are de-allocated when the method completes execution. Thus their lifetime is the duration of the method execution.
Since a method can be invoked a second time before it has completed execution, there may be several "instances" of a variable in existence simultaneously. Consider the following recursive method:
int factorial (int n) { int temp; if (n == 0) return 1; else { temp = factorial(n-1); return temp*n; } }
If the method is invoked with an argument of 3, automatic variables named n
and temp
are allocated, with n
initialized to 3. Before the method completes, it invokes itself with an argument of 2. Thus another pair of variables named n
and temp
are allocated, with n
initialized to 2. Expression evaluation uses the most recently allocated existing instance of a variable. For details of recursive methods, see chapter 17 of Niño-Hosch.
The scoping rules of a language associate applied occurrences of an identifier with defining occurrences. A defining occurrence is the introduction of an identifier in a declaration to name the entity being defined. For instance, in the method declaration
void reflect (Point p) { int temp; temp = p.x; p.x = p.y; p.y = temp; }
the first occurrences of the identifiers reflect
, p
and temp
are defining occurrences. They are introduced to name a method, parameter, and local variable respectively. All other identifier occurrences in the method are
applied occurrences: they are used to reference the named entity.
The scope of a declaration is the section of program text in which applied occurrences of an identifier refer to the identifier introduced by the declaration. The scope of a local variable declaration is from the declaration to the end of the compound statement (or method body) containing the declaration. A parameter is treated like a local variable defined just inside the method body. Thus the scope of parameter definition is the method body.
The scoping rules tell us that the variable temp
referred to in the first assignment statement, is the int
variable declared in the previous statement. As the scope of the declaration is from the declaration to the end of the method body. It would not be correct to write the declaration after the assignment:
void reflect (Point p) { temp = p.x; int temp; ...
Now the occurrence of temp
in the assignment statement is outside of the scope of the declaration.
Also notice that
int maxIndex = 3; int size = maxIndex+1;
is correct, while the following is not
int size = maxIndex+1; int maxIndex = 3;
Though a method can contain several distinct declarations of variables with the same name, their scopes cannot overlap. For example, the following method contains legal declarations of two variables named sum
:
double halve (int x) { double result; if (x > 1) { int sum = x; result = sum/2; // scope of int sum ends here! } else { // declaration of another variable named sum is ok. double sum = x; result = sum/2; } return result; }
while this one does not:
double halve (int x) { double result; double sum = x; if (x > 1) { // still in the scope of double sum! // declaration of another variable named sum not legal. int sum = x; result = sum/2; } else { result = sum/2; } return result; }
Finally, an identifier prefixed with "
something.
" is never a reference to an automatic variable. For instance, in the following method
void setSize (int size) { this.size = size; }
the occurrence of size
on the right hand side of the assignment refers to the parameter, while the occurrence of size
on the left (this.size
) does not.
The value of a local variable that is declared without an initializer is undefined. It is not legal to try to use the value of an undefined variable. For example, in the following
int temp; temp = temp+1;
the variable temp
is undefined when the expression temp+1
is evaluated. The compiler will report this as a syntactic error.
The compiler insists that a variable be defined along every path leading to an expression in which its value is used. For instance, the following is also syntactically incorrect, since the assignment incrementing temp
will be executed without initializing temp
if x
is negative.
int temp; if (x > 0) temp = 1; temp = temp+1;
The requirement that a local variable be explicitly initialized before use also applies to reference variables. For instance, the following is not legal since obj
is used in the expression obj
==
null
without being initialized.
Object obj; if (obj == null) ...
A method can be specified as private
, public
, or protected
, by preceding the declaration with the appropriate key word:
public int length () ... private void setLength (int size) ...
If no scope modifier is specified, the method is restricted. See the section on scope modifiers of class members or section 14.6 of Niño-Hosch for details.
In an interface, a method specified without a scope modifier is public, not restricted.
A method specified as final
cannot be overridden or hidden in a subclass. A method is specified as final by prefixing the keyword final
:
final public void dispose () ...
A method specified as static
is a
class method. That is, the method is associated with the class itself rather than with an instance of the class. A static method cannot reference instance variables or non-static methods of the class.
static public void condition (boolean condition) ...
A method can be specified as abstract
in an abstract class or interface, in which case its body is replaced by a semicolon:
public abstract void get (int i);
In an interface, the key word abstract need not be written.
Scope, final, static, and abstract modifiers can be written in any order. For instance, the specifications
public abstract int size ();
and
abstract public int size ();
are equivalent.