重载 hashCode()

2024-06-03 19:58
文章标签 重载 hashcode

本文主要是介绍重载 hashCode(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在明白了HashMap 具有哪些功能之后,学习如何写一个 hashCode()会更有意义。

 

首先,你无法控制bucket 数组的索引值的产生。这个值依赖于具体的 HashMap 对象的

容量,而容量的改变与负载因子和容器有多满有关。hashCode()生成的结果,经过处理后

成为“桶”的索引(在 SimpleHashMap 中,只是对其取模,模数为 bucket 数组的大小)。

 

设计 hashCode()时最重要的因素就是:无论何时,对同一个对象调用 hashCode()都应

该生成同样的值。如果在将一个对象用 put()添加进 HashMap 时,产生一个 hashCode()

值,而用get()取出时,却产生了另一个 hashCode()值,那么你就无法重新取得该对象了。

所以,如果你的hashCode()依赖于对象中易变的数据,用户就必须当心了,因为此数据

发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的“键”。

 

此外,你也不应该使hashCode()依赖于具有唯一性的对象信息。尤其是使用 this 的值,

这只能作出很糟糕的hashCode()。因为你无法生成一个新的“键”,使之与 put()中原始

的“键值对”中的“键”相同。这正是 SpringDetector.java 的问题所在,因为它默认的

hashCode()使用的是对象的地址。所以,你应该使用对象内有意义的识别信息。

 

下面以 String 类为例。String 有个特点:如果程序中有多个 String 对象,都包含相同的

字符串序列,那么这些 String 对象都映射到同一块内存区域(附录 A 详细描述此机制)。

所以 newString(“hello”)生成的两个实例,虽然是相互独立的,但是对它们使用

hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况:

 

//:c11:StringHashCode.java

import com.bruceeckel.simpletest.*;

 

public   class StringHashCode { 

private   static Test monitor = new Test();

public   static    void main(String[] args) {

    System.out.println("Hello".hashCode());

    System.out.println("Hello".hashCode());

    monitor.expect(new String[] {

"69609650",

"69609650"

    });

  }

}   ///:~

 

对于 String 而言,hashCode()明显是基于 String 的内容的。

 

因此,要想使hashCode()实用,它必须速度快,并且必须有意义。也就是说,它必须基

于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而

不是唯一性),但是通过 hashCode()和 equals(),必须能够完全确定对象的身份。

 

因为在生成bucket 的索引前,hashCode()还需要进一步的处理,所以散列码的生成范围

并不重要,只要是int 即可。

 

还有另一个影响因素:好的 hashCode()应该产生分布均匀的散列码。如果散列码都集中

在一块,那么HashMap 或者 HashSet 在某些区域的负载会很重,就不会有分布均匀的散

列函数那么快。

 

EffectiveJava (Addison-Wesley2001)           这本书中,Joshua Bloch 为怎样写出一份

像样的 hashCode(),给出了基本的指导:

 

1. 给 int 变量 result 赋予某个非零值常量,例如 17。

2.为对象内每个有意义的属性 f(即每个可以作 equals()操作的属性)计算出一个

int 散列码 c:


 

 

域类型

boolean


 

c = (f ? 0 : 1)


 

计算


byte,char, short, or     c = (int)f

int


long

float

double


c = (int)(f ^ (f >>>32))

c = Float.floatToIntBits(f);

long l = Double.doubleToLongBits(f);

c = (int)(l ^ (l >>> 32))


Object,    其 equals() c = f.hashCode( )

调用这个域的

equals( )

数组


 

 

 

对每个元素应用上述规则


 

 

1. 合并计算得到的散列码:

result = 37 *result + c;

2.返回 result

3.检查 hashCode()最后生成的结果,确保相同的对象有相同的散列码。

 

下面便是遵循这些指导的一个例子:

 

//:c11:CountedString.java

// Creating agood hashCode().

import com.bruceeckel.simpletest.*;

import java.util.*;

 

public   class CountedString {

private   static Test monitor = new Test();

private   static List created = new ArrayList();

private String s;

private   int id = 0;

public CountedString(String str) {

    s = str;

    created.add(s);

    Iterator it = created.iterator();

// Id is thetotal number of instances

// of thisstring in use by CountedString:

while(it.hasNext())

if(it.next().equals(s))

        id++;

  }

public String toString() {

return   "String: " + s + " id: " + id +

"hashCode(): "+ hashCode();

  }

public   int hashCode() {

// Very simpleapproach:

// returns.hashCode() * id;

// Using JoshuaBloch's recipe:

int result = 17;

    result = 37*result + s.hashCode();

    result = 37*result + id;

return result;

  }

public   boolean equals(Object o) {

return (o    instanceof CountedString)



 

 

 

      && s.equals(((CountedString)o).s)

      && id == ((CountedString)o).id;

  }

public   static    void main(String[] args) {

    Map map = new HashMap();

    CountedString[] cs =     new CountedString[10];

for(int i = 0; i < cs.length; i++) {

      cs[i] = new CountedString("hi");

      map.put(cs[i], new Integer(i));

    }

    System.out.println(map);

for(int i = 0; i < cs.length; i++) {

      System.out.println("Looking up " + cs[i]);

      System.out.println(map.get(cs[i]));

    }

    monitor.expect(new String[] {

"{String:hi id: 4 hashCode(): 146450=3," +

" String:hi id: 10 hashCode(): 146456=9," +

" String:hi id: 6 hashCode(): 146452=5," +

" String:hi id: 1 hashCode(): 146447=0," +

" String:hi id: 9 hashCode(): 146455=8," +

" String:hi id: 8 hashCode(): 146454=7," +

" String:hi id: 3 hashCode(): 146449=2," +

" String:hi id: 5 hashCode(): 146451=4," +

" String:hi id: 7 hashCode(): 146453=6," +

" String:hi id: 2 hashCode(): 146448=1}",

"Lookingup String: hi id: 1 hashCode(): 146447",

"0",

"Lookingup String: hi id: 2 hashCode(): 146448",

"1",

"Lookingup String: hi id: 3 hashCode(): 146449",

"2",

"Lookingup String: hi id: 4 hashCode(): 146450",

"3",

"Lookingup String: hi id: 5 hashCode(): 146451",

"4",

"Lookingup String: hi id: 6 hashCode(): 146452",

"5",

"Lookingup String: hi id: 7 hashCode(): 146453",

"6",

"Lookingup String: hi id: 8 hashCode(): 146454",

"7",

"Lookingup String: hi id: 9 hashCode(): 146455",

"8",

"Lookingup String: hi id: 10 hashCode(): 146456",

"9"

    });

  }

}   ///:~

 

CountedString 由一个 String 和一个 id 组成,此 id 代表包含相同 String 的

CountedString 对象的编号。所有的 String 都被存储在 static ArrayList 中,构造器中

使用迭代器遍历此ArrayList,此时完成对 id 的计算。

 

hashCode()和 equals()都基于 CountedString 的这两个属性来生成结果;如果它们只

基于 String 或者只基于 id,不同的对象就可能产生相同的值。

 

在 main()中,使用相同的 String 创建了一串 CountedString 对象。以此说明,虽然 String

相同,但是由于id 不同,所以使得它们的散列码并不相同。在程序中,HashMap 被打印

了出来,因此你可以看到它内部是如何存储元素的(以无法辨别的次序),然后程序单独

查询每一个“键”,以此证明查询机制工作正常。

 

为你的类编写正确的hashCode()和equals()是很需要技巧的。Apache的“Jakarta

Commons”项目中有许多工具可以帮助你,可在jakarta.apache.org/commons

“lang”下面找到。


这篇关于重载 hashCode()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1027963

相关文章

C++操作符重载实例(独立函数)

C++操作符重载实例,我们把坐标值CVector的加法进行重载,计算c3=c1+c2时,也就是计算x3=x1+x2,y3=y1+y2,今天我们以独立函数的方式重载操作符+(加号),以下是C++代码: c1802.cpp源代码: D:\YcjWork\CppTour>vim c1802.cpp #include <iostream>using namespace std;/*** 以独立函数

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

C++可以被重载的操作符Overloadable operators

C++允许绝大多数操作符被重载,也就是重新定义操作符实现的功能,这样它们的行为可以被设计出来以适应所有的数据类型,包括类。 以下是C++可以被重载的操作符(Overloadable operators): //四则运算符+ - * / %+= -= *= /= %=//比较运算符> >= == != //赋值运算符= //位操作

c++/《重载操作符》

为什么要对运算符进行重载:         C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型(类)是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。 <返回类型说明符> operator <运算符符号>(<参数表>) { <函数体> }

java基础总结10-面向对象6(方法的重载)

1 方法的重载 方法名一样,但参数不一样,这就是重载(overload)。   所谓的参数不一样,主要有两点:第一是参数的个数不一样,第二是参数的类型不一样。只要这两方面有其中的一方面不一样就可以构成方法的重载了。 package cn.galc.test;public class TestOverLoad {void max(int a, int b) {System.out.prin

C++ 第7章 运算符重载

7.1 运算符重载规则 7.1.1 重载运算符的限制 可重载运算符: + - * / % ^ & | ~! = < > += -+ *= /= %=^= &= |= << >> >>= <<= == !=<= >= && || ++ -- ->* , ->[] () new delete 不可重载运算符: . .* :: ?: sizeof 重载运算符函数可以对运算符做出新的解

谈谈函数返回值为什么不能重载

一、函数的定义:       函数将有效的输入值变换为唯一的输出值,同一输入总是对应同一输出。       计算机本质是对抽象数学公式的具体实现,并以此具体实现来解决现实生活中的实际问题。 注:wiki百科对 “函数” 的定义如图,图比较大,请点击打开详情,左右拖动查看 全部内容。 二、悖论      反过来设想一下,如果返回值的类型 能用来 重载,那么对于相同的输入值,程序怎么决定

重写equals和hashCode的原则规范

当符合以下条件时不需要重写equals方法:     1.     一个类的每一个实例本质上都是唯一的。     2.     不关心一个类是否提供了“逻辑相等”的测试功能     3.     超类已经改写了equals方法,并且从超类继承过来的行为对于子类也是合适的。     4.     一个类时私有的或者是package私有的,并且可以确定它的equals方法永远不会被调用。(这

12 Python面向对象编程:运算符重载

本篇是 Python 系列教程第 12 篇,更多内容敬请访问我的 Python 合集 在理解运算符重载之前我们已经知道了什么是方法重载,方法重载就是子类继承父类并且定义了一个和父类一样的方法。 知道了什么是重载,也知道了什么是运算符(加减乘除等),那么运算符重载也很好理解了,其实就是在类里面也定义一些特殊方法,使得调用这些方法能实现类对象的加减乘除。当然方法名不是随意取的,要和运算符对应

Java中的重载感悟

引言 在面向对象编程语言中,方法重载(Overloading)是一种允许创建具有相同名称但参数列表不同的多个方法的功能。这种方法提高了代码的可读性和组织性,同时也简化了接口设计。本文将详细介绍Java中的方法重载概念、其实现方式以及一些最佳实践。 什么是方法重载? 方法重载是指在同一类中可以定义多个同名的方法,但是这些方法的参数列表必须有所不同。参数列表的不同可以体现在参数的数量、类型或顺序上。