Why use of the @Override annotation is essential in Java
Back in the day, Java 1.5
introduced the notion of
annotations and, of particular
importance in the context of this article, the
@Override
annotation.
@Override
indicates simply that a method in a
particular class overrides
a method in its parent class.
Why should we need this? Surely it is obvious when one method overrides another?
If a method has the same name and arguments as a corresponding method in
the base class, then the base class method is overridden, right?
Well, not always. Consider the following code snippet, which shows three
Java classes in the same package.
package mypackage;
public class Parent
{
void doSomething()
{
System.out.println ("This is the parent");
}
}
package mypackage;
public class Child extends Parent
{
void doSomething()
{
System.out.println ("This is the child");
}
}
package mypackage;
class Test
{
public static void main (String[] args)
{
Parent p = (Parent) new Child();
p.doSomething();
}
}
Executing the Test class displays the message
This is the child
,
even though the
doSomething()
method call is on a reference
to an instance of
Parent
. This, of course, is polymorphism at
work -- since the variable
p
refers to an instance which
is known at run-time to be of class
Child
, and there is
a method
Child.doSomething()
that overrides
Parent.doSomething()
, then the method in
Child
is called, quite properly.
All well and good. But suppose that, during a code refactoring exercise, we
move the class
Child
into a different package. So the
definition of
Child
becomes:
package mypackage2;
import mypackage.parent;
public class Child extends Parent
{
void doSomething()
{
System.out.println ("This is the child");
}
}
We'll also have to change the invocation in class
Test
, so now
we have:
Parent p = (Parent) new mypackage2.Child();
Now when we run the
Test
class, we get the output
"This is the parent."
It seems that either run-time linking has failed, or that the
doSomething()
method in
Child
no longer overrides the corresponding
method in its base class. In fact, the latter explanation is the correct
one -- and it's potentially quite dangerous that a trivial change of packaging
can disturb the program logic so effectively.
What's happened here is that the method
doSomething()
in
Parent
is not tagged with a
public
or
protected
modifier. Consequently, to a class outside the
package
mypackage
, the method is effectively private. In Java,
it is not an error to define a method that has the same name as a private
method in a superclass -- but it doesn't create an override, either.
In practice, on many occasions where class A extends class B, A and B are going
to be in the same package. In such cases, if you don't declare the method
public
or
protected
that won't prevent the method
being overridden by a subclass. But this -- leaving the method with default
access specification -- is a dangerous practice, because it opens the way
to problems of the kind described here. A potential solution is to
routinely mark
all methods as
protected
, other than
those which must genuinely remain private. This is pretty common practice
in C++ programming, but there is a subtle difference between what
'protected' means in this two languages.
In C++,
protected
means 'accessible to subclasses only'. Java
has no real equivalent of this access level -- in Java
protected
means 'accessible to subclasses and the package'. There is no way in Java
to stipulate that a method can be overridden by subclasses, but
not
called by other classes in the same package. Whatever the thinking, I've
noticed that Java programmers are reluctant to use 'protected' access, and
the risk of silently breaking your application by a trivial packaging change
is a very real one.
This is where
@Override
comes in. If a method is annotated in
this way, it does not change the program logic at all, but it causes the
compiler to emit an error if the method in question does
not override
a method in a base class. In this example above, if I define
Child.doSomething()
like this:
@Override void doSomething() {...}
Then the compiler will fail, with the following message:
Child.java:5: error: method does not override or implement a method from a
supertype
@Override void doSomething()
Interestingly, I can't fix the error within
Child.java
, because
that's not where the problem is (in this case). I need to sort out the
access specification in
Parent.java
.
@Override
is also useful for trapping trivial programmer errors, like spelling
wrongly the name of the overridden method, or providing the wrong argument
list.
In my view, methods in Java classes should be defined as
protected
or
private
in all cases, and never left with the default
(package-private) access specification. However, since we have to use
libraries we have no control over, the next best thing is to use
@Override
in all cases where a method is expected to
override a base class method.