【Java关键字】理解instanceof关键字

前言

前文对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 is true if the value of the RelationalExpression is not null and the reference could be cast to the ReferenceType without raising a ClassCastException. Otherwise the result is false.

拙译:在运行时,如果需判断的参数不为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的执行策略(笔者拙译,水平不高,欢迎指正):

  1. 如果objectref为null,则直接返回false;否则就需要检查,objectref代表的类型对象S是否为T的子类型;
  2. 如果S == T,则返回true;
  3. 如果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的时候符合运行时的规则。

结语

英语真的太重要了,而且水平也决定了看问题的角度和高度。知乎上这位大佬的回答很值得去看,但是能看明白又是一回事了,屁股决定脑袋。

关于Java基础,笔者还写了一些文章,有需要的读者可以看一看:

【Java关键字】理解static关键字

【Java集合】你回答得出HashMap(JDK1.8)的7个问题吗?

【Java集合】除了Vector,还有另一个提供线程安全的List是什么?

【Java集合】LinkedList的使用及原理

【Java集合】ArrayList的使用及原理

你真的有好好了解过序列化吗:Java序列化实现的原理

关于String的这9个问题,值得一看

参考资料:

java中的instanceof关键字

Java关键字(一)——instanceof

posted @ 2020-04-15 16:32  NYfor2018  阅读(577)  评论(0编辑  收藏  举报