发现好多初学的朋友对多态部分总是比较模糊,小弟今天在看朋友的学习笔记的时候也发现这个问题,所以借我朋友笔记上的问题,发表一下自己的看法,先假设有继承关系如下的类: 代码:
Animal
|
Mammal
|
---------------------------------------------------------------
| | | |
Dog Cat Raccoon SwampThing
(implements (implements
Washer) Washer)
? interface Washer {}
? class Animal {}
? class Mammal extends Animal {}
? class Dog extends Mammal {}
? class Cat extends Mammal implements Washer {}
? class Raccoon extends Mammal implements Washer {}
? class SwampThing extends Mammal {}
下面的为对这几个类的应用和我朋友的笔记:
public class Test14 {
public static void main(String[] a) {
//----------------------------------
Dog rover,fido;
Animal anim;
rover =new Dog();
anim=rover; //理解成Animal anim = new Dog();
fido =(Dog)anim; //这段话能够通过编译和运行!!!!!
//fido =anim;不能通过编译
//----------------------------------
Washer wawa=new Cat();
SwampThing pogo;
pogo=(SwampThing)wawa;
//这句话能够通过编译,但不能运行
//两个毫不相干的类对象,居然能够通过编译?
//经另外测试,任意两个不相干的类对象cast是会出现编译错误的,
//所以,上面的类可能是继承了同一个类的缘故?
我的解释:
首先,我们知道,超类类型的变量可以引用一个子类类型的对象,即
代码:
Animal anim = new Dog();
这样是完全可以的,但是要注意的是编译器只检查且承认定义时候的类型,所以这样定义并赋值的anim变量不能调用Dog类中特有的实例字段和方法,这就是我们通常所说的隐藏,如果我们仍然要用到这些被隐藏的东西怎么办呢,那就是后面我们看到的笔者认为费解的那句:
代码:
fido =(Dog)anim;
其实这样只是恢复了被隐藏的功能,并不是把超类对象赋给子类变量,当然,编译器并不管最终这个造型转换能否成功,编译器现在只知道你在把一个Dog型的对象赋值给一个Dog型的变量,这当然没有问题,同样,后面的这样3句
代码:
Washer wawa=new Cat();
SwampThing pogo;
pogo=(SwampThing)wawa;
也很好理解,编译器只知道你在试图把一个SwampThing型的对象赋值给一个SwampThing型的变量,这当然没问题,注意对象wawa原来是什么类型编译器并不管(这句话并不全面,具体见下面),它只知道不管什么类型已经强制转换成了SwampThing型,至于这种转换会否成功则会在运行时判断,如果不能成功转换当然会抛出异常,这就是为什么总说造型前请用instanceof判断对象本身是否属于要转换的类!
让我们探讨的再深一点:
注意笔者上边最后注释的话是不对的,
Cat类与SwampThing类不是毫不相干的,它们都实现了Washer接口,注意变量wawa被定义的类型并不是Cat类型,而是Washer接口类型,所以
代码:
pogo=(SwampThing)wawa;
这句话才有可能成功,如果变量wawa被定义为Cat类型,则上面的造型绝对不可能成功,即编译也无法通过!由此我们知道,编译器也会为造型做出判断,不过编译器只会判断被造型的对象有没有可能被造型成功,就象上面的代码,wawa定义的时候是Washer类型,任何实现了这个接口的类的对象都可以被wawa引用,而SwampThing类也实现了这个接口,所以当编译器执行到上面的代码时,编译器只通过对wawa的定义检查,认为wawa有可能指向SwampThing类型的对象,这个造型有可能成功,所以可以被编译成功!
还可以再深一点讨论:
上面例子中笔者注释了这么一句话:
//fido =anim;不能通过编译
通过阅读之前的代码我们知道变量anim指向的实际对象和fido的定义类型是一致的,都是Dog,但是不能编译,这是因为编译器只检查anim的定义,发现是Dog的超类,所以我们必须要先类型转换,但是假设可以通过某种方法强制通过编译,那么这句话运行时反而没有问题因为实际类型是匹配的,这种情况可以说正好与上面讨论的能通过编译但是运行出错的情况相反!
总结:正是java中编译器只检查定义类型,运行时检查实际类型的双重检查方式造成了面向对象中多态这种即规范同时又非常有灵活性的机制