多态性
Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量
时使用的类型决定,运行时类型由实际
赋给该变量的对象决定。如果编译时类型和运行时类型不
一致,就可
能出现所谓的多态(Polymorphism)。
先看下面程序。
上面程序的main()方法中显式创建了三个引用变量,对于前两个引用变量bc和sc,它们编译时类
型和运行时类型完全相同,因此调用
它们的成员变量和方法非常正常,完全没有任何问题。但第
三个引用
变量ploymophicBc则比较特殊,它的编译时类型是BaseClass,而运行
时类型是
SubClass,当调用该引用变量的test()方法(BaseClass类中
定义了该方法,子类SubClass覆盖了
父类的该方法)时,实际执行的
是SubClass类中覆盖后的test()方法,这就可能出现多态了。
因为子类其实是一种特殊的父类,因此Java允许把一个子类对象
直接赋给一个父类引用变量,
无须任何类型转换,或者被称为向上转
型(upcasting),向上转型由系统自动完成。
当把一个子类对象直接赋给父类引用变量时,例如上面的
BaseClass ploymophicBc=newSubClass();,这个ploymophicBc引用
变量的编译时类型是
BaseClass,而运行时类型是SubClass,当运行时
调用该引用变量的方法时,其方法行为总是表
现出子类方法的行为特
征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、
调用
同一个方法时呈现出多种不同的行为特征,这就是多态。
上面的main()方法中注释了ploymophicBc.sub();,这行代码会在编译时引发错误。虽然
ploymophicBc引用变量实际上确实包含sub()方
法(例如,可以通过反射来执行该方法),但因为
它的编译时类型为
BaseClass,因此编译时无法调用sub()方法。
与方法不同的是,对象的实例变量则不具备多态性。比如上面的ploymophicBc引用变量,程序
中输出它的book实例变量时,并不是输 出SubClass类里定义的实例变量,而是输出BaseClass类
的实例变量。
注意:引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码 时,引用变量只能调用声明该变量时所用类里包含的方法。例如, 通过Object p=new Person()代码定义一个变量p,则这个p只能调用 Object类的方法,而不能调用Person类里定义的方法。注意: 通过引用变量来访问其包含的实例变量时,系统总是试图访问 它编译时类型所定义的成员变量,而不是它运行时类型所定义的成 员变量。
引用变量的强制类型转换
编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型的方法,
即使它实际所引用的对象确实包含该方
法。如果需要让这个引用变量调用它运行时类型的方法,
则必须把它
强制类型转换成运行时类型,强制类型转换需要借助于类型转换运算
符。
类 型 转 换 运 算 符 是 小 括 号 , 类 型 转 换 运 算 符 的 用 法 是 :(type)variable,这种用
法可以将variable变量转换成一个type类型
的变量。前面在介绍基本类型的强制类型转换时,已经
看到了使用这
种类型转换运算符的用法,类型转换运算符可以将一个基本类型变量
转换成另一个
类型。
除此之外,这个类型转换运算符还可以将一个引用类型变量转换成其子类类型。这种强制类型转换不是万能的,当进行强制类型转换 时需要注意:➢ 基本类型之间的转换只能在数值类型之间进行,这里所说的数 值类型包括整数型、字符型和浮点型。但数值类型和布尔类型 之间不能进行类型转换。➢ 引用类型之间的转换只能在具有继承关系的两个类型之间进 行,如果是两个没有任何继承关系的类型,则无法进行类型转 换,否则编译时就会出现错误。如果试图把一个父类实例转换 成子类类型,则这个对象必须实际上是子类实例才行(即编译 时类型为父类类型,而运行时类型是子类类型),否则将在运 行时引发ClassCastException异常。
下面是进行强制类型转换的示范程序。下面程序详细说明了哪些情况可以进行类型转换,哪些情况
不可以进行类型转换。
考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判
断是否可以成功转换。例如,上面
的var str=(String)objPri;代码运行时会引发
ClassCastException
异常,这是因为objPri不可转换成String类型。为了让程序更加健
壮,可以将
代码改为如下:
在进行强制类型转换之前,先用instanceof运算符判断是否可以成功转换,从而避免出现
ClassCastException异常,这样可以保证程
序更加健壮。
注意:当 把 子 类 对 象 赋 给 父 类 引 用 变 量 时 , 被 称 为 向 上 转 型(upcasting),这种转型总是可以成功的,这也从另一个侧面证实 了子类是一种特殊的父类。这种转型只是表明这个引用变量的编译 时类型是父类,但实际执行它的方法时,依然表现出子类对象的行 为方式。但把一个父类对象赋给子类引用变量时,就需要进行强制 类型转换,而且还可能在运行时产生ClassCastException异常,使 用instanceof运算符可以让强制类型转换更安全。
instanceof和类型转换运算符一样,都是Java提供的运算符,与+、-等算术运算符的用法大致相
似,下面具体介绍该运算符的用法。
instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以
是接口,可以把接口理解成一种特
殊的类),它用于判断前面的对象是否是后面的类,或者其子
类、实
现类的实例。如果是,则返回true,否则返回false。
在使用instanceof运算符时需要注
意:instanceof运算符前面操
作数的编译时类型要么与后面的类相同,要么与后面的类具有父子
承
关系,否则会引起编译错误。下面程序示范了instanceof运算符的
用法。
上面程序通过Object hello="Hello";代码定义了一个hello变量,这个变量的编译时类型是Object
类,但实际类型是String。因为
Object类是所有类、接口的父类,因此可以执行hello instanceof
String和hello instanceof Math等。
但如果使用var a="Hello";代码定义的变量a,就不能执行a
instanceof Math,因为编译器会推断出a
的编译类型是String,
String类型既不是Math类型,也不是Math类型的父类,所以这行代码
编译就
会出错
instanceof运算符的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实
例,是否可以成功转换,从而保证
代码更加健壮。
instanceof和(type)是Java提供的两个相关的运
算符,通常先用
instanceof判断一个对象是否可以强制类型转换,然后再使用(type)
运算符进行强
制类型转换,从而保证程序不会出现错误