关于向上转换与动态绑定的混淆



所以我在理解动态绑定与向上转换时有些困惑。

我最初的理解是,当您创建一个base_class引用并将其分配给派生类对象时,如下所示:

base_class obj = new derived_class();

您现在可以使用obj.method_of_derived class()调用derived_class的方法/成员函数,而无需动态绑定。

我认为只有当base_classderived_class具有相同名称的函数/方法时,您才需要动态绑定,并且在编译时需要进行某种覆盖才能正确分配它们。

我完全错了吗?

还是以下代码不起作用还有其他原因?

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);   
}
}

以及从上述类扩展而来的派生类

public class originalTicket extends Ticket {
protected boolean catchAnotherFlight;
protected boolean baggageTransfer;
public originalTicket(){
catchAnotherFlight = false;
baggageTransfer = false;
}
public void setoriginalTicket(lnode ticket){
setTicket(ticket);
}
}

我无法做到这一点:

Ticket object = new originalTicket();
object.setoriginalTicket();//Can't call the originalTicket method.

我在C++中使用 upcast 编写了一些程序,但我总是使用动态绑定。我只是想拿起Java,现在我有点吃惊,我一直把所有的概念都弄错了。谢谢。

让我尝试使用更外行的术语来解释:

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

对象animal只是对动物的任何事物的对象引用。即使实际的实例类型是Cat,编译器也会简单地将其视为Animal

因此,在编译时,只允许您调用Animal类定义的方法。

如果你知道animalCat的实例,并且你想在类中调用一个方法Cat你需要强制转换它。通过强制转换,你告诉编译器,"嘿,我知道这个东西是一只猫,我希望你像对待一只猫一样对待它。

因此,通过强制转换,您可以访问类Cat中的方法,其中还包括从Animal类继承的所有成员。

如果你不确定animalCat还是Fish,但你仍然投射并调用moveWithLegs(),你会在运行时得到一个ClassCastException,这意味着你违反了你在编译时商定的契约。

我最初的理解是,当您创建一个base_class指针并将其分配给派生类对象时,如下所示:

base_class obj = new derived_class();

现在,您可以使用 obj.method_of_derived class() 调用 derived_class 的方法/成员函数,而无需动态绑定。

这是不正确的。objbase_class类型的引用变量,因此base_class定义了您可以通过obj访问的接口(可用于该引用的字段和方法)。因此,您可以使用obj.setTicketobj.displayTicket,但不能使用obj.originalTicketoriginalTicket因为它不是base_class接口的一部分。(如果您知道变量引用的对象实际上是derived_class对象,则可以使用强制转换来更改对象:((derived_class)obj).originalTicket的接口

。您可能想到的是,尽管通过obj与对象的接口是由base_class定义的,但实现来自derived_class。举一个更简单的例子:

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");
}
}

然后:

Base obj = new Derived();
obj.a(); // "derived a"
obj.b(); // Compilation error

注意,虽然我们与对象的接口是Base类型,但当我们调用obj.a()时,我们得到了Derived的实现——因为对象实际上是一个Derived实例。

让我们考虑C++作为起点,因为它是你来自的语言:

class Base
{
void f() { };
};
class Derived : public Base
{
void g() { };
};
Base* b = new Derived();
b->g();

行得通吗?

您只能调用基类中已知的方法 (Java)/函数 (C++)。您可以通过将其设为虚拟 (C++;Java:所有非静态方法都是隐式虚拟的!

class Base
{
virtual void f() { };
};
class Derived : public Base
{
virtual void f() override { };
};
Base* b = new Derived();
b->f(); // calls Derived's variant of!

回到第一个示例:你需要一个向下转换来调用新函数:

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

在 Java 中完全相同,只是您没有显式指针,而是隐式引用......

Base b = new Derived();
((Derived)b).g();

您现在可以使用 obj.method_of_derived_class() 调用 derived_class 的方法/成员函数,而无需动态绑定。

您只能在 Java 中调用声明接口的方法。在您的情况下,只有base_class的方法。

我以为你只需要动态绑定当base_class和 derived_class具有相同名称的函数/方法,以及一些 在编译时需要进行某种覆盖才能分配 他们正确。

继承中的方法调用始终在运行时使用 Java 进行评估。因此,在您的示例中,这意味着您必须通过以下方式声明您的类:

public class Ticket {
...
public void setTicket() {
System.out.println("I am the superclass");
}
...
}

这是你的子类

public class OriginalTicket extends Ticket {
...
@Override
public void setTicket(){
System.out.println("I am the subclass");
super.setTicket();
}
...
}

你必须以这种方式打电话给setTicket()

Ticket object = new OriginalTicket();
object.setTicket();

在运行时,将变量的接口声明为Ticket还是OriginalTicket并不重要。输出将始终为:

"I am the subclass"
"I am the superclass"

通过OriginalTicket接口调用setTicket()在运行时评估期间不会产生任何差异。

OriginalTicket object = new OriginalTicket();
object.setTicket();

输出将是相同的:

"I am the subclass"
"I am the superclass"

如果从实现中省略super.setTicket(),则不会调用Ticket.setTicket()方法,输出将为:

"I am the subclass"

大多数情况下,当你做多态性时,你使用抽象类或接口作为基础。

正如我们所知,我们正在运行时多态性进行UPCAST,这是通过方法覆盖实现的。因此,您只能访问基类中的重写方法。 如果要完全访问派生类,则需要通过强制转换来显式告诉编译器。

今晚我在学习Java时也有同样的困惑,花了很多时间提问和阅读问题。为了补充其他人所说的话,我相信《绝对Java》第6版第531页中的一段话完全消除了我的困惑:

类变量的类型决定了哪些方法名称可以与变量一起使用, 但是由变量命名的对象决定了方法名称的定义是 使用。此规则的特例如下:类参数的类型决定了 哪些方法名称可以与参数一起使用,但参数确定哪个 使用方法名称的定义。

第一句话解释了为什么我(可能还有你)认为向上转换和动态绑定有一种奇怪的关系。首先我们需要了解对象类型和引用类型之间的区别(教科书使用变量),然后我们需要了解动态绑定实际上使用这两种类型。这让我今晚感到困惑,但终于可以睡得很香了。

相关内容

  • 没有找到相关文章

最新更新