将超类方法返回的超类实例强制转换为子类实例



考虑:

public class Super
{
Integer state = 0;
Super f()
{
Super x = new Super();
x.state = this.state + 1;
return x;
}
}
public class Sub extends Super
{
Sub f()
{
return (Sub) super.f();
}
}
public class Main
{
public static void main (String[] args)
{
Sub s = new Sub();
Sub t = s.f();
}
}

这会导致崩溃:

Exception in thread "main" java.lang.ClassCastException: Super cannot be cast to Sub
at Sub.f(Sub.java:5)
at Main.main(Main.java:6)

然而,Sub的实例与Super的最小特征不同。

在一个更现实的示例中,Sub将具有与Super完全相同的属性(即 是什么让我相信它们可以安全地双向铸造(,但重新定义了它的一些方法。

是否可以在不重复f定义的情况下使此代码工作?

让我们解决问题的第一部分:

是否有可能使此代码工作

在当前状态下,代码无法工作,因为这违反了 Java 语言规范第5.5.3 节中定义的语言规范,其中指出:

如果 T 是类类型,则 R 必须是与 引发 T 或 T 的子类或运行时异常

在问题 (return (Sub) super.f();( 中提供的示例中,TSubRSuper

现在让我们解决问题的第二部分:

不重复 f 的定义

实现此目的的一种方法是将superSub之间通用的代码移动到单独的方法:

public class Super {
Integer state = 0;
Super f() {
Super x = new Super();
incrementState(x);
return x;
}
protected void incrementState(Super x) {
x.state = this.state + 1;
}
}
public class Sub extends Super {
Sub f() {
Sub s = new Sub();
incrementState(s);
return s;
}
}

是的,你可以,但你的f操作应该在类层次结构中受到保护

static class Super implements Cloneable {
int state = 0;
// inspection
public int getState() {
return state;
}
// default behavior
public void doSomething() {
state += 1;
}
// unsafe, should be protected
public <S extends Super> S f() throws CloneNotSupportedException {
// unchecked cast !!!
S s = (S) this.clone();
s.doSomething();
return s;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
static class Sub extends Super {
// override behavior
@Override
public void doSomething() {
super.doSomething();
super.doSomething();
}
}
public static void main(String[] args) throws Exception {
Sub s = new Sub();
System.out.printf("%d => %d%n", s.getState(), s.f().getState());
}

带输出:

0 => 2

在这里,f返回的不是Super而是扩展Super的任何S,现在f将在组操作(每个超类操作(下关闭。但必须受到保护,因为在编译时未选中该强制转换。例如。

Sub s = new Sub();
Sub t = s.f();  // ok
Sub2 u = s.f(); // java.lang.ClassCastException !!!!

这就是为什么f操作应该受到保护而不是公开暴露的原因。

所以简短的回答是,是的,你可以低调。前提是运行时的实际对象类型也是您要向下投射的目标类型。

当您的示例运行时return (Sub) super.f();它将失败并显示java.lang.ClassCastException因为在这种情况下,实际对象不是Sub,而是Super。您可以通过修改方法来查看这一点,如下所示:

Sub f() {
Super sup = super.f();
if(sup instanceof  Sub) {
System.out.println("Instance of Sub");
} else {
System.out.println("Not an instance of Sub, it is a : " +  sup.getClass());
}
return (Sub) sup;
}

这里的原因是因为在Super f()实例化纯Super对象并返回它时,因此实际的对象类型是Super,并且由于Super不是Sub,因此我们不能将其转换为Sub

下面是一个示例:

public class Animal {
private String type;
private int age;
public Animal(String type, int age) {
this.type = type;
this.age = age;
}
public int getAge() {
return age;
}
}
public class Cat extends Animal {
public Cat(String type, int age) {
super(type, age);
}
}
public class Dog extends Animal {
public Dog(String type, int age) {
super(type, age);
}
}
public class Vet {
public static void printHumanYears(Animal animal) {
if(animal instanceof Dog) {
Dog dog = (Dog) animal;
System.out.println("Dogs age : " + dog.getAge() * 7);
} else if(animal instanceof Cat) {
Cat cat = (Cat) animal;
System.out.println("Cat age : " + cat.getAge() * 5);
} else {
System.out.println("Not sure how to calculate age for a " + animal.getClass());
}
}
public static void main (String [] args) {
Dog dog = new Dog("Collie", 10);
Cat cat = new Cat("Tabby", 20);
Animal animal = new Animal("Animal", 20);
printHumanYears(dog);
printHumanYears(cat);
printHumanYears(animal);
}

}

当调用方法printHumanYears(Animal animal)时,它会传递一个Animal对象,然后我们检查这个Animal对象是哪个实例,然后相应地向下转换。我们没有得到ClassCastException的原因是目标对象(DogCat(是在运行时创建的。当我们传递一个Animal它不能向下转换为DogCat,因为它在最初实例化时都不是这些。

好记住:

  • 强制转换不会更改实际对象类型。
  • 强制转换仅更改参照类型。
  • 向上投射始终是安全的,永远不会失败。
  • 向下投射是不安全的,可能会抛出ClassCastException,所以使用instanceof来检查目标类型是否也是您要投射的,这始终是一个好主意。

最新更新