[TOC]
## 覆请equals遵守规则
Object中equals方法的实现仅仅是比较了两个对象的地址,对于某些类来说正是所需用的、毋需复写的
equals方法的复写需要满足以下通用约定
* 自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true,就是自己和自己比较必须相等。
* 对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true,就是x若等于y,那么y也应该等于x。
* 传递递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
* 一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
* 非空性:对于任何非空引用值 x,x.equals(null) 都应返回 false。
如无必要不要复写equals 方法,如果复写了此方法一定要记得复写hashCode方法,因为两个对象相等,它们的hashCode也要相等,下面是equals方法的常用步骤
~~~
@Override
public boolean equals(Object o) {
//判断引用是否相等
if (o == this) {
return true;
}
//判断参数类型是否正确 如果o为null也会返回false
//这里判断的是class类型,也有可能是接口类型,这样就允许实现这个接口的类之间进行比较
//AbstractSet,AbstractList,AbstracMap的equals方法这一步都是比较的接口
if (!(o instanceof PhoneNumber)) {
return false;
}
//类型转换
// AbstractSet的类型转换 Collection<?> c = (Collection<?>) o;
PhoneNumber pNum = (PhoneNumber) o;
// 判断重要字段的相等,如果使用的是接口,调用接口的方法获取字段
// 对于基本类型 如果不是float或double 直接使用==比较
// float使用Float.compare(float, float), 原因参考testFloat方法
//double使用Double.compare(double, double) 同上
//Float.equals和Double的equals都设计autobox,影响性能
//引用类型继续调用其equals方法
// 上述方法也同样适用于数组元素,如果要比较整个数值,使用Arrays.equals对应的方法
//对象的某些字段能为Null,为了避免NPE,使用Objects.equals(Object, Object)
return this.linNum == pNum.linNum &&this.areaCode == pNum.areaCode &&this.prefix == pNum.this.prefix;
~~~
## 覆盖equals总是覆盖hashCode
上文说到如果复写equals方法一定要复写hashCode方法。下面说说hash值的计算
确保与equals中使用的字段一致
* 如果字段是基本类型,使用包装类计算hash值如Float.hashCode(f)
* 如果字段是引用类型,并且在equals方法中递归调用去equals方法,那么这里也递归调用其hashCode方法
* 如果字段是数组类型,对其中重要元素的hash计算上述方法同样使用,如果要计算整个数组的hash值,使用Arrays.hashCode(array)
* 质素31的选取是个传统,能尽量让不同对象拥有不同hash值,即分布均匀,
* Objects.hash(linNum,prefix,areaCode)方法简便,但涉及可变数组的创建和拆装箱操作,性能敏感
此方法返回的值不应该有详细规范,如String的hashCode方法返回精确值就是一个失误
## ==和equals和hashCode
基本数据类型:“==”比较的是其值
类:“==”比较数据内存地址是否相同
“equals”是Object方法,在子类未重写方法时
~~~
public boolean equals(Object obj) {
return (this == obj);
}
~~~
hashCode返回的是对象在内存中地址转换成的一个int值,所以如果没有重写 hashCode()方法,任何对象的hashCode()方法都是不相等的。
## 始终要覆盖toString
尽量复写toString方法,虽然不及equals和hashCode方法必要,但良好的类描述将能提供充分和友好的信息。
## 谨慎覆盖clone
那么在java语言中,有几种方式可以创建对象呢?
* 使用new操作符创建一个对象。
* 使用clone方法复制一个对象。
如果一个class 实现了Cloneable接口 那么它应该 提供一个public clone方法
* 这是一个毋需构造器就能创建对象的方法
* 注意:这种方式复制对象容易出错而且复杂,难以维护 仅仅在对基本类型数组的复制是可取的
* 这个方法是个浅拷贝,也就是字段到字段的复制,如果都是基本类型,那将是一步到位的,
* 但如果还有引用类型,它们指向的对象不会被拷贝,而仅仅拷贝了引用,这就会导致拷贝后的对象和被拷贝的对象不是相互独立的,这些引用指向了相同的对象,也就是任何一方的修改都在另一方得到体现
* 如果要深度拷贝,可以每个引用类型都需要实现cloneable接口和clone方法
* 在实际中要实现对象拷贝,并不建议使用clone方法,而建议采用静态工厂或构造器方式提供复制操作
## comparable
* 如果此方法返回0那么equals应该返回true,如果不是一定要说明不一致性
* HashSet依赖equals比较元素是否重复,TreeSet依赖compareTo给元素排序
* 不要使用来比较大小,对浮点有例外(基本类型的OK的),也不要使用减号,会有溢出
## 推荐阅读
[深入理解 Java Object](https://www.jianshu.com/p/4222abe52c12)
《Effective Java》第二章
- Java
- Object
- 内部类
- 异常
- 注解
- 反射
- 静态代理与动态代理
- 泛型
- 继承
- JVM
- ClassLoader
- String
- 数据结构
- Java集合类
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- TreeMap
- HashTable
- 并发集合类
- Collections
- CopyOnWriteArrayList
- ConcurrentHashMap
- Android集合类
- SparseArray
- ArrayMap
- 算法
- 排序
- 常用算法
- LeetCode
- 二叉树遍历
- 剑指
- 数据结构、算法和数据操作
- 高质量的代码
- 解决问题的思路
- 优化时间和空间效率
- 面试中的各项能力
- 算法心得
- 并发
- Thread
- 锁
- java内存模型
- CAS
- 原子类Atomic
- volatile
- synchronized
- Object.wait-notify
- Lock
- Lock之AQS
- Lock子类
- 锁小结
- 堵塞队列
- 生产者消费者模型
- 线程池