通过重写 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;
if (this == o) return true;
首先使用 == 直接判断两个对象的内存地址是否相同,如果相同直接返回 true。我自己重写 equals 方法时,就完全没想到用 == 判断,实际上这个 if 可以大大提高效率。
2. if (!(o instanceof EqualsFather)) return false;
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…