Team LiB
Previous Section Next Section

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.
// 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;

modifiers 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 {
     BufferedReader in = new BufferedReader(new FileReader(filename));
     String firstline = in.readLine(  );
     in.close(  );
     return firstline;
}

How do you know if the method you are calling can throw a checked 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) {
     int max = first;
     for(int i: rest) {
         if (i > max) max = i;
     }
     return max;
}

This max( ) method is declared with two arguments. 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)
max(1, 2)
max(16, 8, 4, 2, 1)

As you can tell from the 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; }
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(  ); }
}

This code defines four classes: a two-dimensional point, a 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.

    Team LiB
    Previous Section Next Section