关于向上转换与动态绑定的混淆
confusion about upcasting vs dynamic binding
所以我对动态绑定和向上转换有些困惑。
我最初的理解是,当您创建一个 base_class
引用并将其分配给派生的 class 对象时,例如:
base_class obj = new derived_class();
您现在可以使用 obj.method_of_derived class()
调用 derived_class
的 methods/member 函数,无需动态绑定。
我认为只有当 base_class
和 derived_class
具有同名的 functions/methods 时才需要动态绑定,并且需要在编译期间进行某种覆盖正确分配它们。
我完全错了吗?
或者还有其他原因导致下面的代码不起作用?
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);
}
}
以及从上面class
扩展而来的派生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);
}
}
我无法做到这一点:
Ticket object = new originalTicket();
object.setoriginalTicket();//Can't call the originalTicket method.
我在 C++ 中使用向上转换编写了一些程序,但我总是使用动态绑定。我只是想学习 Java,现在我有点吃惊,因为我一直把所有的概念都弄错了。谢谢。
让我们以 C++ 为起点,因为它是您的母语:
class Base
{
void f() { };
};
class Derived : public Base
{
void g() { };
};
Base* b = new Derived();
b->g();
有用吗?
您只能调用基础 class 中已知的方法 (Java)/函数 (C++)。您可以通过使它们成为虚拟来覆盖它们(C++;Java:所有 non-static 方法都是隐式虚拟的!):
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();
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.
这是不正确的。 obj
是类型 base_class
的引用变量,因此 base_class
定义了您可以通过 obj
访问的接口(您可以使用该引用的字段和方法)。所以您可以使用 obj.setTicket
和 obj.displayTicket
,但不能使用 obj.originalTicket
,因为 originalTicket
不是 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
实例。
you can now call the methods/member functions of derived_class with obj.method_of_derived_class(), without dynamic binding.
您只能调用 Java 中声明的接口方法。在您的情况下,只有 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.
继承中的方法调用总是在运行时用 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"
让我尝试用更通俗的术语来解释:
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
class.
定义的方法
如果您知道 animal
是 Cat
的一个实例,并且您想调用 Cat
class 中的方法,则需要强制转换它。通过强制转换,您是在告诉编译器,"Hey I know this thing is a cat, I want you to treat this like a cat."
因此,通过转换,您可以访问 Cat
class 中的方法,其中还包括从 Animal
class.[=25 继承的所有成员=]
如果您不确定 animal
是 Cat
还是 Fish
,但您仍然投出并调用 moveWithLegs()
,您将得到 ClassCastException
在运行时,这意味着你违反了你在编译时同意的合同。
大多数情况下,当您进行多态性时,您使用的是抽象 class 或接口作为基础。
正如我们所知,我们在运行时进行 UPCASTING 多态性,这是通过方法覆盖实现的。因此,您只能访问基 class 中的覆盖方法。
如果你想完全访问派生的class,你需要通过转换明确地告诉编译器。
所以我对动态绑定和向上转换有些困惑。
我最初的理解是,当您创建一个 base_class
引用并将其分配给派生的 class 对象时,例如:
base_class obj = new derived_class();
您现在可以使用 obj.method_of_derived class()
调用 derived_class
的 methods/member 函数,无需动态绑定。
我认为只有当 base_class
和 derived_class
具有同名的 functions/methods 时才需要动态绑定,并且需要在编译期间进行某种覆盖正确分配它们。
我完全错了吗?
或者还有其他原因导致下面的代码不起作用?
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);
}
}
以及从上面class
扩展而来的派生classpublic 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,现在我有点吃惊,因为我一直把所有的概念都弄错了。谢谢。
让我们以 C++ 为起点,因为它是您的母语:
class Base
{
void f() { };
};
class Derived : public Base
{
void g() { };
};
Base* b = new Derived();
b->g();
有用吗?
您只能调用基础 class 中已知的方法 (Java)/函数 (C++)。您可以通过使它们成为虚拟来覆盖它们(C++;Java:所有 non-static 方法都是隐式虚拟的!):
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();
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.
这是不正确的。 obj
是类型 base_class
的引用变量,因此 base_class
定义了您可以通过 obj
访问的接口(您可以使用该引用的字段和方法)。所以您可以使用 obj.setTicket
和 obj.displayTicket
,但不能使用 obj.originalTicket
,因为 originalTicket
不是 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
实例。
you can now call the methods/member functions of derived_class with obj.method_of_derived_class(), without dynamic binding.
您只能调用 Java 中声明的接口方法。在您的情况下,只有 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.
继承中的方法调用总是在运行时用 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"
让我尝试用更通俗的术语来解释:
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
class.
如果您知道 animal
是 Cat
的一个实例,并且您想调用 Cat
class 中的方法,则需要强制转换它。通过强制转换,您是在告诉编译器,"Hey I know this thing is a cat, I want you to treat this like a cat."
因此,通过转换,您可以访问 Cat
class 中的方法,其中还包括从 Animal
class.[=25 继承的所有成员=]
如果您不确定 animal
是 Cat
还是 Fish
,但您仍然投出并调用 moveWithLegs()
,您将得到 ClassCastException
在运行时,这意味着你违反了你在编译时同意的合同。
大多数情况下,当您进行多态性时,您使用的是抽象 class 或接口作为基础。
正如我们所知,我们在运行时进行 UPCASTING 多态性,这是通过方法覆盖实现的。因此,您只能访问基 class 中的覆盖方法。 如果你想完全访问派生的class,你需要通过转换明确地告诉编译器。