The following examples illustrate some (possibly subtle) points about method declarations.
class Point {
int x = 0, y = 0;
void move(int dx, int dy) { x += dx; y += dy; }
}
class SlowPoint extends Point {
int xLimit, yLimit;
void move(int dx, int dy) { super.move(limit(dx, xLimit), limit(dy, yLimit)); }
static int limit(int d, int limit) { return d > limit ? limit : d < -limit ? -limit : d; }
}
the class SlowPoint
overrides the declarations of method move
of class Point
with its own move
method, which limits the distance that the point can move on
each invocation of the method. When the move
method is invoked for an instance
of class SlowPoint
, the overriding definition in class SlowPoint
will always be
called, even if the reference to the SlowPoint
object is taken from a variable
whose type is Point
.
class Point {
int x = 0, y = 0;
void move(int dx, int dy) { x += dx; y += dy; }
int color;
}
class RealPoint extends Point {
float x = 0.0f, y = 0.0f;
void move(int dx, int dy) { move((float)dx, (float)dy); }
void move(float dx, float dy) { x += dx; y += dy; }
}
the class RealPoint
hides the declarations of the int
instance variables x
and y
of class Point
with its own float
instance variables x
and y
, and overrides the
method move
of class Point
with its own move
method. It also overloads the name
move
with another method with a different signature (§8.4.2).
In this example, the members of the class RealPoint
include the instance variable color
inherited from the class Point
, the float
instance variables x
and y
declared in RealPoint
, and the two move
methods declared in RealPoint
.
Which of these overloaded move
methods of class RealPoint
will be chosen for any particular method invocation will be determined at compile time by the overloading resolution procedure described in §15.11.
This example is an extended variation of that in the preceding section:
class Point {
int x = 0, y = 0, color;
void move(int dx, int dy) { x += dx; y += dy; }
int getX() { return x; }
int getY() { return y; }
}
class RealPoint extends Point {
float x = 0.0f, y = 0.0f;
void move(int dx, int dy) { move((float)dx, (float)dy); }
void move(float dx, float dy) { x += dx; y += dy; }
float getX() { return x; }
float getY() { return y; }
}
Here the class Point
provides methods getX
and getY
that return the values of its
fields x
and y
; the class RealPoint
then overrides these methods by declaring
methods with the same signature. The result is two errors at compile time, one for
each method, because the return types do not match; the methods in class Point
return values of type int
, but the wanna-be overriding methods in class
RealPoint
return values of type float
.
This example corrects the errors of the example in the preceding section:
class Point {
int x = 0, y = 0;
void move(int dx, int dy) { x += dx; y += dy; }
int getX() { return x; }
int getY() { return y; }
int color;
}
class RealPoint extends Point {
float x = 0.0f, y = 0.0f;
void move(int dx, int dy) { move((float)dx, (float)dy); }
void move(float dx, float dy) { x += dx; y += dy; }
int getX() { return (int)Math.floor(x); }
int getY() { return (int)Math.floor(y); }
}
Here the overriding methods getX
and getY
in class RealPoint
have the same
return types as the methods of class Point
that they override, so this code can be
successfully compiled.
Consider, then, this test program:
class Test { public static void main(String[] args) { RealPoint rp = new RealPoint(); Point p = rp; rp.move(1.71828f, 4.14159f); p.move(1, -1); show(p.x, p.y); show(rp.x, rp.y); show(p.getX(), p.getY()); show(rp.getX(), rp.getY()); }
static void show(int x, int y) { System.out.println("(" + x + ", " + y + ")"); }
static void show(float x, float y) { System.out.println("(" + x + ", " + y + ")"); }
}
The output from this program is:
(0, 0) (2.7182798, 3.14159) (2, 3) (2, 3)
The first line of output illustrates the fact that an instance of RealPoint
actually contains the two integer fields declared in class Point
; it is just that their names are hidden from code that occurs within the declaration of class RealPoint
(and those of any subclasses it might have). When a reference to an instance of class RealPoint
in a variable of type Point
is used to access the field x
, the integer field x
declared in class Point
is accessed. The fact that its value is zero indicates that the method invocation p.move(1,
-1)
did not invoke the method move
of class Point
; instead, it invoked the overriding method move
of class RealPoint
.
The second line of output shows that the field access rp.x
refers to the field x
declared in class RealPoint
. This field is of type float
, and this second line of output accordingly displays floating-point values. Incidentally, this also illustrates the fact that the method name show
is overloaded; the types of the arguments in the method invocation dictate which of the two definitions will be invoked.
The last two lines of output show that the method invocations p.getX()
and rp.getX()
each invoke the getX
method declared in class RealPoint
. Indeed, there is no way to invoke the getX
method of class Point
for an instance of class RealPoint
from outside the body of RealPoint
, no matter what the type of the variable we may use to hold the reference to the object. Thus, we see that fields and methods behave differently: hiding is different from overriding.
A hidden class (static
) method can be invoked by using a reference whose type
is the class that actually contains the declaration of the method. In this respect,
hiding of static methods is different from overriding of instance methods. The
example:
class Super { static String greeting() { return "Goodnight"; } String name() { return "Richard"; } }
class Sub extends Super { static String greeting() { return "Hello"; } String name() { return "Dick"; } }
class Test { public static void main(String[] args) { Super s = new Sub(); System.out.println(s.greeting() + ", " + s.name()); } }
Goodnight, Dick
because the invocation of greeting
uses the type of s
, namely Super
, to figure
out, at compile time, which class method to invoke, whereas the invocation of
name
uses the class of s
, namely Sub
, to figure out, at run time, which instance
method to invoke.
Overriding makes it easy for subclasses to extend the behavior of an existing class, as shown in this example:
import java.io.OutputStream;
import java.io.IOException;
class BufferOutput {
private OutputStream o;
BufferOutput(OutputStream o) { this.o = o; }
protected byte[] buf = new byte[512];
protected int pos = 0;
public void putchar(char c) throws IOException { if (pos == buf.length) flush(); buf[pos++] = (byte)c; }
public void putstr(String s) throws IOException { for (int i = 0; i < s.length(); i++) putchar(s.charAt(i)); }
public void flush() throws IOException { o.write(buf, 0, pos); pos = 0; }
}
class LineBufferOutput extends BufferOutput {
LineBufferOutput(OutputStream o) { super(o); }
public void putchar(char c) throws IOException { super.putchar(c); if (c == '\n') flush(); }
}
class Test { public static void main(String[] args)
throws IOException
{ LineBufferOutput lbo =
new LineBufferOutput(System.out); lbo.putstr("lbo\nlbo"); System.out.print("print\n"); lbo.putstr("\n"); } }
This example produces the output:
lbo print lbo
The class BufferOutput
implements a very simple buffered version of an OutputStream
, flushing the output when the buffer is full or flush
is invoked. The subclass LineBufferOutput
declares only a constructor and a single method putchar
, which overrides the method putchar
of BufferOutput
. It inherits the methods putstr
and flush
from class Buffer
.
In the putchar
method of a LineBufferOutput
object, if the character argument is a newline, then it invokes the flush
method. The critical point about overriding in this example is that the method putstr
, which is declared in class BufferOutput
, invokes the putchar
method defined by the current object this
, which is not necessarily the putchar
method declared in class BufferOutput
.
Thus, when putstr
is invoked in main
using the LineBufferOutput
object lbo
, the invocation of putchar
in the body of the putstr
method is an invocation of the putchar
of the object lbo
, the overriding declaration of putchar
that checks for a newline. This allows a subclass of BufferOutput
to change the behavior of the putstr
method without redefining it.
Documentation for a class such as BufferOutput
, which is designed to be extended, should clearly indicate what is the contract between the class and its subclasses, and should clearly indicate that subclasses may override the putchar
method in this way. The implementor of the BufferOutput
class would not, therefore, want to change the implementation of putstr
in a future implementation of BufferOutput
not to use the method putchar
, because this would break the preexisting contract with subclasses. See the further discussion of binary compatibility in §13, especially §13.2.
This example uses the usual and conventional form for declaring a new exception
type, in its declaration of the class BadPointException
:
class BadPointException extends Exception { BadPointException() { super(); } BadPointException(String s) { super(s); } } class Point { int x, y; void move(int dx, int dy) { x += dx; y += dy; } }
class CheckedPoint extends Point { void move(int dx, int dy) throws BadPointException { if ((x + dx) < 0 || (y + dy) < 0) throw new BadPointException(); x += dx; y += dy; } }
This example results in a compile-time error, because the override of method
move
in class CheckedPoint
declares that it will throw a checked exception that
the move
in class Point
has not declared. If this were not considered an error, an
invoker of the method move
on a reference of type Point
could find the contract
between it and Point
broken if this exception were thrown.
Removing the throws
clause does not help:
class CheckedPoint extends Point { void move(int dx, int dy) { if ((x + dx) < 0 || (y + dy) < 0) throw new BadPointException(); x += dx; y += dy; } }
A different compile-time error now occurs, because the body of the method move
cannot throw a checked exception, namely BadPointException
, that does not
appear in the throws
clause for move
.