I have a Paper object that contains 2 equals
method.
class Paper{
private int height;
private int width;
Paper(int height, int width){
this.height= height;
this.width = width;
}
@Override
public boolean equals(Object obj){
System.out.println("A");
return this == obj;
}
//Overload
public boolean equals(Paper p){
System.out.println("B");
return this == p;
}
}
What will be printed out for the following 6 equals
method calls?
//set up
Paper paper = new Paper(297, 140);
Object object = paper;
// 1 to 3
object.equals( object );
object.equals( (Paper) object );
object.equals( paper );
// 4 to 6
paper.equals( object );
paper.equals( (Paper) object );
paper.equals( paper );
Answer as follows:
// 1 to 3
object.equals( object ); // A
object.equals( (Paper) object ); // A
object.equals( paper ); // A
// 4 to 6
paper.equals( object ); // A
paper.equals( (Paper) object ); // B
paper.equals( paper ); // B
Let's get to the analysis. Feel free to correct me if I am wrong...
Basic knowledge
Compile-time | Run-time | |
---|---|---|
Type | Left hand side of the assignment, fixed upon declaration |
Right hand side of the assignment, vary as program runs |
Binding | Static | Dynamic |
Control | Restricts methods that can be called based on compile-time type | Methods to invoke are determined during run-time |
Check for | Method overloading, more specific method will be invoked |
Method overriding, overridden method will never be invoked |
There are a total of 3 different possible outputs :
- None (Java Object's default equals method is invoked)
- A (Paper's
equals
overriding method is invoked) - B (Paper's
equals
overloading method is invoked)
Let's draw a proper mental model of what is going on.
Explanation for the set-up
//set up
Paper paper = new Paper(297, 140);
Object object = paper;
- A variable named
paper
is created and declared to be of compile-time typePaper
. The variable is also assigned to a newPaper
object created using thePaper
class constructor. The run-time type of the variable is alsoPaper
. - A variable named
object
is created and declared to be of compile-time typeObject
. The variable is also assigned to the createdPaper
object. The run-time type of the variableobject
isPaper
.
Explanation for the 1st to 3rd method calls
// 1 to 3
object.equals( object ); // A
object.equals( (Paper) object ); // A
object.equals( paper ); // A
During compile-time, we look at the compile-time type. So in the above three cases, we have a variable named object
that has compile-time type of Object
. This fact tells us that the variable can execute any method as long as the method is included in the Object
class.
At the same time, we check if there is any method overloading, where two or more methods have the same name but different method signatures. If there is method overloading, we will look for the most specific method within that class to call upon. This is static binding.
During run-time, we know that certain objects might have different type as compared to compile-time, hence we check if any method has been overridden and change our decision made during compile-time, and run the overriding method instead. This is also known as dynamic binding.
From our mental model above, we can see that the Object
class has only one equals
method. So,
- Method call 1 is safe. During compile-time, we loosely understand method call 1 as
(Object) object.equals( (Object) object )
. Since we do have theequals
method withinObject
class, we know at the very least,equals
method can be called from withinObject
class. Come run-time, we realize that object has a run-time type ofPaper
. We now check for any override (not overload!), and turns out that there is an overridingequals
method within the childPaper
class and hence that method is invoked and printed "A". - Method call 2 is safe. We can see that due to typecasting, a
Paper
object is being passed into theequals
method. Although we do not have a particularequals
method in Object class that takes inPaper
, we can treat the parameter asObject
. This is becausePaper
is a subclass ofObject
(by default, all objects in Java inherit fromObject
). Hence, following the same logical reasoning of method call 1, theequals
method withinPaper
class is invoked and printed "A". - Method call 3 is safe. Reasoning follows method call 2.
Additional note:
- The ability to invoke the overriding method in the subclass instead of the overridden method in the parent class supports polymorphism. This means given objects of the same parent, we can call a particular method and invoke different implementations of that method within each child class.
Explanation for the 4th to 6th method calls
// 4 to 6
paper.equals( object ); // A
paper.equals( (Paper) object ); // B
paper.equals( paper ); // B
With the knowledge from the first three method calls, it is much easier to understand the rest.
- Method call 4 is safe. We do have an
equals
method within thePaper
class and looking at the method signature, we know that we will call the one that specifically takes inObject
, which outputs "A". - Method call 5 is safe.
(Paper)object
signals that we want the object to be used asPaper
when this method is run. During compile-time, We see that the parameter is typecast intoPaper
, so we look for a specific method call that match this. We have the secondequals
method withinPaper
that specify that it takes inPaper
and outputs "B". - Method call 6 is safe. Reasoning is similar to method call 5.
Additional note:
- The only time that the overloaded method
equals(Paper)
can be invoked is when the compile-time type of the variable isPaper
.
Conclusion
Understand the different results across run-time, due to inheritance and polymorphism, might be slightly confusing. However, this knowledge will allow us to better appreciate Objec-Oriented design later on. One example is that polymorphism and dynamic binding will make extension of code easier (minimize modification of client code when adding new a subclass).
Top comments (0)