Declares a method, (possibly with a body), so that it can be referenced via a method reference, called, and overridden.
A method is a named group of statements that can be referred to and executed via a method invocation statement. A method declaration introduces a method to the program. The group of statements that are executed are called the method's implementation. A method declaration includes the method's name (and formally, it's signature), which can be used to refer to the method elsewhere, though it is not always necessary for a declaration to include the implementation.
A method declaration consists of:
1 | modifier-list return-type name
( parameter-list
) array-dims
throws throw-list
body |
2 | modifier-list <
type-parameter-list >
annotation-list return-type name
( parameter-list
) array-dims
throws throw-list
body |
where...
modifier-list | is a possibly empty set of keywords and annotations that can
contain:
|
return-type | is a type denoting what type of value, if any,
the method returns. The type may have its own annotations. If void is specified as the method's return type,
the method returns no value.
|
name | is an identifier that names the method. |
parameter-list | is a comma-separated list of parameters, the first of which may be a receiver parameter and the last of which may be a variable-arity (var-args) parameter. |
parameter | is made up of three components, in order:
|
array-dims | is any number of consecutive pairs of [] strings,
optionally separated (e.g. [][] [] )
and/or split (e.g. [][][ ] ) by
whitespace. Each [] pair may be annotated with an
annotation that targets type use. (Such annotations target the
respective dimension of the array that the []
represents.)
|
throw-list | A list of exception types denoting what the method is permitted to
throw .
|
body | is either a semicolon ; or a block statement.
|
type-parameter-list | is a comma-separated list of type parameters. Each type parameter
is an identifier. The identifier may be followed by the keyword extends
and then a set of types to establish an upper bound. The set of types
upper-bounding the parameter must be delimitted by the &
token, and all but the first must be interface types.
|
such that...
@
character, present in any annotation, may have any amount of
whitespace before or after it. If not separated by
whitespace, two consecutive keywords or identifiers will be parsed as
a single identifier token. For example, finalstatic
is
parsed as a single identifier. Annotations, however, never need
whitespace immediately before them, even if a keyword precedes them,
since the @
symbol indicates to Java the end of the
previous token and the beginning of an annotation: public@SuppressWarnings("all")void test() { }
A whitespace is not needed, but is allowed, before or after the @
.
Since @SuppressWarnings
also ends in a closing
parenthesis, whitespace is not required after it either.
For annotations that end in an identifier, e.g. @Override
,
whitespace must come after them if an identifier or keyword
follows, to separate the annotation's name from the succeeding
token:
public @Overridevoid test() { } // Does not compile: Unknown annotation "Overridevoid" and method has no return type.
abstract
or native
keywords,
or
default
keyword,
;
for its body. Otherwise, the method must have a
block statement as its body.
1 A normal method declaration.
2 A generic method declaration, with type parameters.
A method can be declared directly within a class or interface, or within an enum's body declaration (after the constant list). Method declarations can override methods inherited from a parent type.
Annotations can be placed in two distinct places in a method declaration, either in the modifier-list, intermixed with keyword modifiers, or, if type parameters are included, after the type-parameter-list and immediately before the return type.
Annotations included in the method declaration's modifier list will apply to:
If the annotation targets both type-uses and methods, the annotation applies to both the return type and the method. If the annotation does not target either, a compile error occurs.
If the method declares type parameters and the annotation is placed after the type-parameter-list, the annotation must target type-uses and the annotation applies only to the return type.
Keyword modifiers are any of public
, protected
,
private
, static
, final
, synchronized
,
strictfp
, default
, abstract
, and
native
.
Access modifiers affect the accessibility of a method, which determines from where the method may be invoked (via a method invocation statement), referenced (via a method reference), and overridden. Without any access modifier keywords, a method's accessibility is package-protected, meaning that it can be accessed by code within the same package as where it is declared. If the method has the access modifier:
public
, it can be accessed from anywhere,protected
, it can be accessed from within the
top-level class it is declared in and within all of the subclasses of
the immediate, surrounding class it is declared in,private
, it can be accessed from within the top-level
class it is declared in only.A method can have at most one access modifier keyword, otherwise a compile error occurs.
final |
The final modifier, when used on a method, disables
the ability for any subclasses to override the method. Despite having
no effect, it may be used on private methods. It cannot
be used on declarations directly in an interface , nor on
any method declared abstract or native .
|
static |
The static modifier causes a method to be a class
method rather than an instance method. Resultingly, the
method may not refer to this , super , or any
other instance methods or instance fields without qualifying such
references with an explicit instance. This is in contrast to instance
methods, which, in their bodies, may access instance-specific members
without explicit qualification. Instance methods additionally may
refer to this and super .
|
strictfp |
The strictfp modifier causes all floating point
computations written in the body of a so modified method to
consistently operate on the normal float/double value set (depending
on expression type) across implementations. See strictfp for details.
|
synchronized |
A synchronized method synchronizes on:
before it begins executing. The lock is released after the method completes. |
abstract |
Abstract method declarations cannot provide a block statement
body. abstract methods cannot be static , final ,
native , private , synchronized ,
nor strictfp . Any type containing an abstract
method must also, itself, be abstract (interface s
are always inherently abstract ). abstract
types cannot be instantiated directly; only concrete sub-types can be
instantiated directly.
Any non- |
native |
The native modifier is used to declare a method whose
implementation is provided through some other language, typically C
or C++. Such methods' implementations are connected to the program
through the Java Native Interface.
The |
default |
The default modifier allows a method declared in an interface
to include an implementation. default methods are not
abstract. A method declaration containing default must
include a block as its body.
|
A method's return type determines the type of the method invocation
that invokes it. Specifically, any method invocation (e.g. doSomething()
)
will be an expression whose type is the same as the return type of the
method it invokes. For example, the following method's return type is int
:
int getRandomNumber() { return (int) (Math.random() * 10); }// Method declaration
so the expression that invokes it (getRandomNumber()
) is
an int
expression, that can be assigned to an int
variable:
int result = getRandomNumber();
If a method has a return type other than void
, it is
required to execute at least 1 return
statement at the end of every possible path of normal
execution that does not throw
a value.
finally
block, a method may execute
multiple return
statements. In such a case, the last abrupt
exit from execution of the method takes precedence, i.e., the
final return
or throw
that is executed is
what the method completes with: int test() {
try {
return 5;
} finally {
return 10;
}
}
A call to test()
returns 10
,
even though the return 5
does in fact get executed.
If a path of execution does not end, (e.g., if you have an
infinite while
or for
loop), then that path
is not required to return a value: int neverEndingMethod() {
while(true);
// No return statement required.
// In fact, writing a return statement here is a compile time error, since the return statement would be unreachable.
}
A method's parameters are a list of inputs that must be provided upon a call to the method. The variables declared in the parameter list are accessible throughout the body of the method and are given values when the method is called.
A method may be overloaded if there are two or more method declarations with its same name, but different signatures. A method's signature includes the ordered list of types of its parameters, and methods with different ordered lists of parameter types are considered different when resolving overloads to execute a method invocation statement. The presence of a receiver parameter has no effect on overload resolution, since method invocation statements cannot provide an argument for that parameter explicitly.
Overload resolution happens statically, so the type of the expression used as an argument in the method invocation is what determines the method to be called.
The receiver parameter is a parameter whose type is that of the class
the instance method belongs to and whose parameter name is the keyword
this
. It may be annotated as any other parameter, but it
otherwise has no effect on the code; it does not change the method's
signature or affect overloading or invoking. The this
keyword is innately accessible in instance methods without needing to
be declared.
The final parameter in a parameter list can optionally be a variable-arity
parameter, by succeeding its type with the ...
token. Such
a parameter allows any number of arguments, of the parameter's type, to
be provided in a method invocation statement as the final arguments in
the statement's argument list. An array is automatically created from
the arguments provided, and in the body of the method, the varargs
parameter is useable in the exact same way as an array.
Generic type parameters declared in the type-parameter-list can be referenced throughout the remainder of the method, including in the method's parameter-list, body, and even the method's throw-list. Any type parameters declared in the type-parameter-list may also be referenced anywhere else within the type-parameter-list. This allows for recursive type parameters.
When used as a type in the parameter-list, a type parameter's upper bound is used for overload resolution.
<T> void test(T input) { }
// void test(Object input) { } // Compiler error, generic method test(T) conflicts with this method.
This conflict can be avoided in cases where it is possible to upper-bound the parameter:
<T extends Dog> void test(T input) { }
void test(Object input) { } // Does not conflict; test(T) cannot be called with ANY object.
However, the upper bound, if provided explicitly, can still conflict with other methods:
<T extends Dog> void test(T input) { }
// void test(Dog input) { } // Conflicts with test(T), since T's upper bound is Dog.
Type parameters may also be used in the throw-list
to declare a method that throws a generic exception. This is only
possible if the type parameter being used is bounded so that it is
guaranteeably a type that can be thrown (Throwable
or a
subclass thereof).
Type parameters can be used as any other type within the body of the method they are declared in.
The array-dims is a legacy
syntactical element that allows the pairs of brackets ([]
),
used to declare a dimension of an array type for the return type of the
method, to be placed after the parameter list of a method declaration.
It can consist of any number of pairs of brackets. The array dims
specified in this location are applied to the return type as if
appended to the return type.
throws
Clause
The throws
clause declares what exceptions a method can
throw. Any checked exceptions that are thrown in the method body under
any path of execution must be assignable to at least one of the types
listed in the throws
clause, otherwise, the code will not
compile. The same exception may be listed in the throw-list more than once. Additionally, RuntimeException
s
and Error
s, (both of which are the root unchecked types),
and their subtypes, do not need to be listed, but may be.
The rules governing required types in the throw-list
apply only to checked exceptions that the method may abruptly terminate
due to the throwing of. Because of this, if a checked exception is
thrown and caught within the method's body, the exception does not need
to be listed in the throws
clause.
static
Method Restrictions
Example of inability to reference instance properties from a static
method body:
class Dog {
static Dog someDogInstance = new Dog();
int age; // Instance field
static void grow() {
// age++; // Not allowed; no Dog instance to increment the age of.
Dog x = new Dog(); // Make a Dog instance.
x.age++;
someDogInstance.age++;
}
void growThisDog() {
age++; // Instance methods are called with an instance, and that instance is used implicitly here in the method body.
this.age++; // Equivalent to age++
someDogInstance.age++; // Instance methods can still access static data directly, but not vice versa.
}
}
abstract
Methods
An expression whose type is an abstract type can still be used to invoke abstract methods in that abstract type, though the implementation that gets executed depends on the concrete type of the value the expression evaluates to:
abstract class Animal {
abstract void makeSound();
}
class Dog extends Animal {
void makeSound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
void makeSound() {
System.out.println("Meow");
}
}
class Test {
void test() {
Animal a = Math.random() < .5 ? new Dog() : new Cat();
a.makeSound(); // May print "Meow" or "Woof"
}
}
abstract
methods must be implemented by any concrete
subclasses that inherit them:
abstract class A {
abstract void test();
}
class Sub extends A {
// Sub must implement test since Sub is not an abstract class, test is abstract, and test was inherited by Sub from A.
@Override void test() {
System.out.println("Sub");
}
}
class Test {
static A getNewA() {
return new Sub();
}
void test() {
A obj = getNewA();
obj.test();
}
}
Example of how return type affects method invocation statements:
public class Test {
static int x() {
System.out.println("x");
return 5;
}
static void y() {
System.out.println("y");
}
public static void main(String[] args) {
int a = x();
// int b = y(); // Not allowed; y does not return anything.
// String c = x(); // Not allowed; x does not return a String.
System.out.println(a); // Prints 5.
}
}
Example of return type imposing return requirements on method body:
int test() {
if (Math.rand() > 0.5) {
return 4;
}
// Function may reach this point without returning a value; this is not allowed.
}
Method parameters determine which method gets invoked by a method call:
void test(int x) {
System.out.println("Test was called with the number: " + x);
}
void test(String x) {
System.out.println("Test was called with: " + x);
}
void runTest() {
test("abc");
}
The output of invoking runTest()
is:
Test was called with: abc
Changing the type, but not the value, of an expression used as an argument for a method, can cause a different overload to be invoked:
void test(String x) {
System.out.println("STRING VERSION CALLED");
}
void test(Object x) {
System.out.println("OBJECT VERSION CALLED");
}
void runTest() {
test((Object) "abc"); // Provide a String, but casted to type "Object."
}
In the above example, test(Object)
will be invoked with a
String
as its argument, leading to the following output:
OBJECT VERSION CALLED
Any number of arguments can be provided in place of a varargs parameter:
void test(String... args) {
System.out.println("TEST was called with: " + args.length + " argument(s).");
}
void runTest() {
test(); // Provide no arguments.
test("a", "b", "c"); // Provide three arguments.
test("a"); // Provide one argument.
}
A call to runTest()
prints:
TEST was called with: 0 argument(s).
TEST was called with: 3 argument(s).
TEST was called with: 1 argument(s).
A var-args parameter can also be unambiguously provided an array argument:
test(new String[] {"a", "b", "c"});
Such method call prints:
TEST was called with: 3 argument(s).
throws
Clause
Use of a type parameter in a throws
clause:
<T extends Exception> void doSomethingThenThrow(T exception) throws T {
if (Math.random() < 0.5)
throw exception;
}
This can allow callers to have to handle checked exceptions only when calling the method with checked exceptions:
doSomethingThenThrow(new RuntimeException()); // This invocation effectively declares "throws RuntimeException", so no need to wrap in try-catch.
try {
doSomethingThenThrow(new Exception()); // This invocation effectively declares "throws Exception"
} catch (Exception e) {
}
Methods declared with array-dims
// Two total array dims, both after parameter list.
int x()[][] {
return new int[1][1];
}
// Three total array dims: two in return type, one after parameter-list.
int[][] x()[] {
return new int[1][1][1];
}
throw
ing Without throws
Clause
A throws
clause is only necessary if the method may
terminate with a checked exception:
void riskyMethod() throws Exception {
if (Math.random() < .1)
throw new Exception("Throwing an exception!");
}
void test() {
try {
riskyMethod();
} catch (Exception e) {
// Ignore the exception!
}
}
The above example compiles since the body of the method test()
does not have any code deemed to potentially complete abruptly due to
a checked exception. If the try
-catch
were
not used to wrap the call to riskyMethod()
, Exception
or its supertype, Throwable
, would need to be listed in
the throw-list.
final
modifier never has an effect on private
methods, since such instance methods are not dynamically bound, and
since such static
methods cannot be overridden anyway.
Type parameters can be used to throw a checked exception in an
unchecked fashion by allowing the type of the expression provided to
a throw
statement to be an unchecked exception while the
value itself remains a checked exception. This can be done by casting
a checked exception to a generic type parameter, and then throwing
the result:
public static @SuppressWarnings("unchecked") <E extends Throwable> void throwUnchecked(Throwable t) throws E {
throw (E) t;
}
Any call to the method that does not explicitly provide type
arguments will infer E
to be RuntimeException
,
however, the exception provided to the method will still be thrown,
even if it is a Throwable
instance.
The cast to E
does not fail, since E
is
upper-bounded by Throwable
and the argument to the
method is any Throwable
instance.
The compiler prefers method overloads that are closer to the type of the expression being used to invoke a method. A call can be ambiguous if two or more overloads can plausibly be called by a single method invocation statement. There are many cases where this can happen, most often with a var-args overload or a null argument, but sometimes with overloads whose problematic parameters are sibling types in a type hierarchy:
void varargs(int... x) { }
void varargs(Integer... x) { }
Calling varargs(1)
will fail. The caller is required to
provide an array explicitly to pick which method is desired, such as
through: varargs(new int[] {1})
.
Another example, involving a class hierarchy:
// Wrapper class
public class AmbiguityTest {
interface X { }
interface Y { }
class Z implements X, Y { }
static void m(X x) { }
static void m(Y y) { }
public static void main(String[] args) {
// m(new Z()); // Ambiguous method call; expression of type Z is equidistant from both X and Y. (X & Y are the types of the parameters of the two method overloads of m.)
}
}
Such can be resolved using a cast, e.g. m((X) new Z())
will invoke m(X)
. If a method overload is introduced to
explicitly accept a Z
parameter, such overload will be
used instead, since the method invocation has an argument of type Z
.
The null
literal can often lead to ambiguity, because it
can directly exist as an expression of any reference type:
// Wrapper class
public class AmbiguityTest {
interface X { }
interface Y { }
class Z implements X, Y { }
static void m(X x) { }
static void m(Y y) { }
public static void main(String[] args) {
// m(null); // Ambiguous method call with null literal.
}
}
A cast can clarify the type of the expression null
in
the method call and resolve the ambiguity in this case as well.
Adding a more specific overload in the class hierarchy will cause the
method invocation to prefer that as well:
// Wrapper class
public class AmbiguityTest {
interface X { }
interface Y { }
class Z implements X, Y { }
static void m(X x) { }
static void m(Y y) { }
static void m(Z z) { }
public static void main(String[] args) {
m(null); // Calls m(Z)
}
}
However, if there is no overload that is a subtype of other overloads, the ambiguity will persist:
// Wrapper class
public class AmbiguityTest {
static void m(String x) { }
static void m(Number y) { }
public static void main(String[] args) {
// m(null); // Ambiguous
}
}