考虑:
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();
( 中提供的示例中,T
Sub
,R
Super
。
现在让我们解决问题的第二部分:
不重复 f 的定义
实现此目的的一种方法是将super
和Sub
之间通用的代码移动到单独的方法:
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
的原因是目标对象(Dog
或Cat
(是在运行时创建的。当我们传递一个Animal
它不能向下转换为Dog
或Cat
,因为它在最初实例化时都不是这些。
好记住:
- 强制转换不会更改实际对象类型。
- 强制转换仅更改参照类型。
- 向上投射始终是安全的,永远不会失败。
- 向下投射是不安全的,可能会抛出
ClassCastException
,所以使用instanceof
来检查目标类型是否也是您要投射的,这始终是一个好主意。