8.4.3 Method Modifiers

MethodModifiers:
MethodModifier
MethodModifiers
MethodModifier
MethodModifier: one of
public protected private
abstract static final synchronized native

The access modifiers public, protected, and private are discussed in §6.6. A compile-time error occurs if the same modifier appears more than once in a method declaration, or if a method declaration has more than one of the access modifiers public, protected, and private. A compile-time error occurs if a method declaration that contains the keyword abstract also contains any one of the keywords private, static, final, native, or synchronized.

If two or more method modifiers appear in a method declaration, it is customary, though not required, that they appear in the order consistent with that shown above in the production for MethodModifier.

8.4.3.1 abstract Methods

An abstract method declaration introduces the method as a member, providing its signature (name and number and type of parameters), return type, and throws clause (if any), but does not provide an implementation. The declaration of an abstract method m must appear within an abstract class (call it A); otherwise a compile-time error results. Every subclass of A that is not abstract must provide an implementation for m, or a compile-time error occurs. More precisely, for every subclass C of the abstract class A, if C is not abstract, then there must be some class B such that all of the following are true:

If there is no such class B, then a compile-time error occurs.

It is a compile-time error for a private method to be declared abstract. It would be impossible for a subclass to implement a private abstract method, because private methods are not visible to subclasses; therefore such a method could never be used.

It is a compile-time error for a static method to be declared abstract.

It is a compile-time error for a final method to be declared abstract.

An abstract class can override an abstract method by providing another abstract method declaration. This can provide a place to put a documentation comment (§18), or to declare that the set of checked exceptions (§11.2) that can be thrown by that method, when it is implemented by its subclasses, is to be more limited. For example, consider this code:


class BufferEmpty extends Exception {
	BufferEmpty() { super(); }
	BufferEmpty(String s) { super(s); }
}

class BufferError extends Exception { BufferError() { super(); } BufferError(String s) { super(s); } }
public interface Buffer { char get() throws BufferEmpty, BufferError; }
public abstract class InfiniteBuffer implements Buffer { abstract char get() throws BufferError; }

The overriding declaration of method get in class InfiniteBuffer states that method get in any subclass of InfiniteBuffer never throws a BufferEmpty exception, putatively because it generates the data in the buffer, and thus can never run out of data.

An instance method that is not abstract can be overridden by an abstract method. For example, we can declare an abstract class Point that requires its subclasses to implement toString if they are to be complete, instantiable classes:


abstract class Point {
	int x, y;
	public abstract String toString();
}

This abstract declaration of toString overrides the non-abstract toString method of class Object (§20.1.2). (Class Object is the implicit direct superclass of class Point.) Adding the code:


class ColoredPoint extends Point {
	int color;
	public String toString() {
		return super.toString() + ": color " + color; // error
	}
}

results in a compile-time error because the invocation super.toString() refers to method toString in class Point, which is abstract and therefore cannot be invoked. Method toString of class Object can be made available to class ColoredPoint only if class Point explicitly makes it available through some other method, as in:


abstract class Point {
	int x, y;
	public abstract String toString();
	protected String objString() { return super.toString(); }
}

class ColoredPoint extends Point {
	int color;
	public String toString() {
		return objString() + ": color " + color;														// correct
	}
}

8.4.3.2 static Methods

A method that is declared static is called a class method. A class method is always invoked without reference to a particular object. An attempt to reference the current object using the keyword this or the keyword super in the body of a class method results in a compile time error. It is a compile-time error for a static method to be declared abstract.

A method that is not declared static is called an instance method, and sometimes called a non-static method). An instance method is always invoked with respect to an object, which becomes the current object to which the keywords this and super refer during execution of the method body.

8.4.3.3 final Methods

A method can be declared final to prevent subclasses from overriding or hiding it. It is a compile-time error to attempt to override or hide a final method.

A private method and all methods declared in a final class (§8.1.2.2) are implicitly final, because it is impossible to override them. It is permitted but not required for the declarations of such methods to redundantly include the final keyword.

It is a compile-time error for a final method to be declared abstract.

At run-time, a machine-code generator or optimizer can easily and safely "inline" the body of a final method, replacing an invocation of the method with the code in its body, as in the example:


final class Point {
	int x, y;
	void move(int dx, int dy) { x += dx; y += dy; }
}

class Test { public static void main(String[] args) { Point[] p = new Point[100]; for (int i = 0; i < p.length; i++) { p[i] = new Point(); p[i].move(i, p.length-1-i); } } }

Here, inlining the method move of class Point in method main would transform the for loop to the form:


		for (int i = 0; i < p.length; i++) {
			p[i] = new Point();
			Point pi = p[i];
			pi.x += i;
			pi.y += p.length-1-i;
		}

The loop might then be subject to further optimizations.

Such inlining cannot be done at compile time unless it can be guaranteed that Test and Point will always be recompiled together, so that whenever Point-and specifically its move method-changes, the code for Test.main will also be updated.

8.4.3.4 native Methods

A method that is native is implemented in platform-dependent code, typically written in another programming language such as C, C++, FORTRAN, or assembly language. The body of a native method is given as a semicolon only, indicating that the implementation is omitted, instead of a block.

A compile-time error occurs if a native method is declared abstract.

For example, the class RandomAccessFile of the standard package java.io might declare the following native methods:


package java.io;


public class RandomAccessFile
implements DataOutput, DataInput { . . . public native void open(String name, boolean writeable) throws IOException; public native int readBytes(byte[] b, int off, int len) throws IOException; public native void writeBytes(byte[] b, int off, int len) throws IOException; public native long getFilePointer() throws IOException; public native void seek(long pos) throws IOException; public native long length() throws IOException; public native void close() throws IOException; }

8.4.3.5 synchronized Methods

A synchronized method acquires a lock (§17.1) before it executes. For a class (static) method, the lock associated with the Class object (§20.3) for the method's class is used. For an instance method, the lock associated with this (the object for which the method was invoked) is used. These are the same locks that can be used by the synchronized statement (§14.17); thus, the code:


class Test {
	int count;
	synchronized void bump() { count++; }
	static int classCount;
	static synchronized void classBump() {
		classCount++;
	}
}

has exactly the same effect as:


class BumpTest {
	int count;
	void bump() {
		synchronized (this) {
			count++;
		}
	}
	static int classCount;
	static void classBump() {
		try {
			synchronized (Class.forName("BumpTest")) {
				classCount++;
			}
		} catch (ClassNotFoundException e) {
				...
		}
	}
}

The more elaborate example:


public class Box {

public synchronized Object get() { return contents; }
public synchronized boolean put(Object contents) { return false; return true; }
}

defines a class which is designed for concurrent use. Each instance of the class Box has an instance variable contents that can hold a reference to any object. You can put an object in a Box by invoking put, which returns false if the box is already full. You can get something out of a Box by invoking get, which returns a null reference if the box is empty.

If put and get were not synchronized, and two threads were executing methods for the same instance of Box at the same time, then the code could misbehave. It might, for example, lose track of an object because two invocations to put occurred at the same time.

See §17 for more discussion of threads and locks.