7.1 instanceof运算符的陷阱
instanceof运算符的前一个操作数通常是一个引用类型的变量,后一个 操作数通常是一个类(也可以是接口,可以把接口当成一种特殊的类),它用于判断前面的对象是否是后面的类或其子类、实现类的实例。如果是,返回true,否则返回false。
根据java语言规范,使用instanceof运算符有一个限制:instanceof运算符前面操作数的编译时类型必须是如下3种情况:
- 要么与后面的类相同;
- 要么是后面的类的父类;
- 要么是后面类型的子类
如果前面操作数的编译时类型与后面的类型没有任何关系,程序将无法通过编译。
观察下面程序:
public class InstanceofTest { public static void main(String[] args) { Object str = "疯狂Java讲义"; // 执行强制类型转换,让math引用原来str引用的对象 Math math = (Math) str; // 1 System.out.println("字符串是否是String的实例:" + (math instanceof String)); //2 } }
粗看之下,可能会得到一个结论:这个程序不能通过编译,编译期应该在1行代码处提示编译错误,因为1行代码尝试把一个String强制转型为Math。但是编译该程序时,却发现编译期提示如下的错误:
InstanceofTest.java:6: 不可转换的类型 找到: java.lang.Math 需要: java.lang.String System.out.println("字符串是否是String的实例:" + (math instanceof String)); ^ 1 错误
至于1行出的代码为何没有出现编译错误,这和强制转型的机制有关。对于Java的强制转型而言,也可以分为编译、运行两个阶段来分析它。
- 在编译阶段,强制转型要求被转型变量的编译时类型必须是如下3种情况之一:
- 被转型变量的编译时类型与目标类型相同;
- 被转型变量的编译时类型是目标类型的父类;
- 被转型变量的编译时类型是目标类型子类,这种情况下可以自动向上转型,无需强制转换。
如果被转型变量的编译时类型与目标类型没有任何继承关系,编译期将提示错误。通过上面的分析可以看出,强制类型转换的编译阶段只关心引用变量的编译时类型,至于该引用变量实际引用对象的类型,编译器并不关心,也没法关心。
- 在运行阶段,被转型变量所引用对象的时实际类型必须是目标类型的实例,或者是目标类型的子类、实现类的实例,否则在运行时将引发ClassCastException异常。
另外如果给一个引用变量赋值null,例如以下程序,将返回false:
public class InstanceofTest { public static void main(String[] args) { String str = null; System.out.println("null是否是String的实例:" + (str instanceof String)); } }