问题
So I am having some confusion understanding dynamic binding vs upcasting.
My original understanding was that when you create a base_class
reference and assign it to a derived class object like:
base_class obj = new derived_class();
you can now call the methods/member functions of derived_class
with obj.method_of_derived class()
, without dynamic binding.
I thought you only needed dynamic binding when the base_class
and derived_class
have functions/methods that have the same name, and some sort of overriding needs to happen during the compile time to assign them correctly.
Am I totally wrong?
or is there another reason the below code doesn't work?
public class Ticket {
protected String flightNo;
public Ticket(){
this.flightNo = new String();
}
public void setTicket(lnode ticket){
this.flightNo= ticket.getFlightNumber();
}
public void displayTicket(){
System.out.println("Flight number: " + this.flightNo);
}
}
And a derived class extended from the above class
public class originalTicket extends Ticket {
protected boolean catchAnotherFlight;
protected boolean baggageTransfer;
public originalTicket(){
catchAnotherFlight = false;
baggageTransfer = false;
}
public void setoriginalTicket(lnode ticket){
setTicket(ticket);
}
}
and I am unable to do this:
Ticket object = new originalTicket();
object.setoriginalTicket();//Can't call the originalTicket method.
I have written some programs using upcasting in C++ but I always used dynamic binding. I was just trying to pick up Java and now I am a little startled that I got all the concepts wrong all along. Thanks.
回答1:
Let me try to explain using more layman terms:
public abstract class Animal {
public abstract void move();
}
public class Cat extends Animal {
@Override public void move() {
moveWithLegs();
}
public void moveWithLegs() {
// Implementation
}
}
public class Fish extends Animal {
@Override public void move() {
moveBySwimming();
}
public void moveBySwimming() {
// Implementation
}
}
Animal animal = new Cat();
animal.move(); // Okay
animal.moveWithLegs(); // Not okay
((Cat) animal).moveWithLegs(); // Okay
((Fish) animal).moveWithLegs(); // ClassCastException exception
The object animal
is just an object reference to anything that is an animal. Even though the actual instance type is Cat
, the compiler would simply treat it as if it is an Animal
.
Therefore, at compile time, you are only allowed to invoke methods defined by Animal
class.
If you know animal
is an instance of Cat
, and you would want to call a method in Cat
class, you need to cast this. By casting, you are telling the compiler, "Hey I know this thing is a cat, I want you to treat this like a cat."
Therefore, by casting, you get the access to the methods in Cat
class, which also includes all members inherited from Animal
class.
If you are not sure if animal
is a Cat
or Fish
, and yet you still cast and call moveWithLegs()
, you would get a ClassCastException
at runtime, which means you broke the contract that you agreed on at compile time.
回答2:
My original understanding was that when you create a base_class pointer and assign it to a derived class object like:
base_class obj = new derived_class();
you can now call the methods/member functions of derived_class with obj.method_of_derived class(), without dynamic binding.
That's incorrect. obj
is a reference variable of type base_class
, so base_class
defines the interface you have access to via obj
(the fields and methods you can use with that reference). So you can use obj.setTicket
and obj.displayTicket
, but not obj.originalTicket
because originalTicket
isn't part of the base_class
interface. (If you know the object the variable refers to is actually a derived_class
object, you could use use casting to change the interface you have to the object: ((derived_class)obj).originalTicket
.)
What you may be thinking of is that although the interface you have to the object through obj
is defined by base_class
, the implementation is from derived_class
. So to take a simpler example:
class Base {
public void a() {
System.out.println("base a");
}
}
class Derived extends Base {
@Override
public void a() {
// (Often you'd use `super.a()` here somewhere, but I'm not going to
// in this specific example)
System.out.println("derived a");
}
public void b() {
System.out.println("derived b");
}
}
Then:
Base obj = new Derived();
obj.a(); // "derived a"
obj.b(); // Compilation error
Note that although our interface to the object is of type Base
, when we call obj.a()
, we get Derived
's implementation of it — because the object is actually a Derived
instance.
回答3:
Let's consider C++ as a starting point, as it is the language you are coming from:
class Base
{
void f() { };
};
class Derived : public Base
{
void g() { };
};
Base* b = new Derived();
b->g();
Would it work?
You only can call methods (Java)/functions (C++) that are known in the base class. You can override them by making them virtual (C++; Java: all non-static methods are implicitly virtual!):
class Base
{
virtual void f() { };
};
class Derived : public Base
{
virtual void f() override { };
};
Base* b = new Derived();
b->f(); // calls Derived's variant of!
Getting back to the first example: You need a downcast to call the new function:
Base* b = new Derived();
static_cast<Derived*>(b)->g(); // if you know for sure that the type is correct
auto d = dynamic_cast<Derived*>(b);
if(d) d->g(); // if you are not sure about the actual type
Exactly the same in Java, just that you do not have explicit pointers, but implicit references...
Base b = new Derived();
((Derived)b).g();
回答4:
you can now call the methods/member functions of derived_class with obj.method_of_derived_class(), without dynamic binding.
You can only call the declared interface's methods in Java. In your case only methods of base_class
.
I thought you only needed dynamic binding when the base_class and derived_class have functions/methods that have the same name, and some sort of overriding needs to happen during the compile time to assign them correctly.
Method calls in inheritance are always evaluated in runtime with Java. So, in your example this would mean, that you have to declare your classes in the following way:
public class Ticket {
...
public void setTicket() {
System.out.println("I am the superclass");
}
...
}
And this is your subclass
public class OriginalTicket extends Ticket {
...
@Override
public void setTicket(){
System.out.println("I am the subclass");
super.setTicket();
}
...
}
You have to call setTicket()
this way.
Ticket object = new OriginalTicket();
object.setTicket();
At runtime it doesn't matter if you declare your variable's interface as Ticket
or OriginalTicket
. Output will always be:
"I am the subclass"
"I am the superclass"
Calling setTicket()
through the OriginalTicket
interface won't make any different during runtime evaluation.
OriginalTicket object = new OriginalTicket();
object.setTicket();
Output will the be the same:
"I am the subclass"
"I am the superclass"
If you omit the super.setTicket()
from the implementation, Ticket.setTicket()
method is not called, and output will be:
"I am the subclass"
回答5:
Mostly when you do polymorphism, you're using abstract class or Interface as Base.
As we know we're doing UPCASTING at runtime-polymorphism, and this is achieved by method-overriding. So you can only access override methods in a base class. If you want to access the derived class completely you need to tell the compiler explicitly by casting it.
来源:https://stackoverflow.com/questions/49251434/confusion-about-upcasting-vs-dynamic-binding