聊聊Java中的 ” == “、equals以及hashCode

前段时间一直在工作中使用Java,由于有一些C++功底,于是简单看了一下Java相关的语法便开始编写代码,结果在创建一个自定义类,并将自定义类放入ArrayList中,之后查找ArrayList是否有此元素的时候,发现怎么也查询不到对应的元素。在网上搜了一下资料,发现原因是没有重写对象的equals()方法,导致无法查找到对应的对象。之后由查了与之联系的相关资料,便有了以下的总结。

关于 “ == ”

“ ==
”操作符主要比较的是操作符两端对象的内存地址。如果两个对象的内存地址是一致的,那么就返回true。反之,则返回false。

话不多说,先来看看下面这段代码:

程序1-1

再来看看会输出什么:

上面程序的输出结果

怎么样,这个结果你想到了吗?

以下是关于上面结果的解释:

其实在Java中对于字符串的创建,有两种形式。

  • 一种是形如String a = “Hello”这样的字面量形式;
  • 另一种是形如String d = new String(“Hello”)这样的构造对象的方法。

一、概述

          
在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个。当然在多数情况下,这两个方法是不用我们考虑的,直接使用默认方法就可以帮助我们解决很多问题。但是在有些情况,我们必须要自己动手来实现它,才能确保程序更好的运作。

这篇总结的形式是提出个问题,然后给出问题的答案。这是目前学习知识的一种尝试,可以让学习更有目的。

字面量形式

在JVM中,为了减少对字符串变量的重复创建,其拥有一段特殊的内存空间,这段内存被称为字符串常量池(constant
pool)
或者是“字符串字面量池”

程序1-1中,我们首先使用 String a = “Hello”
创建了一个对象。此时默认字符串常量池中没有内容为“Hello”的对象存在。当JVM在字符串常量池中没有找到内容为”Hello”的对象时,就会创建一个内容为”Hello”的对象,并将它的引用返回给变量a。

那么当我们在后面使用 String b = “Hello” 的时候,会发生什么情况呢?
首先JVM会在字符串常量池中查询是否有内容为“Hello”的对象。因为此时字符串常量池中已经有内容为“Hello”的对象了,故JVM不会创建新的地址空间,而是将原有的“Hello”对象的引用返回给b——所以此时变量a和变量b拥有的是同一个对象引用,即指向的是同一块内存地址,因此使用
== 操作符对a和b进行比较,结果为true。

1.1 规则

粗略总结一下在JavaDoc中所规定hashcode方法的合约:

     Objects that are equal must have the same hash code within a
running process

   (在程序执行期间,如果两个对象相等,那么它们的哈希值必须相等)

  注意,下面两条常见错误想法:

  • Unequal objects must have different hash codes – WRONG!
  • Objects with the same hash code must be equal – WRONG!

关于hashcode,你必须知道的三件事

  1. Whenever you implement equals, you MUST also implement
    hashCode
  2. Never misuse hashCode as a key
  3. Do not use hashCode in distributed applications

详情见:The 3 things you should know about
hashCode()

 

对于hashCode,我们应该遵循如下规则

      1.
在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。

      2. 如果两个对象根据equals(Object
o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。

      3. 如果两个对象根据equals(Object
o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。

 

对于equals,我们必须遵循如下规则

      对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

      自反性:x.equals(x)必须返回是“true”。

      传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

      一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

Q1.什么时候应当重写对象的equals方法?

答:一般在我们需要进行值比较的时候,是需要重写对象的equals方法的。而例外情况在《effective
java》的第7条“在改写equals的时候请遵守通用约定”中清楚描述了。

我们知道,在Java中,每个对象都继承于Object.如果不重写,则默认的equals代码如下所示:

public boolean euqals(Object obj){
    return this == obj;
}

由上面的代码可以看出,equal默认是使用“==”来判断两个对象是否相等。两个对象使用“==”比较的是对象的地址,只有两个引用指向的对象相同的时候,“==”才返回true。所以,在开头的例子中,就需要重写equals方法,让两个对象有equals的时候。

使用new来构造一个对象

然而当我们new一个字符串对象时,不管字符串常量池中是否有内容相同的对象,JVM都会创造一个新的对象,所以地址肯定不一样,因此使用
== 操作符对a和d进行比较,结果为false。

1.2 作用

hashcode:

      常被系统用来快速检索对象。

equals:

     
1、如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;

     
2、String、Date等类对equals方法进行了重写,它们比较的是所指向的对象的内容。

当然在重写equals方法中,可以指定比较对象的地址,如果这样的话,就失去了重写的意义,所以重写,一般是比较对象的内容。

注意:equals方法不能作用于基本数据类型的变量。

Q2.如何重写equals?

答:首先,当改写equals方法时,需要保证满足它的通用约定。这些约定如下所示:

  • 自反性,对于任意的引用值x,x.equals(x)一定为true。
  • 对称性,对于任意的引用值x和y,当且仅当y.equals(x)时,x.equals(y)也一定返回true.
  • 传递性,对于任意的引用值x,y,z。如果x.equals(y)返回true,y.euqals(z)返回true,则x.equals(z)也返回true。
  • 一致性,对于任意的引用值x和y,如果用于equals比较的对象信息没有修改,那么,多次调用x.equals(y)要么一致返回true,要么一致返回false.
  • 非空性,所有的对象都必须不等于null。

其实我觉的一个简单的方法是参照String的equals方法即可,官方出版,满足各种要求。其代码如下所示

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n– != 0) {
                if (v1[i++] != v2[j++])
                    return false;
            }
            return true;
        }
    }
    return false;
}

函数的解释如下所示:

  1. 使用==检查“实参是否是指向对象的一个引用”。
  2. 使用instanceof检查实参是否和本对象同类,如果不同类,就不相等。
  3. 将实参转换为正确的类型。
  4. 根据类的定义,检查实现此对象值相等的各个条件。

更详细的信息,还是请看《effective
java》的第7条“在改写equals的时候请遵守通用约定”。

关于equals

在Object类中,equals方法源码如下:

Object类中的equals方法

我们可以看到,在Object类中的equals方法其实在内部也是使用了“ ==
”操作符—–如果两个对象地址一样则返回true,反之则返回false。
既然equals方法里面也是使用“ ==
”,那为什么还要设立一个equals方法,而不是直接用“ == ”操作符呢?

别急,这只是Object类里面的equals方法,其实绝大部分Object的子类都对equals方法进行了改写:

String类中的equals方法

HashMap中的equals方法

除了会比较内存地址,以上两个类中的equals方法还会比较对象所包含的内容。如果两个对象所包含的内容相同,equals方法也是返回true。

官方文档中对equals方法的描述:

equals方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值x,x.equals(x)都应返回true。
  • 对称性:对于任何非空引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)才应返回true。
  • 传递性:对于任何非空引用值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。
    Object类的equals方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值x和y,当且仅当x和y引用同一个对象时,此方法才返回true(x
    == y具有值true)。
    注意:当此方法被重写时,通常有必要重写hashCode方法,以维护hashCode方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

其实在很多时候,我们都需要改写equals方法以适应我们的实际情况:

下面是一个Person类,包含name和age两个属性。

Person类

现在我们创建两个Person对象,但是name和age分别都设置一样的值,同时使用equals方法对这两个对象进行比较:

创建两个Person对象并用equals方法对其进行比较

比较之后的结果

虽然这从JVM的角度来看这个程序是对的,可是结果并不是我们想要的:对象one和对象two虽然是两个不同的对象,但是它们包含的元素的值是相同的,也就是说one和two应该都表示的是同一个人—–name
为 EakonZhao,年龄为19。可是为什么调用equals方法之后输出的却是false呢?

原因:由于我们没有对equals方法进行改写,所以当我们在调用equals方法的时候实际上调用的是Object类的equals方法。从前面我们可以得知,Object的equals方法在内部是直接使用“
== ”操作符对对象进行比较,这样当然会返回false啦!

下面我将对equals方法进行改写,以满足我们的需求;

改写之后的equals方法

改写equals方法之后的输出结果

对于改写equals方法之后是否需要改写hashCode方法以维护hashCode方法的常规协定,我在介绍完hashCode方法之后会继续讲

1.3 关联

至于hashcode与equals之间的关联关系,我们只需要记住如下即可:

  •       如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
  •      
    如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

 

因此,在重写equals方法时,总是重写hashCode方法。改写后equals方法,使得两个不同的实例在逻辑上是相等的;如果不重写hashCode方法,则hashCode判断两个不同实例是不同的;导致违反“如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。”

 

Q3.修改equals时需要注意什么?

答:大致需要注意以下几点:

若修改equals方法,也请修改hashCode方法

首先这个是语言的一个约定),这么做的一个原因是当此对象作为哈希容器的元素时,需要依赖hashCode,对象默认的hashCode是返回一个此对象特有的hashCode,不同的对象的hashCode返回值是不一样的,而哈希容器处理元素时,是按照对象的哈希值将对象分配到不同的桶中,若我们不重写对象的hashCode,那么值相等的对象产生的哈希值也会不同,这样当在哈希容器中查找时,会找不到对应的元素。

更详细的信息请看《effective
Java》的第8条“改写equals时总是要改写hashCode”。

重写时保证函数声明的正确

请注意equals的声明是

public boolean equals(Object obj)

参数类型是Object,如果参数类型是此对象类型的话,如下:

class Point{
final int x;
final int y;
public void Point(int x, int y)
    this.x = x;
    this.y = y;
}
public boolean euqals(Point obj){
         return (this.x == obj.x && this.y == obj.y);
    }
}

下面代码执行是按照我们的预期执行的。

Point a(1, 2);
Poinr b(1, 2);
System.out.println(a.equals(b));// 输出true

但是如果将类A放入容器中,则会出问题

import java.util.HashSet;

HashSet<Point> coll = new HashSet<Point>();
coll.add(a);
System.out.println(coll.contains(b));// 输出false

这是由于HashSet中的contains方法中调用的是equals(Object obj),而Point中的equals(Object
obj)仍是Object的equals,这个方法在前面已经说过了,比较的是对象的地址,所以在coll中调用contains(b)时,当然得不到true。

当有继承关系时注意equals的正确
当一个类重写equals方法后,另一个类继承此类,此时,可能会违反前面说到的对称性,代码如下所示:

public class ColoredPoint extends Point { 
    private final Color color;
    public ColoredPoint(int x, int y, Color color) {
        super(x, y);
        this.color = color;
    }

    @Override 
    public boolean equals(Object other) {
        boolean result = false;
        if (other instanceof ColoredPoint) {
            ColoredPoint that = (ColoredPoint) other;
            result = (this.color.equals(that.color) && super.equals(that));
        }
        return result;
    }
}

当我们作比较时

Point p = new Point(1, 2);
ColoredPoint cp = new ColoredPoint(1, 2, Color.RED);
System.out.println(p.equals(cp)); //输出ture
System.out.println(cp.equals(p)); //输出false

原因是当调用Point.equals的时候,只比较了Point的x和y坐标,同时ColoredPoint也是Point类型,所以上面第三行代码相等,而调用ColoredPoint的时候,Point不是ColoredPoint类型,这样就导致第四行代码输出false。

若我们忽略Color的信息来比较呢,例如将ColoredPoint的equals方法改为:

@overwrite
public boolean equals(Object obj){
    if((obj instanceof Point)){
        return false;
    }

    if(!(obj instanceof ColoredPoint)){
        return obj.equals(this);
    }

    return super.equals(obj) && ((ColoredPoint)obj).color == color;
}

这样就保证了对称性,但是却违反了传递性,即下面的情况:

ColoredPoint cp1 = new ColoredPoint(1, 2, Color.RED);
Point p = new Point(1, 2);
ColoredPoint cp2 = new ColoredPoint(1, 2, Color.BLUE);
System.out.println(cp1.equals(p)); //true
System.out.println(p.equals(cp2)); //true
System.out.println(cp1.equals(cp2)); //false

面对这种情况,大致有两种解决方案,一种酷壳的文章–如何在Java中避免equals方法的隐藏陷阱的最后一条,断绝了Point和ColoredPoint相等的可能,这是一种处理方法,认为Point和ColoredPoint是不同的。另一种方法是effective
Java上提出的,使用聚合而不是继承,将Point作为ColoredPoint的一个成员变量。目前我倾向于这种方法,因为聚合比继承更灵活,耦合更低。这种方法的代码如下所示:

class ColoredPoint{
    private final Point point;
    private final Color color;

    public Point asPoint(){
        return point;
    }

    public boolean equals(Object obj){
        boolean ret = false;
        if(obj instanceof ColoredPoint){
            ColoredPoint that = (ColoredPoint)obj;
            ret = that.point.equals(point) && color.equals(that.color);
        }
        return ret;
    }
}

当ColoredPoint需要比较坐标时,可以调用asPoint方法来转化为坐标进行比较。其他情况比较坐标和颜色,这样就可以解决上面关于对称性和传递性的问题了。

以上就是全文的内容,由于水平有限,文章中难免会有错误,希望大家指正。谢谢

关于在使用”==”操作符和equals方法时发生的自动装箱与自动拆箱现象

二、equals详解

简单介绍一下自动拆箱和自动装箱

自动装箱:将Java的基本类型转换成包装器类型;
自动拆箱:将Java的包装器类型转换成基本类型。

基本数据类型及其包装器类型

自动装箱与自动拆箱

如上图所示,其实这是一个非常简单的程序,但是实际上Java自动帮我们完成了拆装箱的操作。

背景:
在JDK1.5之前,如果我们要生成一个数值为1的Integer对象,那么我们必须使用下面这行代码:

创建一个数值为1的Integer对象

可是引入了自动装箱功能之后,我们只需使用这样一行代码就能完成了:

自动装箱

这是如何实现的呢?下面我们将上面那段代码的class文件反编译一下:

反编译之后的代码

我们可以看到,其实Java是使用了
Integer的valueOf(int)方法来完成自动装箱的,而在拆箱过程当中调用的是Integer的intValue方法。对于其他的包装器类型来说,其实这两个过程也是类似的。

装箱过程是通过调用包装器类型的valueOf方法实现的,拆箱过程是通过调用包装器类型的xxValue方法实现的(其中xx代表的是对应的基本类型)。

让我们再来看看下面这段代码:

Integer缓存示例

为什么上面两条打印代码会输出不同的结果?
原因也很简单:与字符串常量池类似,这其实也是JVM节省内存的一个方法—–对于Integer类型的对象来说,如果我们要创建的Integer对象的数值在
[-128,127]的区间之内,那么JVM就会在缓存中查找,看看有没有已经存放在缓存中的数值一样的Integer对象。如果有,就返回已经存在的对象的引用。

关于使用equals方法时发生的自动拆装箱现象就不赘述了,其实很容易理解。

小结:
“==”运算符其实比较的是地址相不相同,而equals方法比较的是值相不相同。

2.1 equals的设计指导

public class Person

{

    private String    name;

    private int age;

 

    public String getName()

    {

        return name;

    }

 

    public void setName(String name)

    {

        this.name = name;

    }

 

    public int getAge()

    {

        return age;

    }

 

    public void setAge(int age)

    {

        this.age = age;

    }

    

     @Override

     public boolean equals(Object other)

     {

         // 1、 自反性

         if (other == this)

         {

             return true;

         }

         // 2、判断空值

         if (other == null)

         {

             return false;

         }

         // 3、对象所属类的类型判断

         if (!getClass().equals(other.getClass()))

         {

             return false;

         }

         // 4、对象的类型转换

         Person person = (Person) other;

         // 5、针对所需比较的域进行比较

         if ((name.equals(person.name))&&(age==person.age))

         {

             return true;

         }

    

         return false;

     }

}

在第3点,对象所属类的类型判断,经常有两种方式:

  1. getClass
  2. instanceof

如何选择这两种方式呢?

如果子类能够拥有自己的相等概念,则对称性需求将强制采用getClass进行检测。

如果由超类决定相等的概念,那么就可以使用instanceof进行检测,这样可以在不同子类的对象之间进行相等的比较。

查看经典String中equals的方法,如下:

public boolean equals(Object anObject)

{

    // 1、自反性判断

    if (this == anObject)

    {

        return true;

    }

    // 3、类型判断

    if (anObject instanceof String)

    {

        ///4、类型转换

        String anotherString = (String) anObject;

        ///5、针对所需比较的域进行比较

        int n = value.length;

        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;

}

虽然,String没有对null值判断,但在其注释有解释:

* Compares this string to the specified object.  The result is {@code
* true} if and only if the argument is not {@code null} and is a
{@code
* String} object that represents the same sequence of characters as
this
* object.

 

 

 

关于hashCode

官方文档中队hashCode方法的描述:

public int hashCode()
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如java.util.Hashtable
提供的哈希表)的性能。
hashCode的常规协定是:

  • 在 Java
    应用程序执行期间,在对同一对象多次调用hashCode方法时,必须一致地返回相同的整数,前提是将对象进行equals比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 如果根据equals(Object)方法,两个对象是相等的,那么对这两个对象中的每个对象调用hashCode方法都必须生成相同的整数结果。
  • 如果根据equals(java.lang.Object)方法,两个对象不相等,那么对这两个对象中的任一对象上调用hashCode方法要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由Object类定义的 hashCode
方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是
JavaTM
编程语言不需要这种实现技巧。)

上面这段话,简单的来说就是以下几点:

  • hashCode存在的意义主要是提供查找的快捷性,比如说在Hashtable、HashMap中等。hashCode是用来在散列存储结构中确定对象存储的位置的;
  • 如果两个对象相同,即调用equals方法返回的是true,那么它俩的hashCode值也要相同;
  • 如果equals方法被改写了,那么hashCode方法也尽量要改写,并且产生hashCode的对象也要和equals的对象保持一致;
  • 两个对象的hashCode相同并不代表两个对象就一定相同,也就是不一定适用于equals(java.lang.Object)方法,只能够说明这两个对象在散列存储对象中,如Hashtable中,是存放在同一个篮子里的。(关于Hashtable的介绍我在后面会开博客探究其源码)

下面这段话是从别人那里转过来的,我觉得能帮助理解hashCode:

  • hashcode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有
    例如内存中有这样的位置
    01234567
    而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,如果不用hashcode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。
    但如果用hashcode那就会使效率提高很多。
    我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8求余数直接找到存放的位置了。
  • 但是如果两个类有相同的hashcode怎么办呢?(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义equals了。
    也就是说,我们先通过hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过equals来在这个桶里找到我们要的类。
    那么。重写了equals(),为什么还要重写hashCode()呢?
    想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊

下面是代码示例:
我新建了一个HashExample类,里面定义了一个属性为id,并改写了hashCode方法:

HashExample类

现在我new两个对象,这两个对象的id我都赋予相同的值,并将它们两个存入一个Set(Set中的元素是不重复的)当中,然后分别输出它们两个的hashCode以及使用equals方法比较的结果以及将这个Set也输出:

改写equals方法之前

以上这个示例,我们只是重写了hashCode方法,从上面的结果可以看出,虽然两个对象的hashCode相等,但是实际上两个对象并不是相等;,我们没有重写equals方法,那么就会调用object默认的equals方法,是比较两个对象的引用是不是相同,显示这是两个不同的对象,两个对象的引用肯定是不定的。这里我们将生成的对象放到了hashSet中,而hashSet中只能够存放唯一的对象,也就是相同的(适用于equals方法)的对象只会存放一个,但是这里实际上是两个对象a,b都被放到了HashSet中,这样hashSet就失去了他本身的意义了。

现在把equals也改写一下:

改写之后的equals方法

改写equals方法之后的输出结果

现在我们可以看到,这两个对象已经完全相等了,并且hashSet中也只存放了一份对象。

三、hashCode详解

3.1 hashCode设计指导

Josh Bloch在他的书籍《Effective
Java》告诉我们重写hashcode方法的最佳实践方式。

一个好的hashcode方法通常最好是不相等的对象产生不相等的hash值,理想情况下,hashcode方法应该把集合中不相等的实例均匀分布到所有可能的hash值上面。

下面就详细介绍一下如何设计这个算法。这个算法是有现成的参考的,算法的具体步骤就是:

1、把某个非零常数值(一般取素数),例如17,保存在int变量result中;

2、对于对象中每一个关键域f(指equals方法中考虑的每一个域),计算int类型的哈希值c:

  • boolean型,计算(f ? 0 : 1);
  • byte,char,short型,计算(int)f;
  • long型,计算(int) (f ^ (f>>>32));
  • float型,计算Float.floatToIntBits(afloat);
  • double型,计算Double.doubleToLongBits(adouble)得到一个long,然后再执行long型的计算方式;
  • 对象引用,递归调用它的hashCode方法;
  • 数组域,对其中每个元素调用它的hashCode方法。

3、步骤2中,计算得到的散列码保存到int类型的变量c中,然后再执行result=31*result+c;(其中31可以自选,最好是素数)

4、最后返回result。

核心公式:

result = 基数(31) * result + 哈希值(c:算法步骤2获得)

例如,Person类的hashCode

@Override
public int hashCode()
{
    int result = 17; 
    result = 31 * result + age;
    result = 31 * result + stringToHashCode(name);
    return result;
}

private int stringToHashCode(String str)
{
    int result = 17;
    if (str.length()>0)
    {
        char[] value = str.toCharArray();
        for (int i = 0; i < value.length; i++)
        {
            char c = value[i];
            result = 31 * result + c;    
        }
    }
    return result;
}

查看String中hashCode代码设计如下:

/** The value is used for character storage. */
private final char    value[];

/** Cache the hash code for the string */
private int            hash;        // Default to 0

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;
}

 

参考:

1、浅谈Java中的hashcode方法

2、Java中hashCode的作用

3、Java提高篇(二六)——hashCode

4、hashCode与equals的区别与联系

5、Java核心技术卷1

发表评论

电子邮件地址不会被公开。 必填项已用*标注