Java中的"动态"强制类型转换

5

大家好,

想知道是否有Java黑客可以告诉我为什么以下代码无法工作:

public class Parent {
    public Parent copy() {
       Parent aCopy = new Parent();
       ...
       return aCopy;
    }
}

public class ChildN extends Parent {
    ...
}

public class Driver {
     public static void main(String[] args) {
         ChildN orig = new ChildN();
         ...
         ChildN copy = orig.getClass().cast(orig.copy());
     }
}

代码在编译时没有问题,但在运行时决定抛出ClassCastException异常D=。

编辑:哇,回复真快。谢谢大家!看来我不能使用这种方法进行向下转型...在Java中还有其他方法可以进行向下转型吗?我想过让每个ChildN类重写copy(),但不喜欢添加额外的模板代码。


你可以做到。看一下我的修改。我认为你最初是在理解“转换”方面遇到了问题。 - OscarRyz
7个回答

9

这就像试图做这件事:

  public Object copy(){
       return new Object();
  }

然后尝试:

  String s = ( String ) copy();

你的父类子类N的关系就像ObjectString的关系一样。

要使其正常工作,您需要执行以下操作:

public class ChildN extends Parent {
    public Parent copy() {
        return new ChildN();
    }
}

也就是说,重写“copy”方法并返回正确的实例。


编辑

根据您的编辑,这实际上是可能的。以下是一种可能的方式:

public class Parent {
    public Parent copy() {
        Parent copy = this.getClass().newInstance();
        //...
        return copy;
    }
}

这样你就不必在每个子类中重写“copy”方法。这是原型设计模式。

但是,使用此实现时,您应该注意两个已检查的异常。以下是完整的程序,可以编译并且没有问题运行。

public class Parent {
    public Parent copy()  throws InstantiationException, IllegalAccessException  {
       Parent copy = this.getClass().newInstance();
       //...
       return copy;
    }
}
class ChildN  extends Parent {}

class Driver {
     public static void main(String[] args) throws  InstantiationException ,  IllegalAccessException  {
         ChildN orig = new ChildN();
         ChildN copy = orig.getClass().cast(orig.copy());
         System.out.println( "Greetings from : " + copy );
    }
}

通过示例教学!这个答案对我来说非常清晰。现在,Child.copy也可以声明为返回ChildN而不是Parent吗?(我知道在C++中可以) - xtofl
不是Java代码。至少不是Java源代码。为了稍微混淆一下问题,它在Java字节码中是允许的... - Matthew Murdoch

6
演员们实际上正在尝试做到这一点:
ChildN copy = (ChildN) orig.copy();

它是在执行时计算要执行的转换,但这就是因为orig.getClass()将会是ChildN.class。然而,orig.copy()不会返回一个ChildN实例,而是只返回一个Parent实例,因此它不能被转换为ChildN


4
如果ChildN没有重写copy()方法返回ChildN的实例,那么你正在试图将类型为parent的对象向下转型为类型ChildN。

2

java.lang.Class#cast(Object)方法如果Class#isInstance()方法返回false,则会抛出ClassCastException异常。从该方法的javadoc中可以看到:

确定指定的对象是否可与此类所表示的对象分配兼容。此方法是Java语言instanceof运算符的动态等效项...具体来说,如果此Class对象表示一个已声明的类,则如果指定的Object参数是所表示类(或其任何子类)的实例,则此方法返回true;否则返回false。

由于Parent不是Child的子类,因此isInstance()方法返回false,因此cast()方法会抛出异常。这可能违反了最少惊讶原则,但它按照文档的方式工作 - cast()只能向上转型,而不能向下转型。


2

您是否只是想要一个对象的副本/克隆?

如果是这样,请实现Cloneable接口并根据需要重写clone()方法。

public class Parent implement Cloneable {
   public Object clone() throws CloneNotSupportedException {
     Parent aCopy = (Parent) super.clone();
   ...
   return aCopy;
   }
}

public class ChildN extends Parent {
    ...
}

public class Driver {
     public static void main(String[] args) {
         ChildN orig = new ChildN();
         ...
         ChildN copy = orig.getClass().cast(orig.clone());
     }
}

这段代码使用了Java中的"copy()"方法。它与复杂的反射方法相比更加简单易用。即使没有公共默认构造函数,Clone方法也可以正常工作,而反射方法则会出现问题或变得非常丑陋。请注意保留HTML标签。

也有效果!我听说clone()方法口碑不佳,但具体原因我不是很清楚。 - user50264
这可能是“其他”可能的方法。无需更改方法签名(使用克隆和返回对象),“copy”作为名称,“Parent”作为返回值即可。 - OscarRyz

1

关于Cloneable:如果您正在实现Cloneable,请按以下方式实现;更加清晰易懂...

public class Foo implements Cloneable {
    public Foo clone() {
        try {
            return (Foo) super.clone();
        } catch (CloneNotSupportedException e) {
            return null; // can never happen!
    }
}

[编辑:我也看到其他人使用过

throw new AssertionError("This should never happen! I'm Cloneable!");

在 catch 块中。


的确,这就是我最终做到它的方式!尽管我的评论更多地是 // JAVA EPIC FAIL :)。 - user50264

0
向下转型无法正常工作的原因是,当您将父对象强制转换为子类型时,无法在父对象上调用子类型的方法。但是反过来可以正常工作...

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