There are many descriptions of overloading and overriding, so I will not go into details about that. The purpose of this post is to point out when the method to call is selected. Overloaded methods are selected at compile time. Overridden methods are selected at runtime.
Consider this example in Swift.
main.swift
import Foundation
let classC : ClassC = ClassC()
let classB : ClassB = classC
let classA : ClassA = classC
//Runtime binding for overrides
classC.message()
classB.message()
classA.message()
print("------------------")
//Compile-time binding for overloads
let messageUtil : ClassMessageUtil = ClassMessageUtil()
messageUtil.message(classC)
messageUtil.message(classB)
messageUtil.message(classA)
Output:
ClassC:message: BindingExperiment.ClassC
ClassC:message: BindingExperiment.ClassC
ClassC:message: BindingExperiment.ClassC
------------------
ClassC:message BindingExperiment.ClassC
ClassB:message BindingExperiment.ClassC
ClassA:message BindingExperiment.ClassC
ClassA
import Foundation
class ClassA {
func message() {
let messageString : String = "ClassA:message: " + String(describing: self)
print(messageString)
}
}
ClassB
import Foundation
class ClassB : ClassA {
override func message() {
let messageString : String = "ClassB:message: " + String(describing: self)
print(messageString)
}
}
ClassC
import Foundation
class ClassC : ClassB {
override func message() {
let messageString : String = "ClassC:message: " + String(describing: self)
print(messageString)
}
}
ClassMessageUtil
import Foundation
class ClassMessageUtil {
func message(_ classA : ClassA) {
let messageString : String = "ClassA:message " + String(describing: classA)
print(messageString)
}
func message(_ classB : ClassB) {
let messageString : String = "ClassB:message " + String(describing: classB)
print(messageString)
}
func message(_ classC : ClassC) {
let messageString : String = "ClassC:message " + String(describing: classC)
print(messageString)
}
}
Above is all of the code and the output, now let's review "main" and the output.
The inheritance of the classes are:
ClassC "isa" ClassB
ClassB "isa" ClassA
ClassA is the "most" base class in this example. These classes implement "message", so each subclass overrides the implementation. This will show runtime binding of method selection.
ClassMessageUtil was created to show method overloading. There are three implementations of "message", each one taking a different input variable type, one for ClassA, one for ClassB, and one for ClassC. This is used to show that the binding for method selection happens at compile time.
let classC : ClassC = ClassC()
let classB : ClassB = classC
let classA : ClassA = classC
We instantiate one instance of ClassC and assign that instance to variables that have the type ClassB and ClassA. This can be done because of inheritance.
classC.message() outputs ClassC:message: BindingExperiment.ClassC
classB.message() outputs ClassC:message: BindingExperiment.ClassC
classA.message() outputs ClassC:message: BindingExperiment.ClassC
At runtime the system recognizes that the instance is a "ClassC", and therefore calls ClassC message.
What follows shows compile-time binding. This happens for overloaded methods. At compile time the best "type" fit is selected.
let messageUtil : ClassMessageUtil = ClassMessageUtil()
messageUtil.message(classC) outputs ClassC:message BindingExperiment.ClassC
messageUtil.message(classB) outputs ClassB:message BindingExperiment.ClassC
messageUtil.message(classA) outputs ClassA:message BindingExperiment.ClassC
Because classB has a declared type of ClassB, even though it references an instance of ClassC, the compiler selects the overloaded method that matches the best which is:
func message(_ classB : ClassB) {
let messageString : String = "ClassB:message " + String(describing: classB)
print(messageString)
}
It did not use the method that has a parameter of type ClassC. The selection was made at compile time.
This may not be what you expected.
It is not uncommon to create methods that take parameters of some base class and then use overrides to get the runtime behavior you desire. This practice can surprise you when compile-time binding comes into play for overloaded methods. If you have this problem and you find yourself trying to fix it by using a "switch" statement and cast the parameter to its subclass, this is usually wrong and the code needs to be redesigned.
One approach is to use "protocols" in Swift or "interfaces" in Java as parameter types instead of some base class.
Here is the same example in Java.
public class Main {
public static void main(String[] args) {
ClassC cClass = new ClassC();
ClassB bClass = cClass;
ClassA aClass = cClass;
//Runtime binding for overrides
cClass.message();
bClass.message();
aClass.message();
System.out.println("------------------------");
//Compile-time binding for overloads
ClassMessageUtil messageUtil = new ClassMessageUtil();
messageUtil.message(cClass);
messageUtil.message(bClass);
messageUtil.message(aClass);
}
}
Output:
ClassC:message com.XXX.ClassC
ClassC:message com. XXX.ClassC
ClassC:message com. XXX.ClassC
------------------------
ClassC:message com. XXX.ClassC
ClassB:message com. XXX.ClassC
ClassA:message com. XXX.ClassC
public class ClassA
{
public void message()
{
String messageString = "ClassA:message " + this.getClass().getName();
System.out.println(messageString);
}
}
public class ClassB extends ClassA
{
public void message()
{
String messageString = "ClassB:message " + this.getClass().getName();
System.out.println(messageString);
}
}
public class ClassC extends ClassB
{
public void message()
{
String messageString = "ClassC:message " + this.getClass().getName();
System.out.println(messageString);
}
}
public class ClassMessageUtil
{
public void message(ClassA classA)
{
String messageString = "ClassA:message " + classA.getClass().getName();
System.out.println(messageString);
}
public void message(ClassB classB)
{
String messageString = "ClassB:message " + classB.getClass().getName();
System.out.println(messageString); }
public void message(ClassC classC)
{
String messageString = "ClassC:message " + classC.getClass().getName();
System.out.println(messageString); }
}
As you can see in this example, Swift and Java behave the same.
No comments:
Post a Comment