2.6. Methods
A
method is a named sequence of Java statements
that can be invoked by other Java code. When a method is invoked, it
is passed zero or more values known as
arguments . The method performs some computations
and, optionally, returns a value. As described in Section 2.4 earlier in this chapter, a
method invocation is an expression that is evaluated by the Java
interpreter. Because method invocations can have side effects,
however, they can also be used as expression statements. This section
does not discuss method invocation, but instead describes how to
define methods.
2.6.1. Defining Methods
You
already know how to define the body of a method; it is simply an
arbitrary sequence of statements enclosed within curly braces. What
is more interesting about a method is its
signature .[5] The signature specifies the following:
[5] In the Java Language
Specification, the term "signature"
has a technical meaning that is slightly different than that used
here. This book uses a less formal definition of method
signature.
- The name of the method
- The number, order, type, and name of the parameters used by the method
- The type of the value returned by the method
- The checked exceptions that the method can throw (the signature may
also list unchecked exceptions, but these are not required) - Various method modifiers that provide additional information about
the method
A method signature defines everything you need to know about a method
before calling it. It is the method
specification and defines the API for the
method. The reference section of this book is essentially a list of
method signatures for all publicly accessible methods of all publicly
accessible classes of the Java platform. In order to use the
reference section of this book, you need to know how to read a method
signature. And, in order to write Java programs, you need to know how
to define your own methods, each of which begins with a method
signature.A method signature looks like this:
modifiers type name ( paramlist ) [ throws exceptions ]The signature (the method specification) is followed by the method
body (the method implementation), which is simply a sequence of Java
statements enclosed in curly braces. If the method is
abstract (see Chapter 3),
the implementation is omitted, and the method body is replaced with a
single semicolon.
In Java 5.0 and later, the signature
of a generic method may
also include type variable declarations. Generic methods and type
variables are discussed in Chapter 4.Here are some example method definitions, which begin with the
signature and are followed by the method
body:
// This method is passed an array of strings and has no return value.modifiers
// All Java programs have a main entry point with this name and signature.
public static void main(String[] args) {
if (args.length > 0) System.out.println("Hello " + args[0]);
else System.out.println("Hello world");
}
// This method is passed two double arguments and returns a double.
static double distanceFromOrigin(double x, double y) {
return Math.sqrt(x*x + y*y);
}
// This method is abstract which means it has no body.
// Note that it may throw exceptions when invoked.
protected abstract String readText(File f, String encoding)
throws FileNotFoundException, UnsupportedEncodingException;
is zero or more special modifier keywords, separated from each other
by spaces. A method might be declared with the
public and static modifiers,
for example. The allowed modifiers and their meanings are described
in the next section.The
type in a
method signature specifies the return type of the method. If the
method does not return a value, type must
be void. If a method is declared with a
non-void return type, it must include a
return statement that returns a value of (or
convertible to) the declared type.A
constructor
is a special kind of method used to initialize newly created objects.
As we'll see in Chapter 3,
constructors are defined just like methods, except that their
signatures do not include this type
specification.The
name of a method follows the specification
of its modifiers and type. Method names, like variable names, are
Java identifiers and, like all Java identifiers, may contain letters
in any language represented by the Unicode character set. It is
legal, and often quite useful, to define more than one method with
the same name, as long as each version of the method has a different
parameter list. Defining multiple methods with the same name is
called method
overloading
.
The System.out.println( ) method
we've seen so much of is an overloaded method. One
method by this name prints a string and other methods by the same
name print the values of the various primitive types. The Java
compiler decides which method to call based on the type of the
argument passed to the method.
When
you are defining a method, the name of the method is always followed
by the method's parameter list, which must be
enclosed in parentheses. The parameter list defines zero or more
arguments that are passed to the method. The parameter
specifications, if there are any, each consist of a type and a name
and are separated from each other by commas (if there are multiple
parameters). When a method is invoked, the argument values it is
passed must match the number, type, and order of the parameters
specified in this method signature line. The values passed need not
have exactly the same type as specified in the signature, but they
must be convertible to those types without casting.
C and C++ programmers should note that
when a Java method expects no arguments, its parameter list is simply
( ), not (void).In Java 5.0 and later, it is possible to define and invoke methods
that accept a variable number of arguments, using a syntax known
colloquially as varargs . Varargs are covered in
detail later in this chapter. The final part of a method signature
is the tHRows clause, which is used to list the
checked exceptions that a method can throw.
Checked exceptions are a category of exception classes that must be
listed in the tHRows clauses of methods that can
throw them. If a method uses the throw statement
to throw a checked exception, or if it calls some other method that
throws a checked exception and does not catch or handle that
exception, the method must declare that it can throw that exception.
If a method can throw one or more checked exceptions, it specifies
this by placing the tHRows keyword after the
argument list and following it by the name of the exception class or
classes it can throw. If a method does not throw any exceptions, it
does not use the throws keyword. If a method
throws more than one type of exception, separate the names of the
exception classes from each other with commas. More on this in
a
bit.
2.6.2. Method Modifiers
The
modifiers
of a method consist of zero or more modifier keywords such as
public, static, or
abstract. Here is a list of allowed modifiers and
their meanings. Note that in Java 5.0 and later,
annotations, such as
@Override, @Deprecated, and
@SuppressWarnings, are treated as modifiers and
may be mixed in with the modifier list. Anyone can define new
annotation types, so it is not possible to list all possible method
annotations. See Chapter 4 for more on
annotations.
- abstract
An abstract method
is a specification without an implementation. The curly braces and
Java statements that would normally comprise the body of the method
are replaced with a single semicolon. A class that includes an
abstract method must itself be declared
abstract. Such a class is incomplete and cannot be
instantiated (see Chapter 3).- final
A final method
may not be overridden or hidden by a subclass, which makes it
amenable to compiler optimizations that are not possible for regular
methods. All private methods are implicitly
final, as are all methods of any class that is
declared final.- native
The native modifier specifies that the method
implementation is written in some
"native" language such as C and is
provided externally to the Java program. Like
abstract methods, native
methods have no body: the curly braces are replaced with a semicolon.When Java was first released, native methods were
sometimes used for efficiency reasons. That is almost never necessary
today. Instead, native methods are used to interface Java code to
existing libraries written in C or C++. Native methods are implicitly
platform-dependent, and the procedure for linking the implementation
with the Java class that declares the method is dependent on the
implementation of the Java virtual machine. Native methods are not
covered in this book.- public, protected, private
These access modifiers specify whether and where a method can be used
outside of the class that defines it. These very important modifiers
are explained in Chapter 3.- static
A method declared static
is a class
method associated with the class itself rather than with
an instance of the class. This is explained in detail in Chapter 3.- strictfp
A method declared
strictfp
must perform floating-point arithmetic using 32- or 64-bit floating
point formats strictly and may not take advantage of any extended
exponent bits available to the platform's
floating-point hardware. The "fp"
in this awkwardly named, rarely used modifier stands for
"floating point."- synchronized
The synchronized
modifier makes a method threadsafe.
Before a thread can invoke a synchronized method,
it must obtain a
lock on the method's
class (for static methods) or on the relevant
instance of the class (for non-static methods).
This prevents two threads from executing the method at the same time.The synchronized modifier is an implementation
detail (because methods can make themselves threadsafe in other ways)
and is not formally part of the method specification or API. Good
documentation specifies explicitly whether a method is threadsafe;
you should not rely on the presence or absence of the
synchronized keyword when working with
multithreaded programs.
2.6.3. Declaring Checked Exceptions
In
the
discussion of the
throw statement, we said that exceptions are
Throwable objects and that exceptions fall into
two main categories, specified by the Error and
Exception subclasses. In addition to making a
distinction between Error and
Exception classes, the Java exception-handling
scheme also distinguishes between checked and unchecked exceptions.
Any exception object that is an
Error is unchecked. Any exception object
that is an Exception is checked, unless it is a
subclass of
java.lang.RuntimeException
, in which case it is
unchecked.
(RuntimeException is a subclass of
Exception.)The distinction between checked and unchecked exceptions has to do
with the circumstances under which the exceptions are thrown.
Practically any method can throw an unchecked exception at
essentially any time. There is no way to predict an
OutOfMemoryError,
for example, and any method that uses objects or arrays can throw a
NullPointerException
if it is passed an invalid null argument. Checked
exceptions, on the other hand, arise only in specific, well-defined
circumstances. If you try to read data from a file, for example, you
must at least consider the possibility that a
FileNotFoundException
will be thrown if the specified file cannot be found.Java has different rules for working with checked and unchecked
exceptions. If you write a method that throws a checked exception,
you must use a tHRows clause to declare the
exception in the method signature. The reason these types of
exceptions are called checked exceptions is that the Java compiler
checks to make sure you have declared them in method signatures and
produces a compilation error if you have not.Even if you never throw an exception yourself, sometimes you must use
a throws clause to declare an exception. If your
method calls a method that can throw a checked exception, you must
either include exception-handling code to handle that exception or
use tHRows to declare that your method can also
throw that exception. For example, the following method reads the
first line of text from a named file. It uses methods that can throw
various types of java.io.IOException objects, so
it declares this fact with a tHRows clause:
public static String readFirstLine(String filename) throws IOException {How do you know if the method you are calling can throw a checked
BufferedReader in = new BufferedReader(new FileReader(filename));
String firstline = in.readLine( );
in.close( );
return firstline;
}
exception? You can look at its method signature to find out. Or,
failing that, the Java compiler will tell you (by reporting a
compilation error) if you've called a method whose
exceptions you must handle or declare.
2.6.4. Variable-Length Argument Lists
In Java 5.0 and later, methods may be declared to accept, and may be
invoked with, variable numbers of arguments. Such methods are
commonly known as
varargs methods. The new
System.out.printf( ) method as well as the related
format( ) methods of
String
and
java.util.Formatter use varargs. The similar, but
unrelated, format( ) method of
java.text.MessageFormat has been converted to use varargs as have
a number of important methods from the Reflection API of
java.lang.reflect.A variable-length argument list is declared by following the type of
the last argument to the method with an
ellipsis (...),
indicating that this last argument can be repeated zero or more
times. For example:
public static int max(int first, int... rest) {This max( ) method is declared with two arguments.
int max = first;
for(int i: rest) {
if (i > max) max = i;
}
return max;
}
The first is just a regular int value. The second,
however may be repeated zero or more times. All of the following are
legal invocations of max( ):
max(0)As you can tell from the
max(1, 2)
max(16, 8, 4, 2, 1)
for/in statement in the body of max(
), the second argument is treated as an array of
int values. Varargs methods are handled purely by
the compiler. To the Java interpreter, the max( )
method is indistinguishable from this one:
public static int max(int first, int[] rest) { /* body omitted */ }To convert a varargs signature to the
"real" signature, simply replace
... with [ ]. Remember that
only one ellipsis can appear in a parameter list, and it may only
appear on the last parameter in the list.Since varargs methods are compiled into methods that expect an array
of arguments, invocations of those methods are compiled to include
code that creates and initializes such an array. So the call
max(1,2,3) is compiled to this:
max(1, new int[] { 2, 3 })If you already have method arguments stored in an array, it is
perfectly legal for you to pass them to the method that way, instead
of writing them out individually. You can treat any
... argument as if it were declared as an array.
The converse is not true, however: you can only use varargs method
invocation syntax when the method is actually declared as a varargs
method using an ellipsis.Varargs methods interact particularly well with the new
autoboxing feature of Java 5.0 (see
Section 2.9.7 later in this
chapter). A method that has an
Object... variable length argument
list can take arguments of any reference type because all objects and
arrays are subclasses of Object. Furthermore,
autoboxing allows you to invoke the method using primitive values as
well: the compiler boxes these up into wrapper objects as it builds
the Object[ ] that is the true argument to the
method. The printf( ) and format(
) methods mentioned at the beginning of this section are
all declared with an Object... parameter.One quirk arises with methods with an
Object... parameter. It does not arise very
often in practice, but studying the quirk will solidify your
understanding of varargs. Recall that varargs methods can be invoked
with an argument of array type or any number of arguments of the
element type. When a method is declared with an
Object... argument, you can pass an
Object[ ] of arguments, or zero or more individual
Object arguments. But every Object[
] is also an Object. What do you do if
you want to pass an Object[ ] as the single object
argument to the method? Consider the following code that uses the
printf( ) method:
import static java.lang.System.out; // out now refers to System.out
// Here we invoke the varargs method with individual Object arguments.
// Note the use of autoboxing to convert primitives to wrapper objects
out.printf("%d %d %d\n", 1, 2, 3);
// This line does the same thing but passes the arguments in an array
// that has already been created:
Object[] args = new Object[] { 1, 2, 3 };
out.printf("%d %d %d\n", args);
// Now consider the following Object[], which we wish to pass
// as a single argument, not as an array of two arguments.
Object[] arg = new Object[] { "hello", "world" };
// These two lines do the same thing: print "hello". Not what we want.
out.printf("%s\n", "hello", "world");
out.printf("%s\n", arg);
// If we want arg to be treated as a single Object argument, we need to
// pass it as an the element of an array. Here's one way:
out.printf("%s\n", new Object[] { arg });
// An easier way is to convince the compiler to create the array itself.
// We use a cast to say that arg is a single Object argument, not an array:
out.printf("%s\n", (Object)arg);
2.6.5. Covariant Return Types
As part of the addition of
generic
types, Java 5.0 now also supports covariant
returns . This means that an overriding method may narrow
the return type of the method it overrides.[6] The
following example makes this clearer:
[6] Method
overriding is not the same as method overloading
discussed earlier in this section. Method overriding involves
subclassing and is covered in Chapter 3. If you
are not already familiar with these concepts, you should skip this
section for now and return to it later.
class Point2D { int x, y; }This code defines four classes: a two-dimensional point, a
class Point3D extends Point2D { int z; }
class Event2D {
public Point2D getLocation( ) { return new Point2D( ); }
}
class Event3D extends Event2D {
@Override public Point3D getLocation( ) { return new Point3D( ); }
}
three-dimensional point, and event objects that represent an event in
two-dimensional space and in three-dimensional space. Each event
class has a getLocation(
)
method. The Event2D method returns a
Point2D object. Event3D
subclasses Event2D and overrides
getLocation( ). Its version of the method sensibly
returns a Point3D. Because every
Point3D object is also a
Point2D object, this is a perfectly reasonable
thing to do. It simply wasn't allowed prior to Java
5.0.In Java 1.4 and earlier, the return type of an
overriding method must be identical to
the type of the method it overrides. In order to compile under Java
1.4, the Event3D.getLocation( ) method would have
to be modified to have a return type of Point2D.
It could still return a Point3D object, of course,
but the caller would have to cast the return value from
Point2D to Point3D.The @Override in the code example is an
annotation ,
covered in Chapter 4. This one is a
compile-time assertion that the method overrides something. The
compiler would have produced a compilation error if the assertion
failed.