注:本文是《EffectiveJava》的学习笔记。文章引用的Java源码都是JDK1.8版本的源码。
equals是Object这个超类的方法。我们常用这个方法判断逻辑相等。equals是可以继承的,所以父类实现后,子类不实现也可以使用equals来判断逻辑等。规定满足如下。自反性、对称性、传递性、一致性。(其实挺简单的概念)
x.equals(x) = true ; x.equals(y) = true 则 y.equals(x) = true; x.equals(y) ; y.equals(z) ;则 x.equals(z) 成立
多次运行结果相同。
首先需要明确以前学过的知识,equals在包装类和基本数据类型做比较时比较值是否相等,即两个new 出来的包装类也是比较的包装类里面的实例变量值是否相等。例如Integer的equals源码。
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
emmm,再此我还想说一下 Object 与 泛型的区别。
Object是需要强制类型转换才可以变成指定类型的(类转换异常)。而泛型会在编译期做出隐式替换。
instanceof 与 getclass的区别也要明确一下。
instanceof判断是否是某一类型的实例时,该类型可以是父类或者接口。而getclass 用于判断准确的类型。
对于equals的比较应该是instanceof级别的比较。
接下来在过分解读一下Integer的源码。可以看到Integer a = 300 触发了自动装箱、调了valueOf方法。先做缓存区比较[-128,127],不存在再new一个对象(final int value)。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
对于 Integer a = 300; a = 400; 实际上触发了两次装箱操作。对 300 进行装箱在赋值给a valueOf方法不在缓存区会返回一个新的实例。而这两个语句也创建了两个对象。所以能用基本数据类型时不要用包装类。。。。回到equals
tips:有一个小技巧。
在进行字符串equals比较时,尽量使用调用者为尽可能非空的对象。 例如 "".equals("a") 这样不会 NPE空指针。
对于你自己建的类做equals比较, 要是没啥字段 1.可以用 == 作为equals判断即同一个对象。
2.用instanceof做equals中的筛选条件。
3.把参数转换成正确的类型。 参考Integer的equals源码
4.将类中关键变量做比较。
在实现equals的一些帮助如下。
覆盖equals时总要覆盖hashcode
不要改equals的参数类型。
public boolean equals(Object obj){} 这个是重写Object。要是换成别的参数就是相当于重载equals !!!
在进行自动化生成Bean的getter/setter/toString 等方法时我使用到了lombok这个插件。(idea开发)
Object中的hashcode方法是一个native的方法。再进行散列集合运作时必须要重写hashcode,散列就是hashMap等。
需要满足:
1.equals相等的两个对象他们的hashcode值一定相等。
2. equals不等的两个对象,对于hashcode值来说不一定相等。
有一种重写hashcode的方法, Type.hashcode(f)对这个f域取hashcode散列码c,然后 result = result * 31 +c 通过这个公式计算。
选择31是因为他是一个奇素数,如果乘数是偶数,且乘法产生溢出的话会使信息丢失。 31有个很好的特性,用移位和减法来代替乘法,这样就会对计算实现优化。 31 * i = (i << 5) -i
或者你可以考虑使用JDK7引入的objects工具类。
Objects.hash(Object... value) 以一种可变形参的方式对传入的参数进行计算。或者使用AutoValue lombok等插件生成实体对象时生成hashcode方法和equals方法。
tips:不要试图从散列码计算中排除掉一个对象的关键域来提高性能。 不要hashcode方法的返回值做出具体的规定,这样会降低灵活性。
hashcode在散列集合中才有意义,如果用不到散列集合不重写也ok ,但是建议是重写。
如果不覆盖toString 会打印出类名@散列码的信息。但是往往我们想看的是具体内部的东西。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这条建议一般在编程时会直接写出来了。比如lombok的@toString类注解一加就行
clone的对象满足 x.getClass() == x.clone().getClass() x.clone() != x x.clone().equals(x)
clone与final域是不兼容的。
再此我想要叙述一下protected这个关键字。看Object源码应该知道 clone()方法和finalize()方法的修饰符是protected。
在我测试clone方法时无意间发现我对这个protected关键字理解的比较浅。
举一个例子。 有A B 两个类。毫无疑问他们都是Object的子类,那么他们也都继承了Object的clone()方法。
由于clone方法是受保护的。所以是不能跨类调用的。即如果你想在B类中写一个方法调用A对象的clone方法是不成立的。
因为A类的clone方法继承自Object 它是受保护的,不能再除了子类和java.lang包之外被调用。注意:AB相当于都是Object的子类,但是他们是没有任何关系的,你要想在B中调用A的clone 需要B与A有父子类关系。也间接证明了重写不能扩大访问权限
这个接口是做排序用的。相同的还有comparator这个排序器,可以用匿名对象直接进行排序,lambda也可以。
在有排序需求时。Arrays.sort(a);可以直接排序a这个数组,前提是数组的泛型实现了comparable接口。当然完全可以用comparator来实现排序操作。TreeSet这种集合也是要求泛型必须实现comparable接口。还有翻转比较器的说法。
list.stream().sorted(Comparator.reverseOrder()) 翻转
comparator不能比较不同类型,会出现类转换异常。下面是TreeMap的部分源码。可以看到必须实现comparable才能往TreeMap中添加。
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
我自己在用这种排序器时常常使用的是匿名类comparator来构造compare方法进行排序。当然这样确实没有实现comparable的方式好。
tips:在实现comparable的compareTo方法时不建议使用 > < 繁琐易出错。可以使用包装类的compare方法。
例如:Double.compare(100,200) 就返回-1 了 -1就是前者小于后者 当方法返回0的时候便是两者相等。
JDK8还引进了更好用的比较函数。使用如下这种一步步构造的Builder方式。这样也能构造出一个根据你传入字段顺序的比较器
Comparator
Objects是一个final修饰的工具类。相当于对Object的补充。内置方法全是static方法。方法如下图所示
上面有写过hash这个方法。
关于equals方法实际上还需传入对象有两个判断 先判断是不是 == 在判断 firstParam.equals(secondParam);不会有NPE
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
在按上文提到的方法重写完equals时完全可以用这个Objects的equals方法进行比较。相当于多了两步校验。==和NPE
看了下deepEquals的源码。这个方法可以比较数组也可以比较集合,是一个比较矩阵的方法,感觉挺好的。我测试了一维数组、二维数组、集合。都是可以使用这个方法做比较的。具体源码我就不贴了,感兴趣可以自己看一下。
Objects中的toString方法也是做出了null处理。源码如下,避免 NPE 。我觉得这个方法还挺好的。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
hashcode方法做出了null值的处理 null 为0 如果你重写的hashcode方法有这个相关的判断这个就用不上了。
public static int hashCode(Object o) {
return o != null ? o.hashCode() : 0;
}
Objects还有一些对于Null的判断。个人没感觉怎么好用。。。就是写着好看点。有的返回true有的返回原类型。有NPE处理
就是lambda写的时候得劲。