清晰理解java的instanceof关键字

通过重写 Object 的 equals 方法开始本文 ( instanceof 详解跳转)

由于 Object 类中 equals 方法实际上就是 == ,所以大部分类都按需重写了 equals 方法。

public class EqualsFather {
    private int father;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof EqualsFather)) return false;
        EqualsFather that = (EqualsFather) o;
        return father == that.father;
    }

    @Override
    public int hashCode() {
        return Objects.hash(father);
    }
}
-------------------------------------------
public class EqualsSon extends EqualsFather{
    private int son;
}

以上是使用 idea 自动生成的 equals 及 hashCode 方法,虽然说平时我们只需要动动手指生成一下就完事了,但不能知其然而不知其所以然。

1.if (this == o) return true;

首先使用 == 直接判断两个对象的内存地址是否相同,如果相同直接返回 true。我自己重写 equals 方法时,就完全没想到用 == 判断,实际上这个 if 可以大大提高效率。

2.if (!(o instanceof EqualsFather)) return false;

instanceof
是 java 的保留关键字,跟 ==,<,> 类似,是比较运算符。

instanceof 的作用是,判断左侧的对象对应的类是否为右侧类 / 接口的子类 / 实现类,并且是否被同一个类加载器加载。如果不是,或者左侧为 null,输出 false;否则输出 true。

EqualsFather father = new EqualsFather();
EqualsSon son = new EqualsSon();
例子1. System.out.println(son instanceof EqualsFather);
例子2. System.out.println(son instanceof ArrayList);

例子 1 的输出毫无疑问是 true 的,因为 EqualsSon 是 EqualsFather 的子类。

例子 2 输出什么呢? false ?其实根本无法编译通过,并且会提示 cannot cast 'EqualsSon' to 'java.util.ArrayList'

通过伪代码来演示 instanceof 的判断机制

boolean res;
if(obj == null){
    //如果为 null 
    res = false;
}else{
    try{
        obj = (ArrayList) obj;
        res = true;
    }catch(ClassCastException e){
        res = false;
    }
}
return res;

jvm 是如何执行 instanceof 指令的呢?

son instanceof EqualsFather

1.通过 son.getClass() 得到的 class 对象,找到方法区中对应的类型信息。

2.通过 EqualsFather.class 得到的 class 对象,找到方法区中对应的类型信息。

3.通过 java 规范判断两个类型是否为继承/实现关系。

测试:不同的类加载器加载同一个 .class 文件,查看 instanceof 结果

public class EqualsTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = this.getClass().getResourceAsStream(filename);
                    if (is == null){
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name,b,0,b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return super.loadClass(name);
            }
        };
        Object o = myClassLoader.loadClass("object.equals.EqualsSon").newInstance();
        Object o1 = myClassLoader.loadClass("object.equals.EqualsFather").newInstance();
        EqualsSon son = new EqualsSon();
        System.out.println(o.getClass());
        System.out.println(son.getClass().equals(o.getClass()));
        System.out.println(o instanceof object.equals.EqualsSon);
        System.out.println(o instanceof object.equals.EqualsFather);
    }
}

o 为自定义类加载器加载 object.equals.EqualsSon 的 class 文件后生成的对象,o1 同理。

son 为应用程序类加载器加载 object.equals.EqualsSon 的 class 文件后生成的对象。

输出1:

Object o1 = myClassLoader.loadClass("object.equals.EqualsFather").newInstance();

编译通过,但运行报错:JVM 抛出 java.lang.LinkageError: attempted duplicate class definition for name:。LinkageError 表示类加载器多次加载同一个 class 文件。

是由于 EqualsSon 继承了 EqualsFather,当自定义类加载器加载 EqualsSon ,并调用 Class::newInstance() 方法通过 EqualsSon 的无参构造创建 EqualsSon 对象时,触发了父类 EqualsFather 的初始化(主动引用)。当然在 EqualsFather 被初始化前,它的加载、验证、准备阶段已经完成。

如果再让自定义加载器加载 EqualsFather ,那么就不是第一次加载了,一定会抛出 LinkageError。

ClassLoader classLoader = EqualsTest.class.getClassLoader();
classLoader.loadClass("object.equals.EqualsSon");
classLoader.loadClass("object.equals.EqualsFather");

这样就不会报错,因为 loadClass 方法只是让类加载器加载 EqualsSon 类而已。没有触及初始化阶段,不会触发父类的加载。

输出2:

System.out.println(o.getClass()); 输出 class object.equals.EqualsSon 不同的类加载器加载的是同一个 class 文件。

输出3:

System.out.println(son.getClass().equals(o.getClass()));

对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在 Java 虚拟机中的唯一性。每一个类加载器,都有一个独立的类名称空间。比较两个类是否 “相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来自同一个 Class 文件,被同一个 Java 虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。

------《深入理解 Java 虚拟机》周志明

两个不同的类加载器,加载同一个 Class 文件,会生成两个不同的 Class 对象。所以输出 false

输出4:

System.out.println(o instanceof object.equals.EqualsSon);

现在堆中有两个 EqualsSon类 对象,一个由自定义类加载器加载,一个由 ApplicationClassLoader 加载。

输出 false

输出5:

System.out.println(o instanceof object.equals.EqualsFather);

o 对应的 EqualsSon类 与被同一个类加载器加载的 EqualsFather类为继承关系。但与被 ApplicationClassLoader 加载的 EqualsFather类没有继承关系。

输出 false

3.通过成员变量判断两个相同类型的对象是否相等

    EqualsFather that = (EqualsFather) o;
    return father == that.father;

end…

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值