版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Never_Blue/article/details/70809697
1、instanceof运算符的陷阱
instanceof是一个非常简单的运算符。instanceof运算符的前一个操作数通常是一个引用类型的变量,后一个操作数通常是一个类(也可以是接口,可以把接口理解成一种特殊的类),它用于判断前面的对象是否是后面的类或其子类、实现类的实例。如果是,则返回true;否则,返回false。
根据Java语言规范,使用instanceof运算符有一个限制:instanceof运算符前面操作数的编译时类型必须是如下三种情况。
- 要么与后面的类相同。
- 要么是后面类的父类。
- 要么是后面类的子类。
一旦instanceof运算符通过了编译,程序进入运算阶段。instanceof运算返回的结果与前一个操作数(引用变量)实际引用的对象的类型有关,如果它实际引用的对象是第二个操作数的实例,或者是第二个操作数的子类、实现类的实现,那么instanceof运算的结果返回true,否则返回false。
在极端情况之下,instanceof前一个操作数所引用对象的实际类型就是后面的类型,但只要它的编译时类型既不是第二操作数的类型,也不是第二个操作数的父类、子类,程序就没法通过编译。
public class InstanceofTest {
public static void main(String[] args) {
Object str = "Java对象";
Math math = (Math)str; //①
System.out.println("字符串是否是String的实例:" + (math instanceof String)); //② 报错:Incompatible conditional operand types Math and String
}
}
当编译器编译Java程序时,编译器无法检查引用变量实际引用对象的类型,它只检查该变量的编译时类型。对于②行代码来说,math的编译时类型是Math,Math既不是String类型,也不是String类型的父类,还不是String类型的子类,因此程序没法通过编译。至于math实际引用对象的类型是什么,编译器并不关心。
至于①行代码出为何没有出现编译错误,这和强制转型的机制有关。对于Java的强制转型而言,也可以分为编译、运行两个阶段来进行分析。
·
编译阶段,强制转型要求被转型变量的编译时类型必须是如下三种情况之一。
- 被转型变量的编译时类型与目标类型相同。
- 被转型变量的编译时类型是目标类型父类。
- 被转型变量的编译时类型是目标类型子类。在这种情况下可以自动向上转型,无须强制转换。
·
运行
阶段,被转型变量所引用对象的实际类型必须是目标类型的实例,或者是目标类型的子类、实现类的实例,否则在运行时将引发ClassCastException异常。
从上面的分析可以看出,对于①行代码来说,编译时不会出现错误,因为str引用变量的编译时类型是Object,它是Math类的父类。但是str运行时类型是String,它与Math类没有任何关系,所以运行时将会引发ClassCastException异常。
public class ConversionTest {
public static void main(String[] args) {
Object obj = "Hello"; //obj编译时类型是Object,运行时类型是String。
String objStr = (String)obj; //因为obj编译时类型是Object,所以可以通过编译。因为obj运行时类型是String,objStr也是String类,所以可以运行。
System.out.println(objStr);
Object objPri = new Integer(5); //objPri编译时类型是Object,运行时类型是Integer。
String str = (String)objPri; //因为objPri编译时类型是Object,所以可以通过编译。因为obj运行时类型是Integer,objStr是String类,所以会引发异常。
System.out.println(str);
String s = "Java对象"; //s编译时类型和运行时类型都是String。
Math m = (Math)s; //String类不是Math的子类,也不是Math的父类,所以会导致编译错误。
}
}
public class NullInstaceof {
public static void main(String[] args) {
String s = null;
System.out.println("null是否是String类的实例:" + (s instanceof String));
}
}
输出结果为:
null是否是String类的实例:false
虽然null可以作为所有引用类型变量的值,但对于s引用变量而言,它实际上并未引用一个正在的String对象,因此程序会输出false。使null调用instanceof运算符时返回false是非常有用的行为,因为instanceof运算符有一个额外的功能:它可以保证第一个操作数所引用的对象不是null。如果instanceof告知一个引用变量是某个特定类型的实例,那么就可以将其转型为该类型,不用担心会抛出ClassCastException或NullPointerException异常。