本文探讨的是老掉牙的基础问题,先建个实体类

package com.demo.tools;

public class User {

    private String name;

    public User(String name) {
this.name = name;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}

测试:

    public static void main(String[] args){
User a = new User("A");
User b = new User("A");
System.out.println(a.equals(b));
}

输出 false。明明存的都是A,为啥equal的结果是false呢?不是说【equal比较的是值,==比较的是地址】吗?因为默认创建的类是继承Object的,看一下Object类里面的两个方法:

    /**
* 返回对象的哈希码值。
*
* {@code hashCode}的一般约定为:
* 在Java应用程序执行期间对同一对象多次调用{@code hashcode}方法时,只要在对象{@code equals}的比较中使用的信息没有被修改,则该方法必须一致地返回相同的整数。
* 从应用程序的一次执行到同一应用程序的另一次执行,此整数不必保持一致。
*
* 如果调用{@code equals(object)}方法,两个对象相等,那么两个对象调用{@code hashcode}方法必须产生相同的整数结果。
* 根据{@link java.lang.object equals(java.lang.object)}方法,如果两个对象不相等,则这两个对象调用{@code hashcode}方法都必须产生不同的整数结果。
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
public native int hashCode(); /**
* {@code equals}方法在非空对象引用上实现等价关系:
*
* 它是自反的:对于任何非空的引用值{@code x},{@code x.equals(x)}应该返回{@code true}。
* 它是对称的:对于任何非空的引用值{@code x}和{@code y},{@code x.equals(y)}应该返回{@code true};反过来也应该一样{@code y.equals(x)}返回{@code true}。
* 它是可传递的:对于任何非空的引用值{@code x}、{@code y}和{@code z},如果{@code x.equals(y)}返回{@code true},{@code y.equals(z)}返回{@code true},则{@code x.equals(z)}应返回{@code true}。
* 它是一致的:对于任何非空的引用值{@code x}和{@code y},多次调用{@code x.equals(y)}应该一致的返回{@code true}或{@code false},前提是在{@code equals}比较的对象信息没有被修改。
* 对于任何非空引用值{@code x},{@code x.equals(null)}应返回{@code false}。
*
*
* 对于任何非空的引用值{@code x}和{@code y},当且仅当{@code x}和{@code y}引用同一对象,此方法返回{@code true}【此时{@code x==y}的值是{@code true}】。
*
* 注意,通常需要重写{@code hashcode}方法,以便保持{@code hashcode}方法的一般约定,它声明:相等的对象必须有相等的哈希代码。
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}

可以看到: 1. 默认的hashcode方法是个本地方法,也就是对象的内存地址。 2. 而equals方法则是直接使用了==操作符,也是比较内存地址。这样看来,equals和hashcode是一样的。

而根据注释所说: 1. 只要equals返回true,那么hashcode一定相等。 2. 只要equals返回false,那么hashcode一定不相等。3. 重写了equals方法,也要重写hashcode方法,因为相等的对象必须有相同的哈希码。

尝试一:仅仅重写equals

package com.demo.tools;

public class User {

    public static void main(String[] args){
User a = new User("A");
User b = new User("A");
System.out.println(a.equals(b));
System.out.println(a == b);
System.out.println(a.hashCode() + "," + b.hashCode());
} private String name; public User(String name) {
this.name = name;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public boolean equals(Object obj) {
User user = (User) obj;
return this.name.equals(user.name);
}
}

输出:

可以看到,值比较成功了,可是hashcode不一样,这里的hashcode仍然是内存地址。这就违反了约定。

尝试二:重写equals和hashcode方法

package com.demo.tools;

public class User {

    public static void main(String[] args){
User a = new User("A");
User b = new User("A");
System.out.println(a.equals(b));
System.out.println(a == b);
System.out.println(a.hashCode() + "," + b.hashCode());
} private String name; public User(String name) {
this.name = name;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public boolean equals(Object obj) {
User user = (User) obj;
return this.name.equals(user.name);
} @Override
public int hashCode() {
return this.name.hashCode();
}
}

输出:

可以看到,值和hashcode一致了,地址不一样很正常,因为这是两个对象。

尝试三:hashcode相同,equals就相等吗?

    public static void main(String[] args){
User a = new User("Aa");
User b = new User("BB");
System.out.println(a.equals(b));
System.out.println(a == b);
System.out.println(a.hashCode() + "," + b.hashCode());
}

输出:

可以看到,两个不同的字符串,hashcode可能一样。

源码一:Map里的equals方法和hashcode方法

    public boolean equals(Object o) {
// 地址一样,返回true
if (o == this)
return true; // 不是Map的子类,返回false
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
// 大小不同,返回false
if (m.size() != size())
return false; try {
// 遍历Map内容,一一比较
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Map.Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
// HashMap里允许存储null值
if (value == null) {
// 对方不存在此key,返回false
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
// 都有此key,但是值不一样,返回false
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
} return true;
}

这里能回答一个问题:Map中的对象是自定义的,要不要重写对象的equals方法?

看源码我们能知道,默认Map里的equals方法,在内部仅仅调用了value的equals方法,默认比较的是内存地址。在调用Map的equals方法时:

1. 默认一一比较对象的地址,也就是说,两个Map中每一组比较的对象,都引用同一个地址,那才算两个Map相等。

2. 如果我们业务规定了相等的判定,那么默认的则不符合实际需求,所以要重写value的equals方法,不然调用Map的equals方法可能不符合预期,既然重写了equals,那么也要重写hashcode了。

    public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}

不难看出,Map的hashcode方法是把内部元素的hashcode加在一起并返回。

源码二:List的equals方法和hashcode方法

    public boolean equals(Object o) {
// 本身比较,返回true
if (o == this)
return true;
// 非List的子类,返回false
if (!(o instanceof List))
return false; ListIterator<E> e1 = listIterator();
ListIterator<?> e2 = ((List<?>) o).listIterator();
// 当两个迭代器的hasNext都为true的时候,进行比较
while (e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
// 这里很巧妙,利用三目运算符完成值的比较
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
// 当任意一方hasNext为true,返回false;当双方都为false,返回true
return !(e1.hasNext() || e2.hasNext());
}

基本逻辑和Map的一样,都是遍历元素进行比较。hashcode也是一样。

    public int hashCode() {
int hashCode = 1;
for (E e : this)
hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
return hashCode;
}

源码三:String的equals方法和hashcode方法

    public boolean equals(Object anObject) {
// 地址一样,返回true
if (this == anObject) {
return true;
}
// 类型必须是String
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// 比较两个String的长度
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 遍历比较
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

hashcode

    private final char value[];
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
// 这里并不是把元素的hashcode加在一起,而是把元素的ASCII码加在一起
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

总结

不管平时有没有重写过equals和hashcode方法,看了Map和Set的源码就可以知道重写的必要性。因为Set也是基于Map实现的(是一个key不一样,value全一样的Map),所以只用Map举证即可。

    public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
} final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

首先值的位置是根据hashcode决定的,然后判断值相等的条件是equals为true,hashcode相等。其它都为不相等,有一种情况就是equals为true,hashcode不相等,如果插入Set集合,就不能达到去重的效果:

package com.demo.tools;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set; public class Main { public static void main(String[] args) throws Exception {
Set<Foo> set = new HashSet<>();
Foo a = new Foo("A", 12);
Foo b = new Foo("A", 13);
set.add(a);
set.add(b);
set.forEach(e -> System.out.println(e.name));
System.out.println(a.equals(b));
System.out.println(a.hashCode() + "," + b.hashCode());
} static class Foo {
private String name;
private Integer age; public Foo(String name, Integer age) {
this.name = name;
this.age = age;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
// 这里我们认为name一样,则相等
return Objects.equals(name, foo.name);
}
} }

equals为true,但是hashcode不一样:

所以,如果重写了equals,一定要重写hashcode,以避免不必要的问题。

小技巧:IDEA里可以自动生成两个方法

快捷键:Alt+Insert

标准equals和hashcode,值得借鉴

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Foo foo = (Foo) o;
return Objects.equals(name, foo.name) &&
Objects.equals(age, foo.age);
} @Override
public int hashCode() { return Objects.hash(name, age);
}

Java里 equals 和 == 以及 hashcode的更多相关文章

  1. java中equals相同,hashcode一定相同ma

    一.jdk中equals和hashcode的定义和源码进行分析 1.java.lang.Object中对equals()方法的定义 java.lang.Object中对hashCode()方法的定义 ...

  2. JAVA中equals方法与hashCode方法学习

    首先参考文章:http://www.oschina.net/translate/working-with-hashcode-and-equals-methods-in-java 1,equals方法的 ...

  3. java里 equals和== 区别

    1.java中equals和==的区别 值类型是存储在内存中的堆栈(简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中.2.==操作比较的是两个变量的值是否相等,对于引 ...

  4. java里equals和hashCode之间什么关系

    如果要比较实际内存中的内容,那就要用equals方法,但是!!! 如果是你自己定义的一个类,比较自定义类用equals和==是一样的,都是比较句柄地址,因为自定义的类是继承于object,而objec ...

  5. (转)Java中equals和==、hashcode的区别

    背景:学习辉哥总结的基础知识,从头来,直面短板. 1 问题引入及分析 请看下面的代码清单1 @Test public void test01() { String a = "a" ...

  6. java中equals方法和hashcode方法的区别和联系,以及为什么要重写这两个方法,不重写会怎样

    一.在Object类中的定义为:public native int hashCode();是一个本地方法,返回的对象的地址值.但是,同样的思路,在String等封装类中对此方法进行了重写.方法调用得到 ...

  7. JAVA 重写equals和重写hashCode

    面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” 首先你需要了解: hashCode()的作用是获取哈希码(散列码) 它实 ...

  8. Java重写equals方法和hashCode方法

    package com.ddy; public class User {     private Integer id;     private String name;     private St ...

  9. java自定义equals函数和hashCode函数

    所有类都继承自Object类,他所有的非final方法:equals,hashCode, toString, clone 和 finalize,它们都有通用约定. 我们在覆盖这些方法的时候需要遵循这些 ...

随机推荐

  1. Java常用类StringBuffer详解

    内容多为最近学习的自我总结,可能有些地方写的不严谨,甚至会有错误的地方,仅供参考,如发现错误敬请指出,谢谢! 灰色字体为补充扩展内容,多为帮助自己理解. StringBuffer概述: 线程安全的可变 ...

  2. 【JZOJ】2126. 最大约数和

    题目大意 选取和不超过S的若干个不同的正整数,使得所有数的约数(不含它本身)之和最大. 分析 把我们分解出来的因数进行合并,存在一个不知名的数组里,然后我们大可开始我们的迪屁!!(bag),我们可以 ...

  3. node+express修改代码会自动重新运行

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/weixin_40822305/artic ...

  4. sslscan

    msf > use auxiliary/pro/web_ssl_scan msf auxiliary(web_ssl_scan) > show options Module options ...

  5. 《2017年-2018年中国MES软件及服务市场研究报告》正式发布!

    <2017年-2018年中国MES软件及服务市场研究报告>由e-works Research研究编写,报告深度分析了2017年及2018年中国MES市场发展状况,从市场规模.市场特点.需求 ...

  6. Solr搜索器的特性及相关操作

    一.搜索处理器简介 所有的请求处理器都实现一个Java类,本例实现了solr.SearchHandler.在运行时,solr.SearchHandler被解析为内置的Solr类org.apache.s ...

  7. python之pip安装软件包常用命令

    # pip版本号查询 pip -V # 安装软件包.格式:pip install 软件包名 pip install pygame # 安装指定版本号的软件包.格式:pip install 软件包==软 ...

  8. Python实现十大经典排序算法(史上最简单)。

    十大排序算法(Python实现)一. 算法介绍及相关概念解读 算法分类十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn), ...

  9. Logstash连接Elasticsearch异常

    1.背景 elasticsearch集群默认配置启动ok,logstash连接向里面发数据ok. 2.出现问题 修改elasticsearch.yml中的cluster.name,改为 esabc 然 ...

  10. 宝塔https部署没成功的原因排查

    今天ytkah在迁移一个客户网站的时候出了点问题,网站从旧的服务器(windows)换到新的服务器(阿里云centos 7,已经安装了宝塔面板),网站之前有用comodo的ssl证书,因为快要过期了, ...