Calling Superclass Methods
Published · Last edited
When creating a subclass, a common situation is to overwrite a method, but using the original class' logic in doing so. Let's say we have a class for sending messages, and we want to make a variant that is written in a more formal style. In doing so, we want to preserve the "simple message formatting" logic that the superclass implements. A simple approach could look like this:
This might look alright: when overriding send
, we need to use super.send()
to avoid an accidental recursion. For consistency, why not also use super.getReceiver()
? Those methods are all defined in the superclass, after all. Well, consider this extension:
What happened here? Well, that super
is not only special in an overridden method, it generally fixes the implementation of the method to use to that of the superclass.
A regular non-static method call in Java uses "dynamic dispatch": at runtime, the class of the object the method is called on is used to determine what variant is called. For example, formalMessage.getSender()
uses the implementation in Message
, because it wasn't overwritten. formalMessage.send()
would use the implementation in FormalMessage
.
But super.getSender()
uses "static dispatch" instead. That code is located in class FormalMessage
, so super
refers to class Message
- even if the actual object is of type FormalIncognitoMessage
and that type's superclass would be FormalMessage
. So the getSender()
implementation of Message
is used, even if there is a more specific one as well.
Method calls on the bytecode level
So super
works differently - that means we should be able to spot the difference in the compiled code, and indeed we can. Let's create a more simplified example for looking at this:
If we compile this and then look at B
's bytecode:
We get this:
We can roughly read this as (for a more proper understanding, take a look at [stack machines](https://en.wikipedia.org/wiki/Stack_machine), of which the JVM is an example):
- Load the
this
object (aload_0
). - On that object, do a regular dynamic method call to
void foo()
. The word "virtual" here refers to the fact that this is implemented by using a [virtual function table](https://en.wikipedia.org/wiki/Virtual_method_table) or vtable. The#7
here is an index at which the method namefoo
and signature()V
are stored within the class file. - The
this
was "consumed" by that call, so load it again for the second call. - On this object, do a "special" method call to that same method. Note how the method is specified as
A.foo:()V
: the class to search forfoo
is compiled into this instruction instead of determined fromthis
at runtime. - Finally, the method returns to the caller, whoever that was. We don't write that return in Java (for
void
methods), but at the JVM level it's an important part of what a method does.
There are other kinds of method calls in the JVM. They are not the topic here, but if you're interested, try calling static methods and constructors, or this surprisingly intricate piece of code:
Conclusion
Being able to call super.foo()
is important, but basically only meant for situations where a class overrides that method foo
; on inherited methods, using super
is almost always a mistake. The difference between this.foo();
and super.foo();
is bigger than it may first seem and may lead to surprising behavior later on - so it's important to avoid mixing the two up from the start.