前言
前文对static关键字进行了介绍,读者可以知道static关键字是一个可以控制成员变量、成员方法以及代码块的加载顺序和作用范围。我们在平时看源码的时候会时不时看到instanceof关键字,Java开发者对它的第一印象就是:instanceof是用于测试一个对象是否是另一个类的实例。
本文主要对instanceof关键字进行介绍:
- 了解instanceof
- instanceof作用的类型
- instanceof作用于null
- instanceof作用于实例对象、实现类、直接或间接子类
- instanceof的实现策略
了解instanceof
instanceof严格来说是Java中的一个双目运算符,用于测试一个对象是否为一个类的实例,用法如下:
boolean ans = obj instanceof Class
其中,obj作为一个对象,Class表示一个类或一个接口,当obj为Class对象时,或者是接口的实现类、或者是Class的直接或间接子类,ans都会返回true,否则返回false。
也就是说,如果用instanceof关键字做判断的时候,instanceof操作符的左右操作数必须有继承关系或实现关系。
其实,在进行编译之前,编译器会检查obj是否能转换成右边的Class类型,如果不能转换则直接报错,如果不能确定类型,则编译之后,视运行结果而定。
instanceof作用的类型
int a = 1;
System.out.println(a instanceof Integer); //编译不通过
System.out.println(a instanceof Object); //编译不通过
可以看出,instanceof运算符只能对引用类型进行判断,不能是基本类型。
instanceof作用于null
System.out.println(null instanceof Integer);//false
我们知道,Java有两种数据类型,一种是基本数据类型,笔者在 Java的基本数据类型、拆装箱(深入版)有详细介绍,另一种是引用类型,包括类、接口、数组等。而在Java中还有一种特殊的null类型。
null类型没有名字,所以不可能声明null类型的变量或者转换为null类型,null引用是null类型表达式唯一可能的值,null引用也可以转换为任意引用类型。总的来说,null是可以成为任意引用类型的特殊符号。
在JavaSE规范中对instanceof运算符的规定就是:如果判断对象为null,那么将返回false。
instanceof作用于实例对象、实现类、直接或间接子类
instanceof作用于实例对象
Integer a = new Integer(1);
System.out.println(a instanceof Integer);
这是最普遍的一种用法,用于判断a对象是否是Integer类的实例。
instanceof作用于实现类
举个栗子:
了解Java集合类的小伙伴都知道,List作为上层接口,有不少经典的实现类如ArrayList、LinkedList等。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
在此我们可以使用instanceof关键字来判断,某个对象是否是List接口的实现类:
LinkedList linkedList = new LinkedList();
System.out.println(linkedList instanceof LinkedList); //true
当然,反过来也是返回true。
List list = new LinkedList<>();
System.out.println(list instanceof LinkedList);
instanceof作用于直接或间接子类
上面提到过用instanceof关键字做判断的时候,instanceof操作符的左右操作数必须有继承关系或实现关系。用暹罗猫和病毒来举个栗子:
interface Animal{} //动物接口
class Feline implements Animal{} //猫科类
class Cat extends Feline{} //猫类
class SiameseCat extends Cat{} //暹罗猫类
class Virus{}
public class TestInstanceof{
public static void main(String[] args){
System.out.println("Cat的对象是谁的实例?");
instanceofTest(new Cat());
System.out.println("----------------------------------------");
System.out.println("SiameseCat的对象是谁的实例?");
instanceofTest(new SiameseCat());
System.out.println("----------------------------------------");
System.out.println("Virus的对象是谁的实例?");
instanceofTest(new Virus());
}
public static void instanceofTest(Object o){
if(o instanceof Virus)
System.out.println(o.getClass()+"类的实例,是类Virus的实例");
if(o instanceof SiameseCat)
System.out.println(o.getClass()+"类的实例,是类SiameseCat的实例");
if(o instanceof Cat)
System.out.println(o.getClass()+"类的实例,是类Cat的实例");
if(o instanceof Feline)
System.out.println(o.getClass()+"类的实例,是类Feline的实例");
if(o instanceof Animal)
System.out.println(o.getClass()+"类的实例,是类Animal的实例");
if(o instanceof Object)
System.out.println(o.getClass()+"类的实例,是类Object的实例");
}
}
上面的程序,展示出来的继承树是如下图所示:
运行结果为:
Cat的对象是谁的实例?
class Cat类的实例,是类Cat的实例
class Cat类的实例,是类Feline的实例
class Cat类的实例,是类Animal的实例
class Cat类的实例,是类Object的实例
----------------------------------------
SiameseCat的对象是谁的实例?
class SiameseCat类的实例,是类SiameseCat的实例
class SiameseCat类的实例,是类Cat的实例
class SiameseCat类的实例,是类Feline的实例
class SiameseCat类的实例,是类Animal的实例
class SiameseCat类的实例,是类Object的实例
----------------------------------------
Virus的对象是谁的实例?
class Virus类的实例,是类Virus的实例
class Virus类的实例,是类Object的实例
从结果我们可以看到,某个类(接口也可以看成是一种特殊的类,但类和接口只是类型上的区别而已)的对象是不是其他类(或接口)的实例,只需按上图箭头方法,以此对象所在的类为起点到达继承树分支终点,沿途经过的类(包括本类或接口)都是该对象的实例。
但是需要注意的是,在判断某个类(或接口)的对象是不是其他类(或接口)的实例,一定要先进行向上转型,然后才可以用instanceof关键字进行判断。举个栗子:
interface Animal{} //动物接口
class Feline implements Animal{} //猫科动物类
class Canine implements Animal{} //犬科动物类
public class TestInstanceof{
public static void main(String[] args) {
Animal a = new Feline();
System.out.println(a instanceof Feline); //true
System.out.println(a instanceof Canine); //false
}
}
上述程序的继承树为:
在判断接口Animal的对象a是不是类Canine的实例时,因为没有先进行向上转型,所以instanceof关键字判断的时候返回为false。想了解向上转型的朋友可以参考这篇文章:8.JAVA-向上转型、向下转型
instanceof的实现
其实,在进行编译之前,编译器会检查obj是否能转换成右边的Class类型,如果不能转换则直接报错,如果不能确定类型,则编译之后,视运行结果而定。举个栗子:
Feline feline = new Feline();
System.out.println(feline instanceof String); //编译错误
System.out.println(feline instanceof List); //false
System.out.println(feline instanceof List<?>); //false
System.out.println(feline instanceof List<Feline>); //编译错误
虽然Feline很显然不能转换为String对象,但是为什么feline instance List却能通过编译?而feline instanceof List 又不能通过编译?
instanceof的执行过程
我们可以在Java语言规范JavaSE 8版中看到这么一段话:
大家需要注意的是演示代码的上一句话:
At run time, the result of the
instanceof
operator istrue
if the value of the RelationalExpression is notnull
and the reference could be cast to the ReferenceType without raising aClassCastException
. Otherwise the result isfalse
.拙译:在运行时,如果需判断的参数不为null且转换类型的时候,不会触发ClassCastException,那么instanceof的操作结果为true,否则为false。
所以用伪代码描述,就是:
boolean result;
if (obj == null) {
result = false;
} else {
try {
T temp = (T) obj; // checkcast
result = true;
} catch (ClassCastException e) {
result = false;
}
}
总结一下:
- 如果instanceof运算符的obj操作数的类型必须是引用类型或空类型,否则会发生编译时的错误;
- 如果obj强制转换为T时发生编译错误,则关系表达式的instanceof同样会产生编译时错误;
- 在运行时,如果T的值不为null,且obj可以转换为T而不引发ClassCastException,则instanceof运算符的结果为true。
可以知道,因为(String)feline 不能通过编译,而(List)feline可以通过编译,所以才会出现上述问题。
instanceof的执行策略
而instanceof的执行策略可以在JavaSE 8版本:instanceof实现算法中了解。
因为资料中涉及了JVM中的操作,翻译出来的效果可能会有点云里雾里的感觉,有兴趣的朋友可以自行查看资料。在此大致总结instanceof的执行策略(笔者拙译,水平不高,欢迎指正):
- 如果objectref为null,则直接返回false;否则就需要检查,objectref代表的类型对象S是否为T的子类型;
- 如果S == T,则返回true;
- 如果S不属于上述两种情况,则需要分情况来进行“子类型检查”了,而Java语言的类型系统中包括数组类型、接口类型和普通类类型,三者的子类型规定都不一样,须分开讨论。
- 如果S是普通类类型:
- 如果T是类类型,则S必须是T类型或者是T的子类;
- 如果T是接口类型,则S必须实现T。
- 如果S是接口类型:
- 如果T是类类型,那么T必须是Object类型的;
- 如果T是接口类型,那么T必须是S类型或者是S的父接口。
- 如果S是数组类型:
- 如果T是类类型,那么T必须是Object类型的;
- 如果T是接口类型,那么T必须是由数组实现的接口之一;
- 如果T也是数组类型,那么S和T必须是同一种原始类型,且S和T是引用类型的,S在转换成T的时候符合运行时的规则。
- 如果S是普通类类型:
结语
英语真的太重要了,而且水平也决定了看问题的角度和高度。知乎上这位大佬的回答很值得去看,但是能看明白又是一回事了,屁股决定脑袋。
参考资料: