在Java中转换引用变量

3

我对Java中引用变量的转换有些不清楚。

我有两个类A和B。A是B的超类。 如果我有这两个对象,然后有一个打印语句:

A a = new A(); //superclass

B b = new B(); //subclass

System.out.println ((A)b);

当执行println方法时,究竟发生了什么?

我知道因为B是A的子类,所以我可以进行以下转换:

A a2 = (A)b;

我知道,当println将引用变量作为参数时,它会(隐式地)调用创建该参数对象的类的toString()方法。这是因为println()方法正在寻找类型为String的参数,而toString()方法表示对象为字符串。即使我们不写toString(),该方法也会被隐式调用。因此,以下两个语句是等价的:
System.out.println (b);

System.out.println (b.toString());

所以,我的问题是:当我们有 <\/p> 标签时,会采取什么隐含的操作?
System.out.println ((A)b); 

我猜测引用变量 b 的类型会自动从 B 改为 A。该变量仍应指向同一对象——使用

B b = new B();

但是b的类型现在会被改变,这样正确吗?另一个问题:尽管我已经将b的类型更改为超类的类型,但子类中的重写方法将被调用,而不是超类的方法吗?

非常感谢。

敬礼

8个回答

4
在这种情况下,强制类型转换没有影响。
System.out.println(XXX)方法有多个不同类型的参数(多个重载版本),但在此情况下,您将获得接受Object类型参数的版本。由于Java中的每个对象都支持toString()方法,因此toString()将在实际参数上调用,无论它是什么。
现在,由于Java中的所有方法都是动态分派的,运行的版本是对应于动态类型的版本。将B类对象转换为A类只会改变表达式的静态(声明)类型,动态类型(实际上的类型)仍然是B类。因此,将调用B类中的版本。

“System.out.println(XXX)”需要一个字符串类型的参数。这个陈述是不正确的。你可能需要重新表述它来使它正确。 - Adeel Ansari
1
所有方法都不是动态分派的,重载在编译时被选择。如果您有foo(String)和foo(Object),并且您的引用类型是Object,则后者将被调用,即使实际类型是String。 - Tim Frey
但是 println 的实现最终调用了 toString(),并且这是动态分派的。 - Uri
是的,在这种特定情况下。在其他情况下,重载方法可能会执行完全不同的操作。 - Tim Frey
我仍然不明白你的疑虑。显然,静态重载版本首先确定。但是,当进行调用(在这种情况下是toString)时,除非类是final且编译器可以在编译时确定目标,否则分派是动态的。 - Uri
显示剩余3条评论

3

PrintStream类(它是System.out的类型)有许多声明println(...)的方法。

其中两个是:

void println(String x)
void println(Object x)

当你调用println((A)b)时,编译器选择调用println(Object),因为A不是String(或任何其他println支持的类型)。当你调用println(b.toString())时,编译器选择println(String),因为你传递了一个字符串。
在你的情况下,将b强制转换为A没有效果,因为println()对于A和B类型都没有声明。但是类型转换仍然会发生(因为你请求它),或者也许不会发生,因为编译器优化掉了它,知道它是多余的、不会失败并且没有影响。
写成这样不是惯用语:
A a2 = (A)b;

由于B是A的子类,因此这是多余的。编译器可能会优化掉强制类型转换(这是一种运行时操作,用于检查对象是否为特定类型,而不是更改其类型)。

一旦构造了B类型的对象,它的类型就永远不会改变。它始终是一个B:

class B extends/implements A {...}
B b = new B();   // construct a B
A a = b;         // assign a B to an A variable, it's superclass
A a = (A) b      // as above including check to see that b is an A (redundant, may be optimised away).

B b = a;         // Syntax error, won't compile
B b = (B) a      // Will check whether a is of type B then assign to variable b

在最后一种情况下,由于BA的子类,可能会出现a持有一个B实例并且转换将成功的情况。或者a持有某个扩展/实现/是A但不是B的其他类的实例,你将得到ClassCastException
因此,由于类型为B的对象始终保留其身份(即“B”-ness),因此对该对象调用的任何(实例)方法都将始终调用B的实现,而不管您通过哪个变量访问该对象是否声明为A或B。
请记住,您只能调用在变量所定义的类中声明的方法。
例如,如果B声明了一个方法b_only(),则编译器不允许您编写a.b_only();但您可以编写((B)a).b_only()

1

由于Java方法都具有动态分派,因此调用哪个函数不取决于引用的静态类型。因此,即使没有进行强制转换,结果也将是相同的。[如果您进行了向下转换,则结果可能会有所不同-强制转换版本可能会抛出异常]


1

始终将您的对象视为其实例化类型(在您的情况下为B)。如果将其向上转换为A,请将其视为——嗯——将B穿上A的衣服。它可能看起来像A,您可能无法执行任何想要执行的好B操作,但在衣服内部,它仍然是B——衣服根本不会改变基础对象。

因此,总结就是——您只能调用A中的方法,但当您调用它时,它会直接通过并像B一样执行。


1

我认为当我们在Java中使用引用变量,并且通过使用此变量我们可以分配任何类类型的对象。在大多数情况下,我们创建接口和抽象类的引用变量,因为我们无法创建接口和抽象类的对象,因此将类的对象分配到接口或抽象类的引用变量中。

Interface X {
 public abstract void xx();
 public abstract void yy();
}

Class XXX implements X {
   ...........
}

Class XY extends XXX {
  X xy = new XXX();
} 

这里的xy是接口X的引用,并将类XXX的对象分配给接口的引用。

因此,根据我的观点,通过使用引用变量,我们也可以使用接口参与对象创建。


1
这正确吗?
有点。强制转换表达式的结果将是A类型。变量'b'的类型始终保持为B类型。
另一个问题:即使我已经将b的类型更改为超类的类型,子类中重写的方法是否会被调用,而不是超类的方法?
将调用底层对象的实例方法。例如:
class Foo {
    public static void main(String[] args) {
        B b = new B();
        assert "B".equals(((A) b).m());
    }
}

class A {
    String m() { return "A"; }
}

class B extends A {
    String m() { return "B"; }
}

0

正如已经提到的,由于覆盖方法被动态绑定,因此强制转换在这种情况下是无关紧要的。由于toString存在于所有对象中,它满足了这个条件,因此对象类型和要调用的方法在运行时确定。

请注意,这并不适用于所有方法,因为只有覆盖方法是动态绑定的。重载方法是静态绑定的。这里的许多答案都提到Java方法总是动态绑定的,这是不正确的。

请参阅此问题以获取更详细的解释。


0

问题:即使我已经将b的类型更改为超类的类型,子类中重写的方法会被调用,而不是超类的方法吗?

在这种情况下,调用的是子类b的方法;为了更好地理解,可以参考以下现实场景

考虑一个父类Father,展示一个行为(方法):height,定义为

父亲很高;身高=6'2"

Son是继承自Father的子类,继承了height行为;因此他也很高;身高为6',明显覆盖了该行为

每当您的子类Son在自己的名称上调用height行为时,他会显示被覆盖的行为,即他自己的身高6'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接