EffectiveJava(2)之所有对象通用的方法

注:本文是《EffectiveJava》的学习笔记。文章引用的Java源码都是JDK1.8版本的源码。

10.覆盖equals时请遵守通用约定

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开发)

11.覆盖equals时总要覆盖hashcode

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 ,但是建议是重写。

12.始终要覆盖toString方法

如果不覆盖toString 会打印出类名@散列码的信息。但是往往我们想看的是具体内部的东西。

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

这条建议一般在编程时会直接写出来了。比如lombok的@toString类注解一加就行

13.谨慎的覆盖clone方法

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有父子类关系。也间接证明了重写不能扩大访问权限

14.考虑实现comparable接口

这个接口是做排序用的。相同的还有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)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 comparator = Comparator.comparingInt(first).thenComparingInt(second); 
  

 

对于Objects这个工具类打算在这篇博客记录一下方法。

Objects是一个final修饰的工具类。相当于对Object的补充。内置方法全是static方法。方法如下图所示

EffectiveJava(2)之所有对象通用的方法_第1张图片

上面有写过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写的时候得劲。

 

 

你可能感兴趣的:(effectiveJava,对象通用的方法,effectiveJava)