Java中的向下转型

202

Java允许向上转型,但是向下转型会导致编译错误。

通过添加强制类型转换可以消除编译错误,但在运行时仍然会出现错误。

在这种情况下,如果不能在运行时执行向下转型,为什么Java允许向下转型?
这个概念有什么实际用途吗?

public class demo {
  public static void main(String a[]) {
      B b = (B) new A(); // compiles with the cast, 
                         // but runtime exception - java.lang.ClassCastException
  }
}

class A {
  public void draw() {
    System.out.println("1");
  }

  public void draw1() {
    System.out.println("2");
  }
}

class B extends A {
  public void draw() {
    System.out.println("3");
  }
  public void draw2() {
    System.out.println("4");
  }
}

11
给出代码片段和错误信息会让那些正在学习相关概念的人更容易理解这个问题。 - Bob Cross
1
我看到上面的示例取自 http://www.velocityreviews.com/forums/t151266-downcasting-problem.html,那里已经有一些很好的答案了。 - PhiLho
2
@PhiLho - Joel的主要意图是将所有优秀的问题和答案放在一个共同的平台下。这并不重要,即使这些问题/代码/答案已经发布在其他网站上。我希望你能理解这一点,否则请听Joel的播客。 - Omnipotent
请将代码片段缩进四个空格,以修复格式。 - slim
B b = (B) new A(); 这是不合法的,你不应该把它称作下降转换。当我们谈论上/下转换时,实际对象并没有改变,只是变量引用该对象的类型不同而已。你不能使用子类型的变量引用基类型的对象。 - ZZZ
12个回答

325

当有可能在运行时成功时,可以进行向下转型:

Object o = getSomeObject(),
String s = (String) o; // this is allowed because o could reference a String

在某些情况下,这将无法成功:
Object o = new Object();
String s = (String) o; // this will fail at runtime, because o doesn't reference a String

当一个类(例如最后一个)在运行时失败时,会抛出 ClassCastException

在其他情况下它将正常工作:

Object o = "a String";
String s = (String) o; // this will work, since o references a String

请注意,某些类型转换将在编译时被禁止,因为它们根本不会成功。
Integer i = getSomeInteger();
String s = (String) i; // the compiler will not allow this, since i can never reference a String.

Object o = new Object(); String s = (String) o; 对我来说运行良好.. :O 怎么做到的? - Asif Mushtaq
@UnKnown:不应该出现这种情况。请仔细检查您是否已经编译和运行了该版本,如果您仍然可以重现该问题,请发布一个单独的问题(包括 SSCCE)。 - Joachim Sauer
@JoachimSauer 你指的是哪个版本?我正在使用Java 8。 - Asif Mushtaq
2
@UnKnown:我的意思是你发布的代码不应该运行(它会编译,但在运行时抛出异常)。这些评论不是调试的空间,请发布一个单独的问题。 - Joachim Sauer
1
@JoachimSauer 您的编辑插入到了错误的行。 - Gibson
显示剩余7条评论

18

以您提供的示例为例,可以执行以下操作:

public void doit(A a) {
    if(a instanceof B) {
        // needs to cast to B to access draw2 which isn't present in A
        // note that this is probably not a good OO-design, but that would
        // be out-of-scope for this discussion :)
        ((B)a).draw2();
    }
    a.draw();
}

2
当我的抽象类被多个类扩展并且我想要使用这些类的独有方法时,我才意识到 instanceof 的重要性,同时还要引用抽象类类型。如果不使用 instanceof,我会遇到类转换异常。 - Tarun

17

我相信这适用于所有静态类型语言:

String s = "some string";
Object o = s; // ok
String x = o; // gives compile-time error, o is not neccessarily a string
String x = (String)o; // ok compile-time, but might give a runtime exception if o is not infact a String

类型转换实际上是这样说的:假设这是对转换类的引用,并将其用作此类。现在,假设o真的是一个整数,那么假设它是一个字符串就没有意义,会导致意外的结果,因此需要进行运行时检查和异常通知运行环境发生了错误。

在实际使用中,您可以编写适用于更一般类的代码,但如果您知道子类是什么并且需要将其视为此类,则可以将其强制转换为子类。一个典型的例子是重写Object.equals()。 假设我们有一个汽车类:

@Override
boolean equals(Object o) {
    if(!(o instanceof Car)) return false;
    Car other = (Car)o;
    // compare this to other and return
}

我喜欢“真的”这个词,我会编辑您的帖子,使其更加明显。 - Charaf JRA

5
我们都可以看到,您提供的代码在运行时不起作用。这是因为我们知道表达式new A()永远不可能成为B类型的对象。
但编译器并不是这样看待它的。当编译器检查是否允许强制转换时,它只看到这个:
variable_of_type_B = (B)expression_of_type_A;

而正如其他人所演示的那样,这种类型的转换是完全合法的。右侧的表达式很可能会评估为B类型的对象。编译器看到AB具有子类型关系,因此在“表达式”视图下,转换可能起作用。
编译器不考虑它确切地知道expression_of_type_A对象类型将是什么特殊情况。它只看到静态类型为A,并认为动态类型可以是A或任何A的后代,包括B

3
在这种情况下,如果downcasting在运行时无法执行,为什么Java允许downcasting呢?
我认为这是因为编译器无法在编译时知道转换是否成功。对于您的示例,很容易看出转换将失败,但有时并不那么清楚。
例如,假设类型B、C和D都扩展了类型A,然后一个方法public A getSomeA()根据随机生成的数字返回B、C或D的实例。编译器无法知道该方法将返回哪个确切的运行时类型,因此如果您稍后将结果转换为B,则无法知道转换是否成功(或失败)。因此编译器必须假定转换将成功。

2

@原帖作者 - 请查看内联注释。

public class demo 
{
    public static void main(String a[]) 
    {
        B b = (B) new A(); // compiles with the cast, but runtime exception - java.lang.ClassCastException 
        //- A subclass variable cannot hold a reference to a superclass  variable. so, the above statement will not work.

        //For downcast, what you need is a superclass ref containing a subclass object.
        A superClassRef = new B();//just for the sake of illustration
        B subClassRef = (B)superClassRef; // Valid downcast. 
    }
}

class A 
{
    public void draw() 
    {
        System.out.println("1");
    }

    public void draw1() 
    {
        System.out.println("2");
    }
}

class B extends A 
{
    public void draw() 
    {
        System.out.println("3");
    }

    public void draw2() 
    {
        System.out.println("4");
    }
}

2

Downcast适用于我们处理向上转型的对象的情况。

向上转型:

int intValue = 10;
Object objValue = (Object) intvalue;

现在这个objValue变量可以随时被强制转换为int,因为被转换的对象是一个Integer

int oldIntValue = (Integer) objValue;
// can be done 

但是由于objValue是一个对象,所以它不能被转换为String,因为int不能被转换为String


0

我会告诉你为什么会发生这种情况。首先,你必须了解JVM在使用向下转型将父类分配给子类时如何支持,因为涉及到引用。例如,请考虑以下代码。

A is the super type any class that extends from it and can store the reference B class.
    A a =new B();
When you assign a reference variable into the child class jvm will understand that since A can store the reference of B class that is why you can do it.
B b=(B)b;
  1. 为什么会出现编译时错误,以及为什么不能直接将父类赋值给子类,因为它们之间没有任何继承关系。请注意,强制转换只能在使用关键字 extends 时发生,这就是为什么会出现编译时错误的原因。
  2. 另一个原因是由于运行时的 ClassCastException,因为 JVM 直接接受了规则,即我接受它是正确的,但是在运行时,JVM 将会意识到程序员在编写代码时并没有存储任何 Child 类的引用。

0

在编程中,向下转型非常有用。我经常使用它来证明向下转型的实用性。

private static String printAll(LinkedList c)
{
    Object arr[]=c.toArray();
    String list_string="";
    for(int i=0;i<c.size();i++)
    {
        String mn=(String)arr[i];
        list_string+=(mn);
    }
    return list_string;
}

我在链表中存储字符串。 当我检索链表的元素时,返回的是对象。通过向下转型可以访问元素作为字符串(或任何其他类对象)。

Java允许我们编译向下转型的代码,相信我们正在做错误的事情。 如果人类犯错,它仍然会在运行时被捕获。


在Java中使用非泛型集合相当于C++中的void*指针。对我来说,这听起来一点也不是一个好主意。 - Jezor

0

请考虑以下示例

public class ClastingDemo {

/**
 * @param args
 */
public static void main(String[] args) {
    AOne obj = new Bone();
    ((Bone) obj).method2();
}
}

class AOne {
public void method1() {
    System.out.println("this is superclass");
}
}


 class Bone extends AOne {

public void method2() {
    System.out.println("this is subclass");
}
}

在这里,我们创建了Bone子类的对象,并将其分配给超类AOne引用,现在超类引用在编译时不知道子类即Bone中的方法method2。因此,我们需要将超类引用向下转换为子类引用,以便结果引用可以了解子类即Bone中方法的存在。


AOne 看起来有点混乱。请考虑将您的类名更改为 Dog 和 Animal 或其他类似的名称。 - Kartik Chugh

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