java引用变量有两个类型:一个是编译型类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism).
变态: 同一个类型的实例、在执行同一个方法,个别对象存在着变异的行为特征。
多态:同一个类型的实例、在执行同一个方法,呈现出多种的行为特征。
一、多态性
例子:
1 class BaseClass 2 { 3 public int book=6; 4 public void base() 5 { 6 System.out.println("父类中的普通方法"); 7 } 8 public void test() 9 { 10 System.out.println("父类的被覆盖的方法"); 11 } 12 } 13 14 public class SubClass extends BaseClass 15 { 16 //重新定义一个book实例变量,隐藏了父类的book实例变量 17 public String book="疯狂Java"; 18 //重写父类方法 19 public void test() 20 { 21 System.out.println("子类覆盖父类的方法"); 22 } 23 public void sub() 24 { 25 System.out.println("子类普通方法"); 26 } 27 public static void main(String[] args) 28 { 29 //创建父类对象,下面编译时的类型和运行时完全一样不存在多态 30 BaseClass bc=new BaseClass(); 31 System.out.println(bc.book); 32 //调用BaseClass的实例方法 33 bc.base(); 34 bc.test(); 35 36 //创建子类对象,下面编译时的类型和运行时完全一样不存在多态 37 SubClass sc=new SubClass(); 38 System.out.println(sc.book); 39 //调用SubClass的实例方法 40 sc.base(); 41 sc.test(); 42 43 //下面编译时的类型和运行时完全不一样,多态发生 44 BaseClass polyBc=new SubClass();//子类变量赋给父类变量,小到大,自动转换 45 System.out.println(polyBc.book);//表示访问的是父类对象的实例变量 46 47 polyBc.base(); 48 polyBc.test(); 49 50 //由于polyBc的编译时类型为BaseClass,BaseClass类中没有sub()方法,下面编译将出错 51 //polyBc.sub(); 52 } 53 }
运行结果:
---------- 运行Java捕获输出窗 ---------- 6 父类中的普通方法 父类的被覆盖的方法 疯狂Java 父类中的普通方法 子类覆盖父类的方法 6 父类中的普通方法 子类覆盖父类的方法 输出完成 (耗时 0 秒) - 正常终止
对于前两个引用变量bc、sc,它们编译时的类型与运行时的类型相同,因此可以正常的访问成员变量和方法。
对于polyBc引用变量,编译时类型为BaseClass,运行时类型为SubClass,当调用test()方法时,实际上是调用的SubClass的test()方法,这就可能出现多态。
▲向上转型:子类是一种特殊的父类,因此子类对象可以直接赋给父类变量
自动完成
▲向下转型:父类变量赋给子类变量
强制转换 (类型)变量名
值得注意的是,对象的实例变量和方法不同,它不具有多态性。换句话说,通过引用变量访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。比如上面的polyBc引用变量,访问它的book变量时,输出的并不是SubClass类中的实例变量,而是输出BaseClass类中的实例变量。
二、引用变量的强制转换
编写Java程序时,引用变量只能调用它编译时的类型的方法,而不能调用它运行时类型的方法,即使它实际所引用的对象确实包含该方法。如果要让这个引用变量调用它运行时类型的方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型转换符。
类型转换符用法:
1 (type) variable; 2 //将variable变量转换成一个type类型的变量
类型转换符的两个用法:
★将一个基本类型变量转换成另一个类型。
▲基本类型之间转换只能在数值类型之间转换,这里数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不能进行转换。
★将一个引用变量转换成其子类类型。
▲引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果两个没有任何继承关系的类型,则无法进行类型转换,否则出现编译错误。
1 public class ConversionTest 2 { 3 public static void main(String[] args) 4 { 5 var d=13.4; 6 var l=(long)d; 7 System.out.println(l); 8 //下面试图将数值类型转换为布尔类型 9 //var b=(boolean)d;// 错误: 不兼容的类型: double无法转换为boolean 10 11 Object obj="Hello"; 12 //obj的编译类型为Object,运行时类型为String 13 var objStr=(String)obj; 14 System.out.println(objStr); 15 16 //objPri编译时类型为Object,运行时类型为Integer 17 //Object和Integer存在着继承关系 18 Object objPri=Integer.valueOf(5); 19 20 //Integer引用变量和String引用变量之间不存在继承关系 21 var str=(String) objPri; 22 //Exception in thread "main" java.lang.ClassCastException: class java.lang. 23 //Integer cannot be cast to class java.lang.String 24 } 25 }
考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前先通过instanceof运算符来判断是否可以成功转换。例如上面报错代码可以进行改为:
if(objPri instanceof String) { var str=(String) objPri; }
三、instanceof运算符
instanceof运算符的前一个操作数是一个引用类型变量,后一个操作数通常是一个类(也可以是一个接口,接口可以理解是一种特殊的类),它用于判断前面对象是否是后面的类,或者子类、实现类的实例。如果是返回true,否则返回false。
注意:instance运算符前面操作数的编译类型要么与后面的类型相同,要么与后面的类具有父子继承关系,否则会出现编译错误。
1 public class InstanceTest 2 { 3 public static void main(String[] args) 4 { 5 //Object是所有类的父类,但hello的实际类型是String 6 Object hello="Hello"; 7 System.out.println("字符串是否是Object的实例:"+(hello instanceof Object)); 8 System.out.println("字符串是否是String的实例:"+(hello instanceof String)); 9 System.out.println("字符串是否是Math的实例:"+(hello instanceof Math)); 10 } 11 } 12 ---------- 运行Java捕获输出窗 ---------- 13 字符串是否是Object的实例:true 14 字符串是否是String的实例:true 15 字符串是否是Math的实例:false 16 17 输出完成 (耗时 0 秒) - 正常终止