2009-01-31 65 views
3

关于在Java中铸造参考变量,我有一些不清楚的地方。在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()); 

所以,我的问题是:是采取什么样的隐含动作时,我们有

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

我假设参考变量b的类型自动从B到A的可变仍然应该指向相同的对象改变 - 一个与

B b = new B(); 

创建,但B的仅仅是类型现在会改变。它是否正确? 另一个问题:即使我已将b的类型更改为超类的类型,子类中的overriden方法将被调用,而不是超类的方法?

非常感谢。

问候

回答

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

由于Java方法都具有动态分派功能,因此被调用的函数不依赖于引用的静态类型。因此,无论演员是否演员,结果都是一样的。 [结果可能会有所不同,如果你是向下倾斜的 - 铸造版本可能会抛出异常]

4

演员在这种情况下没有影响。System.out.println(XXX)获取不同类型的参数(多个重载版本),但在这种情况下,您将获得使用Object的版本。由于Java中的每个对象都支持toString(),因此toString会在实际参数上调用,而不管它是什么。

现在,由于Java中的所有方法都是动态分派的,因此运行的版本是与动态类型相对应的版本。将B的对象转换为A只会更改表达式的静态(声明)类型。动态类型(真正在那里)仍然是B.因此,B中的版本被调用。

+0

如果传递的参数是String,则不会调用toString()。在其他情况下是的。 – 2009-01-31 04:37:25

1

始终将您的对象视为它实例化的类型(在您的案例中为B)。如果它被认为是 - 嗯 - 把它看成是穿上A衣服。它可能看起来像一个A,你可能无法做任何你想做的漂亮B的东西,但在衣服里它仍然是B - 衣服根本不会改变底层的物体。

所以总结是 - 你只能调用一个方法,但是当你调用它,它直接通过并执行它,因为它会如果它是一个B.

3

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

其中两个是:

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

当你调用println((A)b)编译器选择调用println(Object)因为A不是String(或任何其他类型的println支持的)。当你调用println(b.toString())时,编译器选择println(String),因为你的传递一个字符串。

就你而言,铸造bA不起作用,因为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一个子类,它可能是一个持有B实例和转换才能成功。或者它可能是一个持有其他类的实例,它扩展/实现/是A,而不是B,您将得到ClassCastException。因此,由于类型B的对象总是保留它的身份(它是“B”性),因此对该对象调用的任何(实例)方法将始终调用B的实现,而不管通过其访问对象的变量被宣布为A或B.

请记住,您只能调用在类中声明的变量被定义为的方法。例如,如果B声明了一个方法b_only(),那么编译器将不允许你编写a.b_only();如果B声明方法b_only(),那么编译器将不允许你编写a.b_only();如果B声明方法b_only(),那么编译器将不允许你写a.b_only();但你可以写((B)a).b_only()

0

如上所述,由于被覆盖的方法被动态绑定,所以在这种情况下投射是无关紧要的。由于toString存在于所有对象中,所以它符合此条件,因此要在运行时确定要调用的对象类型和方法。

请注意,虽然所有方法都不是这样,因为只有重写的方法是动态绑定的。重载的方法是静态绑定的。这里的许多答案都提到java方法总是动态绑定的,这是不正确的。

See this question for a more detailed explanation.

1

我认为,当我们在java和通过使用该变量我们可以分配任何类型的对象使用参考变量。大多数情况下,我们创建一个Interface和抽象类的引用变量,因为我们无法创建接口和抽象类的对象,因此在Interface或Abstract类的引用变量中分配类的对象。 EX-

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的对象接口的基准。

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

0

问题:即使我已将b的类型更改为超类的类型,子类中的overriden方法将被调用,而不是超类的那些方法?

在这种情况下,调用小类b的方法;令人信服地理解为什么;你可能会涉及到以下现实世界中的场景

考虑父类父亲表现出的行为(方法):高度 定义为

父亲是高大,身高= 6'2"

儿子是一个子类继承自父高度行为;结果他也是高;高度为6' 显然压倒一切的行为

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