instanceof 实现原理

1 篇文章 0 订阅

instanceof 实现原理

刷面试题的时候,很多资料都会用题目+答案的情况展示给我们,比如:

//问:下面程序有什么问题吗?运行结果是什么?
"" instanceof Object;				//1.true
new String () instanceof String		//2.true
‘c’ instanceof Character			//3.false

并且在下面给我们解释如下:

注释 1 的返回值是 true,"" 是一个字符串,字符串继承自 Object,所以返回 true。
注释 2 的返回值是 true,因为一个类的对象当然是它的实例了。
注释 3 编译不通过,因为 'c' 是一个 char 类型,也就是一个基本类型,不是一个对象,instanceof 只能用于对象的判断,不能用于基本类型的判断。

这两种题目一下来了十几题,十几个结论,难道要一个个想原因或者背结论?别了,之前一点不了解mysql就想考《阿里巴巴开发规范》,结果发现是不行的,一是MySQL规范看的一头雾水,二是就算想背诵考试蒙混过关也是没意义的,所以就直接找到了朋友,听资深的。给我讲解1个小时。

第二天再重新看MySQL规范,都可以直接理解了,也不用背诵了。

同理。上面的题目一下十几个结论,真的这么多吗?肯定不是。

我们先把试题里面给的结论都拿下来。

注释 1 的返回值是 true,"demo" 是一个字符串,字符串继承自 Object,所以返回 true。

注释 2 的返回值是 true,因为一个类的对象当然是它的实例了。

注释 3 的返回值是 false,因为 Object 是父类,其对象当然不是 String 类的实例了,要注意的是这句话是可以编译通过的,只要 instanceof 关键字的左右两个操作数有继承或实现关系就可以编译通过。

注释 4 编译不通过,因为 'A' 是一个 char 类型,也就是一个基本类型,不是一个对象,instanceof 只能用于对象的判断,不能用于基本类型的判断。

注释 5 返回值是 false,因为这是 instanceof 特有的规则,若左操作数是 null 则结果直接返回 false,不再运算右操作数是什么类,这和我们经常用到的 equals、toString 方法不同。

注释 6 返回值是 false,因为 null 没有类型,所以即使做类型转换还是 null。

注释 7 编译通不过,因为 Date 类和 String 没有继承或实现关系,所以在编译时直接就报错了,instanceof 操作符的左右操作数必须有继承或实现关系,否则编译会失败。

注释 8 返回值是 false,因为虽然 T 是 String 类型且与 Date 之间没有继承或实现关系,但是 Java 泛型在编译成字节码时 T 已经被换成了 Object 类型了(泛型擦出),传递的实参是 String 类型而已。

注释 9 编译不通过,instanceof 的右操作符必须是一个接口或者类,null 啥都不是,所以咯。

注释 10 编译通不过,因为 Date 类和 String 没有继承或实现关系,所以在编译时直接就报错了,instanceof 操作符的左右操作数必须有继承或实现关系,否则编译会失败。

注释 11 返回值为 true,因为数组类型也可以使用 instanceof 来判断。

首先 instanceof 直接对应一条虚拟机指令 instanceof 且为 java 语法保留关键字,而非通过反射实现;instanceof 在底层实现上维护了主要超类型(继承深度)小于一个固定数值(一般为 7)的主数组和次要超类型(判断的时候需要 super 链遍历查找),然后在字节码使用特殊指令对常量池中的相关符号引用进行判断,从而来决定是否某个类或者派生类,以此返回 true 或 false。

这么多结论一下理解也很烦,那就从前浅入深看看源码之类的吧。

首先去官网(https://docs.oracle.com/)看下。具体地址(https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.20.2)。

instanceof操作符的关系表达式操作数的类型必须是引用类型或null类型;否则,将发生编译时错误。
如果instanceof操作符后面提到的引用类型不表示可具体化的引用类型,则是编译时错误(§4.7)。
如果将关系表达式转换为引用类型会被拒绝为编译时错误,那么instanceof关系表达式同样会产生编译时错误。在这种情况下,instanceof表达式的结果永远不可能是真的。
在运行时,如果关系表达式的值不为空,并且可以将引用转换为引用类型(§15.16),而不会引发ClassCastException,则instanceof操作符的结果为true。否则结果为假。

以上为翻译后的官网文档。

果然,结论只有4句,已经减少很多了。看得出来,这只是说了instanceof是怎么设计的,根本和实现原理不搭边。继续往后看。官网的demo也是告诉我们,两个类如果有直接或者间接继承,也就是有父子这两种关系,用instanceof不会编译错误,否则编译错误。没了。

那instanceof为什么会遇到编译错误呢。

用一个demo来说明instanceof,记住这个例子,也就可以大概知道instanceof可以使用的场景了,而且也算是不用instanceof的替代方案了。

boolean result;
if (obj == null) {//首先就把null这种情况排除下,instanceof的左值只要是null,都会返回false
  result = false;
}
try {
    T temp = (T) obj; //强制类型转换这里,是否所有的都可以转换
    result = true;
} catch (ClassCastException e) {
    result = false;
}

继续往下看,想起来我之前写过的一篇文章《Java执行前都干了什么?》(https://blog.csdn.net/qq_20735197/article/details/84837188)在那篇文章中说过编译阶段javac时期,附带javac源码地址。

看下那篇文章2.1.1词法分析

com.sun.tools.javac.parser.Scanner类实现。源代码的字符流转变成标记(token)集合。单个字符是编写程序的最小单位,标记是编译过程的最小单位。关键字、运算符、字面量、变量名都可看成标记。

找到枚举类Token,打开,看下里面有这么一个东西:

public enum Token implements Formattable {
    //....
    INSTANCEOF("instanceof"),
    //....

确实,可以看出instanceof确实是在javac在进行词法分析的时候。

继续往下看,解语法树阶段(http://hg.openjdk.java.net/jdk7u/jdk7u/langtools/file/tip/src/share/classes/com/sun/tools/javac/parser/JavacParser.java)对token的操作:instanceof运算符就会生成这个JCTree.JCInstanceof类型的节点

/** Return operation tag of binary operator represented by token,
     *  -1 if token is not a binary operator.
     */
static int optag(Token token) {
    switch (token) {
            //...
        case INSTANCEOF:
            return JCTree.TYPETEST;   //这里吧token的instanceof转成jctree的二进制测试节点
            //...
    }
}

 /*  Expression2Rest = {infixop Expression3}
     *                  | Expression3 instanceof Type
     *  infixop         = "||"
     *                  | "&&"
     *                  | "|"
     *                  | "^"
     *                  | "&"
     *                  | "==" | "!="
     *                  | "<" | ">" | "<=" | ">="
     *                  | "<<" | ">>" | ">>>"
     *                  | "+" | "-"
     *                  | "*" | "/" | "%"
     */
    JCExpression term2Rest(JCExpression t, int minprec) {
        //...
        if (topOp == INSTANCEOF) {
                return F.at(pos).TypeTest(od1, od2);
            } else {
                return F.at(pos).Binary(optag(topOp), od1, od2);
            }
    }

到最后生成字节码的时候为JCTree.JCInstanceof节点生成instanceof字节码指令

public void visitTypeTest(JCInstanceOf tree) {
    genExpr(tree.expr, tree.expr.type).load();
    code.emitop2(instanceof_, makeRef(tree.pos(), tree.clazz.type));
    result = items.makeStackItem(syms.booleanType);
}

不在继续里面一个个方法继续看下去了。但是jvm到这里应该是说一说的,或者一般别人都会问jvm里关于这方面的内容。我也不真知道了,那翻下jvm源码说明呗。(https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.instanceof)

以下为本人翻译,如果不准确请进入上面提供的链接自己看
instanceof
操作说明
	判断给定的object的类型
格式
 instanceof
 indexbyte1
 indexbyte2
Forms
	instanceof = 193 (0xc1)
操作栈
	..., objectref →
	..., result
说明:
	objectref必须是从操作栈pop出的参考类型。无符号的indexbyte1和indexbyte2构成了当前类的运行时常量池的index,index的值是indexbyte1<<8|indexbyte2。index的运行时常量池项必须是类、数组或接口类型的符号引用。
	如果objectref为空,instanceof指令将一个int结果0作为一个int值推送到操作数堆栈上。
否则,将解析指定的类、数组或接口类型(§5.4.3.1)。如果objectref是已解析类或数组的实例,或者实现了已解析的接口,则instanceof指令将一个int结果1作为int推入操作数堆栈;否则,它将推送一个int结果0。
以下规则用于确定一个非空的objectref是否解决的实例类型:如果S是类对象的引用objectref和T是解决类,数组,或接口类型,instanceof决定objectref T的实例如下:
如果S是一个普通(非数组)类,则:
如果T是类类型,那么S必须与T是同一个类,或者S必须是T的子类;
如果T是接口类型,那么S必须实现接口T。
如果S是接口类型,则:
如果T是一个类类型,那么T必须是Object。
如果T是接口类型,则T必须与S或S的超接口相同。
如果S是表示数组类型SC[]的类,即是SC类型组件的数组,则:
如果T是一个类类型,那么T必须是Object。
如果T是接口类型,那么T必须是数组实现的接口之一(JLS§4.10.3)。
如果T是数组类型TC[],即是数组类型TC的组成部分,则必须满足下列条件之一:
TC和SC是相同的原始类型。
TC和SC是引用类型,类型SC可以通过这些运行时规则转换为TC。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值