![](https://markdown-1258186581.cos.ap-shanghai.myqcloud.com/20190606104746.png)
[TOC]
## 1、一些概念:
1. 若 `==` 两侧都是基本数据类型,则判断的是左右两边操作数据的值是否相等
2. 若 `== `两侧都是引用数据类型,则判断的是左右两边操作数的内存地址是否相同。若此时返回 `true` , 则该操作符作用的一定是同一个对象。
3. `Object` 基类的 `equals` 默认比较两个对象的内存地址,在构建的对象没有重写 `equals` 方法的时候,与 `==` 操作符比较的结果相同。
4. `equals` 用于比较**引用数据类型**是否相等。在满足`equals` 判断规则的前体系,两个对象只要规定的属性相同我们就认为两个对象是相同的,比如 String 就重写了 equals 方法,所以可以用 equals 去实现比较字符串是否相等
![](https://img.kancloud.cn/6e/62/6e62dc790330a36c571f5799cf448e41_1314x736.png)
## 2、hashCode 方法返回的是对象的内存地址么?
`Object` 基类的 `hashCode` 方法默认返回对象的内存地址,但是在一些场景下我们需要覆写 `hashCode` 函数,比如需要使用 `Map` 来存放对象的时候,覆写后 `hashCode` 就不是对象的内存地址了。
在 String 类中,就提供了重写的 hashCode 方法,如下:
```java
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
```
可以看到,在 String 类的 hashCode 方法中,会针对当前的 String 字符串计算出唯一的 hash 值。
## 3、为什么一般重写equals 方法 也会重写 hashcode 方法
因为在使用 hashmap 的时候,会先根据 key 值得 hashCode 方法计算出 hash 值,确定该元素在数组中的位置,如果数组中不存在该值,那么就直接把元素的 key 放进数组,代表了元素的插入。
如果数组中存在该 key 的 hashCode ,那么就会遍历链表,遍历链表的时候使用的是元素 key 值的 equals 方法去判断是否相等的,
如果相等用新的 value 取代旧的 value
那么问题来了?
如果不重写 hashCode 方法,那么就无法定位到一个位置,两个通过 equals 方法相等的key,我们认为是同一个key,但是由于hashCode 没有定位到同一个位置,会导致插入重复元素,重写 equasl 就没有什么意思了。
就会出现如图的问题:
![](https://markdown-1258186581.cos.ap-shanghai.myqcloud.com/20190526230158.png)
如果重写了hashcode方法,确保两个对象在定位的时候,都能够定位到相同的位置,那么就可以遍历这条单向链表,使用equals方法判断两个对象是否相同,如果相同,那么就不插入了(HashMap的实现仍然插入,但是覆盖掉旧的value)。如果不相同,就插入到链表的头节点处。
## 4、重写 equals 方法有什么官方规范
1. 自反性:x.equals(x) = true;
2. 对称性:如果有x.equals(y) = true,那么一定有y.equals(x) = true;
3. 传递性:对任意的x,y,z。如果有x.equals(y) = y.equals(z) = true,那么一定有x.equals(z)= true;
4. 一致性:无论多少次调用,x.equals(y)总会返回相同的结果。
5. 非空性(暂定):所有的对象都必须!=null;
步骤:
1. 使用==操作符检查“实参是否为指向对象的一个引用”,如果是则返回true;
2. 使用instanceof操作符检查“实参是否为正确的类型”,如果不是,则返回false;
3. 将传递进来的参装换为正确的要比对的类型;
4. 对于该类中的每一个关键域,检查实参中的域与当前对象中对应的域是否匹配。如果所有测试都成功,则返回true,否则返回false。(**参考 String,是比对没个 char 的值是否相等的**)
5. 方法完成之后,确定 equals 方法的对称性,传递性,一致性。
- Java 面试题
- String、StringBuffer、StringBuilder 的区别?
- Java 中的四种引用
- 接口和抽象类的本质区别
- 集合框架
- 集合概述
- ArrayList 源码分析
- LinkedList 源码分析
- HashMap 源码分析
- LinkedHashMap 源码分析
- Android提供的 LruCache 的分析
- LinkedList 和 ArrayList 的区别
- 多线程
- 实现多线程的几种方式
- 线程的几种状态
- Thread 的 start() 和 run() 的区别
- sleep() 、yield() 和 wait() 的区别 ?
- notify() 和 notifyAll() 的区别?
- 保证线程安全的方式有哪几种?
- Synchronized 关键字
- volatile 和 synchronized 的区别?
- 如何正确的终止一个线程?
- ThreadLocal 原理分析
- 线程池
- 多线程的三个特征
- 五种线程池,四种拒绝策略,三种阻塞队列
- 给定三个线程如何顺序执行完以后在主线程拿到执行结果
- Java 内存模型
- 判定可回收对象算法
- equals 与 == 操作符
- 类加载机制
- 类加载简单例子
- 算法
- 时间、空间复杂度
- 冒泡排序
- 快速排序
- 链表反转
- IO
- 泛型
- Kolin 面试题
- Android 面试题
- Handler 线程间通信
- Message、MessageQueue、Looper、Handler 的对象关系
- Handler 使用
- Handler 源码分析
- HandlerThread
- AsyncTask
- IntentService
- 三方框架
- Rxjava
- rxjava 操作符有哪些
- 如何解决 RxJava 内存泄漏
- Rxjava 线程切换原理
- map和 flatmap 的区别
- Databinding引起的 java方法大于 65535 的问题
- Glide
- Glide 的缓存原理
- Glide 是如何和生命周期绑定的?不同的Context 有什么区别?
- Glide 、Picasso 、的区别,优劣势,如何选择?
- Jetpack
- 源码分析
- EventBus
- EventBus 源码分析
- RxBus 替代 EventBus
- OkHttp
- OkHttp 源码分析
- OkHttp 缓存分析
- RxPermission
- RxPermission 源码分析
- Retrofit
- create
- Retrofit 源码分析
- 优化
- 启动优化
- 布局优化
- 绘制优化
- 内存优化
- 屏幕适配
- 组件
- Activity
- Frgment
- Service
- ContentProvider
- BroadcastReceiver
- 进程间通信
- Binder机制和AIDL
- AILD 中的接口和普通的接口有什么区别
- in、out、inout 的区别
- Binder 为什么只需要拷贝一次
- 在android中,请简述jni的调用过程
- 生命周期
- Activity 生命周期
- Fragment 生命周期
- Service 生命周期
- onSaveInstanceState() 与 onRestoreIntanceState()
- 前沿技术
- 组件化
- 模块化
- 插件化
- 热更新
- UI - View
- Android 动画
- 事件分发机制
- WebView
- 系统相关
- 谈谈对 Context 的理解
- Android 版本
- App应用启动流程
- App 的打包
- App 的加固
- App 的安装
- Activity 启动流程
- ClassLoader
- Lru 算法加载 Bitmap 三级缓存原理
- Parcelable 和 Serializable 的区别
- Activity的启动流程
- 相关概念
- 网络相关
- Http
- Https
- Http 和 Https 的区别
- 为什么要进行三次握手和四次挥手?
- OkHttp使用Https访问服务器时信任所有证书
- 设计模式
- 单例模式
- 构建者模式
- 工厂模式
- 外观模式
- 代理模式