【问题标题】:confusion about upcasting vs dynamic binding关于向上转换与动态绑定的混淆
【发布时间】:2018-03-13 08:27:02
【问题描述】:

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

我最初的理解是,当您创建 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++ 中使用向上转换编写了一些程序,但我总是使用动态绑定。我只是想学习 Java,现在我有点吃惊,我一直把所有的概念都弄错了。谢谢。

【问题讨论】:

  • "...当您创建一个 base_class 指针并将其分配给派生类对象时..." 当您将 object 分配给变量(这是一个引用变量,我们在 Java 中通常不使用“指针”),而不是相反。 :-)
  • @T.J.Crowder 抱歉,这是我第一次尝试使用 Java。我的意思是参考。完全相反。
  • 您始终只能访问已声明类的字段和方法。如果您的对象被声明为Ticket,您将始终看到Ticket 类的属性和方法,那么您将无法在没有强制转换的情况下使用originalTicket 方法。这就是为什么你不能在object 上调用setoriginalTicket(你也有一个缺失的参数,但这不是它不起作用的主要原因)
  • @BackSlash 好的,很困惑。所以这不是选角吗? base_class ref1 = new derived_class();现在ref1不应该可以调用派生类的方法了吗?
  • @LucAux 您可以将其视为隐式向上转换;来自 C++,不过我更喜欢“转换”这个词。无论哪种措辞,您都将派生类的接口简化为基类的接口。基类中的任何未知内容都无法通过引用(Java C++)或指针(C++)在没有显式向下转换的情况下访问(除了具有覆盖虚拟基类的方法/函数的派生类)。

标签: java dynamic-binding upcasting


【解决方案1】:

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

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,这意味着您违反了你在编译时就同意了。

【讨论】:

    【解决方案2】:

    我最初的理解是,当您创建一个 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。)

    您可能会想到的是,虽然您通过obj 到对象的接口 是由base_class 定义的,但实现 来自@987654335 @。所以举个更简单的例子:

    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 实例。

    【讨论】:

    • 那请问,没有动态绑定的upcasting的目的是什么?
    • @Luc 目的是你可以使用某个类Base提供的通用接口,而不必知道对象实例的真实类型是否实际上是Derived_1Derived_2,@ 987654345@, ... 所有这些派生类都可以作为类型Base,但是,它们可能会改变基类的部分行为。想象一个简单的打印机设备,它有两个接口,以太网和 USB。很多不同...但是您现在是否要检查打印机驱动程序中的每个位置是否正在使用一个或另一个连接?当然不是。
    • 因此,您为(基类)和两个实现创建了一个通用接口,一个用于以太网,一个用于 USB,并让两个类都从基类派生。然后,您可以在任何地方使用基类的接口,而不必关心连接是以太网还是 USB;您必须知道哪个连接处于活动状态的唯一一点是实例化驱动程序时......
    • @Aconcagua 非常感谢。
    【解决方案3】:

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

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

    我以为你只需要在 base_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"
    

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

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

    输出将是相同的:

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

    如果你在实现中省略了super.setTicket()Ticket.setTicket()方法就不会被调用,输出会是:

    "I am the subclass"
    

    【讨论】:

      【解决方案4】:

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

      回到第一个例子:你需要一个 downcast 来调用新函数:

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

      【讨论】:

        【解决方案5】:

        大多数情况下,当您进行多态时,您使用抽象类或接口作为基础。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-04-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多