现在的位置: 首页 > 综合 > 正文

正确理解java中引用类型剪裁(Casting)

2013年09月19日 ⁄ 综合 ⁄ 共 2760字 ⁄ 字号 评论关闭
Java一个很迷人的特性就是支持运行时多态(Polymophism). 这个特性省却了许多维护类型的烦恼。你可以使用一个父类的引用(reference)指向一个子类,然后在运行时调用子类的方法。这样无论以后你扩展了多少子类,子类的子类,都可以不更改任何代码使得程序继续运行,JVM在运行时会照顾一切。 为了说明这一点,请看下面例子:

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对象。 记住, 一切都在运行的时候才知道!!!

所以我们可以总结如下:

在进行向上裁剪时,一切都是自然而然的,安全的,你不需要做任何东西
在进行向下裁剪时,你必须显示的进行类型转换,而且你知道你现在做的事情是正确的。 编译器不会帮你纠正错误,一切只有运行的时候才知道。

抱歉!评论已关闭.