java类中的重写标识有什么用啊_Java中自定义类为什么一定要重写HashCode和equals方法?...

文章目录

1. 引入

2. 两者都不重写

3. 只重写hashCode方法

4. 只重写equals方法

5. 原理分析

1. 引入

当想要往类似HashMap的Map接口的实现类对象中存放Java中的包装类对象,例如String、Integer等时,我们可以直接存取,例如:

@Test

public void testString(){

HashMap map = new HashMap<>();

map.put(10, "Kobe");

map.put(23, "James");

System.out.println(map.get(10)); // Kobe

Integer a = 10;

Integer b = 10;

System.out.println(map.get(a) + "---" + map.get(b)); // Kobe---Kobe

}

而如果要存放的是自定义的类对象,我们需要重写hashCode()和equals(),然后才能在Map中正确的存放和获取对象,例如自定义Book类,类中只有Integer类型的price,并且重写了hashCode()和equals(),如下所示:

public class Book{

private Integer price;

public Book(Integer price) {

this.price = price;

}

public Integer getPrice() {

return price;

}

public void setPrice(Integer price) {

this.price = price;

}

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Book book = (Book) o;

return Objects.equals(price, book.price);

}

@Override

public int hashCode() {

return Objects.hash(price);

}

@Override

public String toString() {

return "Book{" +

"price=" + price +

'}';

}

}

然后使用Map存取Book实例对象一切正常,例如:

@Test

public void test(){

HashMap map = new HashMap<>();

Book b1 = new Book(10);

Book b2 = new Book(10);

System.out.println("b1 hashcode is: " + b1.hashCode() + " and b2 hashcode is: " + b2.hashCode());

// b1 hashcode is: 41 and b2 hashcode is: 41

map.put(b1, "AI");

System.out.println(map.get(b1)); // AI

String name = map.get(b2);

System.out.println(name); // AI

}

如果我们不重写这两个方法,或者只重写其中的某一个方法,那么在Map中存取对象时会发生什么呢?

2. 两者都不重写

如果我们在Book类中既不重写hashCode(),也不重写equals()。依然使用HashMap来存取对象,例如:

@Test

public void testNoHashcodeNoEquals() {

HashMap map = new HashMap<>();

Book b1 = new Book(10);

Book b2 = new Book(10);

System.out.println("b1 hashcode is: " + b1.hashCode() + " and b2 hashcode is: " + b2.hashCode());

// b1 hashcode is: 1265094477 and b2 hashcode is: 2125039532

map.put(b1, "AI");

System.out.println(map.get(b1)); // AI

String name = map.get(b2);

System.out.println(name); // null

}

如上代码注释标识的单元测试结果所示,Book对象b1和b2的传参是一样的,但是它们的hashCode不同。这是因为b1和b2是通过两次new得到的对象,那么它们在堆内存中属于两个不同的对象,对应的堆内存地址自然不同。因此,由hashCode标识的内存地址也就不可能相同了。

如果将key=b1, value="AI"的键值对存放在HashMap中,使用b1可以得到"AI",而使用b2得到的是null。这是因为存放b1时,HashMap内部的put()会根据对象的hashCode计算得到它在内部table中的索引,然后将其存放在table对应索引的位置上。由于b1和b2两者的hashCode不同,那么计算得到的索引位置不同,自然使用b2就无法获取到"AI"。

3. 只重写hashCode方法

如果只重写hashCode(),而不重写equals(),执行上述相同的过程会发生什么呢?如下所示:

@Test

public void testNoEquals(){

HashMap map = new HashMap<>();

Book b1 = new Book(10);

Book b2 = new Book(10);

System.out.println("b1 hashcode is: " + b1.hashCode() + " and b2 hashcode is: " + b2.hashCode());

// b1 hashcode is: 41 and b2 hashcode is: 41

map.put(b1, "AI");

System.out.println(map.get(b1)); // AI

Set> entries = map.entrySet();

for (Map.Entry entry : entries) {

System.out.println(entry.toString()); // Book{price=10}=AI

}

String name = map.get(b2);

System.out.println(name); // null

}

从注释标识的输出结果中可以看到,此时b1和b2的hashCode都是41,这就意味着如果将其存放到HashMap中,它们对应的table中的索引应该是相同的。但是,为什么还是无法使用b2获取到"AI"呢?

虽然b1和b2两者指向table中相同的位置,但是根据HashMap的实现源码可知,HashMap最基本的结构是数组 + 链表,即相同索引位置的元素可能会在一条链表上。即时两者的索引相同,也只能说它们处于同一条链上,但并不能证明两者就是相同的。

4. 只重写equals方法

如果只重写equals方法呢?如下所示:

@Test

public void testNoHashCode(){

HashMap map = new HashMap<>();

Book b1 = new Book(10);

Book b2 = new Book(10);

System.out.println("b1 hashcode is: " + b1.hashCode() + " and b2 hashcode is: " + b2.hashCode());

// b1 hashcode is: 1645995473 and b2 hashcode is: 1463801669

map.put(b1, "AI");

System.out.println(map.get(b1)); // AI

String name = map.get(b2);

System.out.println(name); // null

}

由于并没有重写hashCode(),两者的hashCode不同,导致它们在table中的索引位置不同,自然就不同使用b2来获取b1在HashMap中存放的value了。

5. 原理分析

如果不重写hashCode()和equals()方法,那么自定义类默认使用的就是它的超类Object中的hashCode()和equals()。hashCode()的定义和描述如下:

e53bda91d85b74bfe982356ad389ba78.png

这里使用的是native修饰的本地方法hashCode()来直接获取哈希值,它们表示的是对象在内存中的地址。

equals()的定义和描述如下:

840ffc1a723ae0315aae98cdcd250f1f.png

从中可以看到,这里执行的是this == obj,使用==进行对象的比较,其实就是对象引用的比较,不同时间new的对象不同,对应的对象引用自然也就不同。所以,如果不重写这两个方法,那么使用看起来相同的键是无法获取已有的值的。

而在自定义类中重写的这两个方法如下所示:

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

Book book = (Book) o;

return Objects.equals(price, book.price);

}

@Override

public int hashCode() {

return Objects.hash(price);

}

其中hashCode()只关心price是不是相同,只要值相同,那么调用Objects的hashCode()得到的哈希值就是相同的,它们在table中的索引就是相同的。equals()首先判断两个对象的引用是否相同,如果相同,表示两者就是同样的对象,直接返回true。如果两者的引用不同,继续判断传入的对象是否为null或者两者的类型是否相同,如果不同直接返回false,根本就不同同一类型对象。否则,将传入的对象强转为待比较的对象类型,然后再调用Obejcts的equals()进行比较,如果两者的类型相同,传入的对象不为null,而且它们的price也相同,那么证明就是相同的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值