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

时间:2018-03-13 08:27:03

标签: java dynamic-binding upcasting

所以我对动态绑定与向上转换有一些混淆。

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

base_class obj = new derived_class();

您现在可以使用derived_class调用obj.method_of_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 ++中使用upcasting编写了一些程序,但我总是使用动态绑定。我只是想拿起Java,现在我有点吃惊,我一直都认为所有的概念都是错误的。谢谢。

5 个答案:

答案 0 :(得分:3)

  

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

base_class obj = new derived_class();
     

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

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

您可能会想到的是,尽管<{1}}通过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的实现 - 因为该对象实际上是{{1} 1}}实例。

答案 1 :(得分:3)

让我试着用更多的外行术语来解释:

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类中调用方法,则需要强制转换它。通过施法,你告诉编译器,&#34;嘿,我知道这个东西是猫,我希望你像对待猫那样对待它。&#34;

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

如果您不确定animalCat还是Fish,但仍然会投射并致电moveWithLegs(),那么您将获得ClassCastException在运行时,这意味着你破坏了你在编译时同意的合同。

答案 2 :(得分:2)

让我们将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!

回到第一个例子:你需要一个 down 强制转换来调用新函数:

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

答案 3 :(得分:2)

  

现在可以使用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();

在运行时,如果将变量的接口声明为TicketOriginalTicket,则无关紧要。输出将始终为:

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

通过setTicket()接口调用OriginalTicket在运行时评估期间不会有任何不同。

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

输出将是相同的:

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

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

"I am the subclass"

答案 4 :(得分:0)

通常,当您进行多态时,您将抽象类或接口用作基础。

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