【问题标题】:Casting reference variable in Java在 Java 中强制转换引用变量
【发布时间】:2009-01-31 00:10:49
【问题描述】:

关于在 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 的类型更改为超类的类型,是否会调用子类中的覆盖方法,而不是超类的方法?

非常感谢。

问候

【问题讨论】:

    标签: java reference casting


    【解决方案1】:

    在这种情况下,演员表没有影响。

    System.out.println(XXX) 采用不同类型的参数(多个重载版本),但在这种情况下,您将获得采用 Object 的版本。由于 Java 中的每个对象都支持 toString(),因此无论实际参数是什么,都会调用 toString。

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

    【讨论】:

    • 如果传递的参数是字符串,则不会调用 toString()。在其他情况下是的。
    • “System.out.println(XXX) 采用字符串类型的参数” .... 此语句不正确。您可能需要改写它以使其正确。
    • 正如 Vinegar 所说,在这种情况下,System.out.println(XXX) 采用 Object 类型的参数,然后在将结果传递给 println(String) 方法之前在内部调用 toString 。
    • 你们是对的,我的错。有一个接受 String 的版本和一个接受 Object 的版本,我认为(虽然我可能错了)Object 版本是接受 String 的一个方便的方法。
    • 所有方法都不是动态调度的。在编译时选择重载。如果你有 foo(String) 和 foo(Object),并且你的引用是 Object 类型,即使实际类型是 String,也会调用后者。
    【解决方案2】:

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

    其中两个是:

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

    【讨论】:

      【解决方案3】:

      由于 Java 方法都具有动态分派,因此调用哪个函数不取决于引用的静态类型。因此,无论有没有演员,结果都是一样的。 [如果您向下转换,结果可能会有所不同 - 转换版本可能会引发异常]

      【讨论】:

        【解决方案4】:

        这对吗?

        有点。转换表达式的结果将是 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"; }
        }
        

        【讨论】:

          【解决方案5】:

          始终将您的对象视为它被实例化的类型(在您的情况下为 B)。如果对 A 很上心,可以把它想象成——嗯——把它想象成 B 穿上 A 的衣服。它可能看起来像一个 A,你可能无法做任何你想做的好 B 的事情,但在衣服里面它仍然是一个 B——衣服根本不会改变底层的对象。

          所以总结就是——你只能调用A中的方法,但是当你调用它时,它会直接执行它,就像它是B一样。

          【讨论】:

            【解决方案6】:

            我认为当我们在 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类的对象。

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

            【讨论】:

              【解决方案7】:

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

              但请注意,并非所有方法都如此,因为只有被覆盖的方法是动态绑定的。重载的方法是静态绑定的。这里的很多答案都提到java方法总是动态绑定的,这是不正确的。

              See this question for a more detailed explanation.

              【讨论】:

                【解决方案8】:

                问题:即使我把b的类型改成了超类的类型,是否会调用子类中被覆盖的方法,而不是超类的方法?

                在这种情况下,子类 b 的方法被调用;令人信服地理解原因;您可能与以下现实世界场景有关

                考虑一个父类 Father 表现出一种行为(方法):height 定义为

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

                Son 是一个子类,继承了父亲的 height 行为;因此他也很高;高度为 6' 显然覆盖了行为

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

                【讨论】:

                  猜你喜欢
                  • 2019-09-22
                  • 2014-02-16
                  • 2012-02-18
                  • 1970-01-01
                  • 2012-06-20
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-10-28
                  • 1970-01-01
                  相关资源
                  最近更新 更多