example 1:
public class Animal {
public void move() {
System.out.println("Animal: generic move");
}
}
public class Horse extends Animal {
public void move() {
System.out.println("Horse: run");
}
public void jump() {
System.out.println("Horse: jump");
}
}
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
}
}
}
当我们运行UseAnimal的时候,结果如下:
Animal: generic move
Horse: run
如上所述,这样做的一个好处是,我们只需要在UseAnimals中定义一个move(Animal a)方法,就可以对所有Animal进行操作, 无须理会具体的类型。如果我们animals数组中增加一个Dog类型,我们一样可以调用 move方法,只要Dog类重写了Animal中的move方法。
让我们回到类型转换的话题上来。在UseAnimals的main方法中. 在Java中,类及其子类的关心可以形象的看成一棵树,其中父类在上,子类在下。向上裁剪顾名思义就是把一个子类裁剪为父类,这样做的结果就是子类将“丢失”部分成员,而只能使用和父类共同拥有的成员,更准确的说是共有的API.
比如一下代码是不能通过编译的:
Animal animal = new Horse(); //upcasting
animal.jump(); //compiler complains, it cannot see "jump()"
在Java中,向上裁剪总是允许的,不需要强制指定转换类型。 道理显而易见,把一个较宽的类型转换为一个交窄的类型总是可以的! 与upcasting 相对应的是downcasting。在上述例子中,Horse定义了一个jump方法。如果我们确实知道我们处理的是一个Horse对象,想调用它的jump方法,该怎样做?
请看一下代码:
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
if (a instanceof Horse) {
Horse h = (Horse)a;
h.jump();
}
}
}
}
为了实现这个目的,我们必须显示的把一个Animal的引用转化为一个Horse的引用,而且我们必须清楚的知道,我们要转化的对象确实是一个Horse对象,这就是为什么要使用instanceof的原因了。如果没有instanceof这个判断,会怎么样?
请看修改过的代码:
public class TestAnimals {
public static void move(Animal a) {
a.move();
}
public static void main(String[] args) {
Animal[] animals = {new Animal(), new Horse(), new Animal()};
for (Animal a:animals) {
move(a);
Horse h = (Horse)a;
h.jump();
}
}
}
这段代码可以(居然可以!!)通过编译,可是运行的时候就会出错:
Animal: generic move
Exception in thread "main" java.lang.ClassCastException: com.my.Animal cannot be cast to com.my.Horse
at com.my.TestAnimals.main(TestAnimals.java:12)
oop!你也许会抱怨为什么编译器不能发现这个错误,因为很明显, 怎能把一个Horse对象转换成一个Animal对象呢? 难道是把对象放在数组中编译器发现不了? 事实上编译器允许你这样做:
Animal a = new Animal()
Horse h = (Horse)a;
这是因为编译器只能确定两个对象是否在一棵类型树中,而无法检测它们具体的关系。假如你尝试这样做:
Animal a= new Animal()
Stirngs s = (String)a;
编译器会提示你无法把一个Animal类型转换为String类型。 但是当待转换类型和转换后的类型是在同一棵类型树的时候,情况就不同了,因为它有可能是正确的,也有可能是错误的,一切只有等到运行时才能发现。再来看以下代码:
(假设Horse有一个FlyableHorse的子类)
Animal a = new FlyableHorse()
Horse h = (Horse)a;
这两行代码可以通过编译,而且运行时也不会出错。因为a 指向的是Horse的子类FlyableHorse, 当然可以把一个FlyableHorse对象裁剪为一个Horse对象。 记住, 一切都在运行的时候才知道!!!
所以我们可以总结如下:
在进行向上裁剪时,一切都是自然而然的,安全的,你不需要做任何东西
在进行向下裁剪时,你必须显示的进行类型转换,而且你知道你现在做的事情是正确的。 编译器不会帮你纠正错误,一切只有运行的时候才知道。