== 和 equals.方法 hashCode方法以及他们与HashMap相关


一,==和equalsjava

解释一下==号,它比较的是一个对象在内存中的地址值,node

一、若是两个对象相同,那么它们的hashCode值必定要相同;算法

二、若是两个对象的hashCode相同,它们并不必定相同(上面说的对象相同指的是用eqauls方法比较。) app


值类型是存储在内存中的堆栈(之后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其自己则存储在堆中。
函数

       ==操做比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同。this

       equals操做表示的两个变量是不是对同一个对象的引用,即堆中的内容是否相同。(Obejct类使用的是==来实现equals,见下文)编码

      ==比较的是2个对象的地址,而equals比较的是2个对象的内容。spa

      显然,当equals为true时,==不必定为true;设计

好比2个字符串对象
code

String s1 = new String("str");

String s2 = new String("str");

若是用==号比较,会返回false,由于建立了两个对象,他们在内存中地址的位置是不同的。


equals的状况比较复杂,它是java.lang.Object类中的一个方法。由于java中全部的类都默认继承于Object,因此全部的类都有这个方法。


在Object类源码中是这样写的。

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

他一样使用==号进行内存地址的比较。可是许多java类中都重写了这个方法,好比String。

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        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里的方法,若是==号比较不相等,还会进行一下值的比较。

因此equals方法具体的做用要看当前的那个类是如何实现重写父类中该方法的。若是没有重写该方法,那么他和==号等价。



二,hashCode和equals

总的来讲,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素能够重复;后者元素无序,但元素不可重复

首先,想要明白hashCode的做用,必需要先知道Java中的集合。  

总的来讲,Java中的集合(Collection)有两类,一类是List,再有一类是Set。 前者集合内的元素是有序的,元素能够重复;后者元素无序,但元素不可重复。

那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 这就是Object.equals方法了。可是,若是每增长一个元素就检查一次,那么当元素不少时,后添加到集合中的元素比较的次数就很是多了。 也就是说,若是集合中如今已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大下降效率。  

因而,Java采用了哈希表的原理。哈希(Hash)其实是我的名,因为他提出一哈希算法的概念,因此就以他的名字命名了。 哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。初学者能够这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并非)。   

这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一会儿能定位到它应该放置的物理位置上。 若是这个位置上没有元素,它就能够直接存储在这个位置上,不用再进行任何比较了;若是这个位置上已经有元素了, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 因此这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大下降了,几乎只须要一两次。 

因此,Java对于eqauls方法和hashCode方法是这样规定的:

一、若是两个对象相同,那么它们的hashCode值必定要相同;

二、若是两个对象的hashCode相同,它们并不必定相同(上面说的对象相同指的是用eqauls方法比较。)  

你固然能够不按要求去作了,但你会发现,相同的对象能够出如今Set集合中。同时,增长新元素的效率会大大降低。

hashcode这个方法是用来鉴定2个对象是否相等的。 那你会说,不是还有equals这个方法吗? 不错,这2个方法都是用来判断2个对象是否相等的。可是他们是有区别的。 通常来说,equals这个方法是给用户调用的,若是你想判断2个对象是否相等,你能够重写equals方法,而后在代码中调用,就能够判断他们是否相等 了。简单来说,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是否是相等。

举个例子,有个学生类,属性只有姓名和性别,那么咱们能够 认为只要姓名和性别相等,那么就说这2个对象是相等的。 hashcode方法通常用户不会去调用,好比在hashmap中,因为key是不能够重复的,他在判断key是否是重复的时候就判断了hashcode 这个方法,并且也用到了equals方法。这里不能够重复是说equals和hashcode只要有一个不等就能够了!因此简单来说,hashcode相 当因而一个对象的编码,就好像文件中的md5,他和equals不一样就在于他返回的是int型的,比较起来不直观。咱们通常在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。举个例子,仍是刚刚的例子,若是姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名 的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。 要从物理上判断2个对象是否相等,用==就能够了。

在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是本身设计其中一个,就要设计另一个。在多数状况 下,这两个函数是不用从新设计的,直接使用它们的默认设计就能够了。可是在一些状况下,这两个函数最好是本身设计,才能确保整个程序的正常运行。最多见的是当 一个对象被加入收集对象(collection object)时,这两个函数必须本身设计。更细化的定义是:若是你想将一个对象A放入另外一个收集对象B里,或者使用这个对象A为查找一个元对象在收集对 象B里位置的钥匙,并支持是否容纳,删除收集对象B里的元对象这样的操做,那么,equals()和hashCode()函数必须开发者本身定义。其余情 况下,这两个函数是不须要定义的。

equals():

它是用于进行两个对象的比较的,是对象内容的比较,固然也能用于进行对象参阅值的比较。什么是对象参阅值的比较?就是两个参阅变量的值得比较,咱们 都知道参阅变量的值其实就是一个数字,这个数字能够当作是鉴别不一样对象的代号。两个对象参阅值的比较,就是两个数字的比较,两个代号的比较。这种比较是默 认的对象比较方式,在Object这个对象中,这种方式就已经设计好了。因此你也不用本身来重写,浪费没必要要的时间。

对象内容的比较才是设计equals()的真正目的,Java语言对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”。

hashCode():
这个函数返回的就是一个用来进行赫希操做的整型代号,请不要把这个代号和前面所说的参阅变量所表明的代号弄混了。后者不只仅是个代号还具备在内存中才查找对 象的位置的功能。hashCode()所返回的值是用来分类对象在一些特定的收集对象中的位置。这些对象是HashMap, Hashtable, HashSet,等等。这个函数和上面的equals()函数必须本身设计,用来协助HashMap, Hashtable, HashSet,等等对本身所收集的大量对象进行搜寻和定位。

这些收集对象究竟如何工做的,想象每一个元对象hashCode是一个箱子的 编码,按照编码,每一个元对象就是根据hashCode()提供的代号纳入相应的箱子里。全部的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象,咱们须要寻找一个元对象时,先看它的代码,就是hashCode()返回的整型值,这样咱们找到它所在的箱子,而后在箱子里,每 个元对象都拿出来一个个和咱们要找的对象进行对比,若是两个对象的内容相等,咱们的搜寻也就结束。这种操做须要两个重要的信息,一是对象的 hashCode(),还有一个是对象内容对比的结果。

hashCode()的返回值和equals()的关系以下:

•若是x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

•若是x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

为何这两个规则是这样的,缘由其实很简单,拿HashSet来讲吧,HashSet能够拥有一个或更多的箱子,在同一个箱子中能够有一个 或更多的独特元对象(HashSet所容纳的必须是独特的元对象)。这个例子说明一个元对象能够和其余不一样的元对象拥有相同的hashCode。可是一个 元对象只能和拥有一样内容的元对象相等。因此这两个规则必须成立。

设计这两个函数所要注意到的:
若是你设计的对象类型并不使用于收集性对象,那么没有必要本身再设计这两个函数的处理方式。这是正确的面向对象设计方法,任何用户一时用不到的功能,就先不要设计,以避免给往后功能扩展带来麻烦。

若是你在设计时想别出心裁,不遵照以上的两套规则,那么劝你仍是不要作这样想入非非的事。我尚未遇到过哪个开发者和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规则的状况时,都是做为设计错误处理。

当一个对象类型做为收集型对象的元对象时,这个对象应该拥有本身处理equals(),和/或处理hashCode()的设计,并且要遵照前面所说 的两种原则。equals()先要查null和是不是同一类型。查同一类型是为了不出现ClassCastException这样的异常给丢出来。查 null是为了不出现NullPointerException这样的异常给丢出来。

若是你的对象里面容纳的数据过多,那么这两个函数 equals()和hashCode()将会变得效率低。若是对象中拥有没法serialized的数据,equals()有可能在操做中出现错误。想象 一个对象x,它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠 这个整型数据,那么,这个对象在serialization以前和以后,是否同样?结果是不同。由于serialization以前的整型数据是有效的 数据,在serialization以后,这个整型数据的值并无存储下来,再从新由二进制数据流转换成对象后,二者(对象在serialization 以前和以后)的状态已经不一样了。这也是要注意的。

 


在通常的应用中你不须要了解hashcode的用法,但当你用到hashmap,hashset等集合类时要注意下hashcode。

    你想经过一个object的key来拿hashmap的value,hashmap的工做方法是,经过你传入的object的hashcode在内存中找地址,当找到这个地址后再经过equals方法来比较这个地址中的内容是否和你原来放进去的同样,同样就取出value。

    因此这里要匹配2部分,hashcode和equals但假如说你new一个object做为key去拿value是永远得不到结果的,由于每次new一个object,这个object的hashcode是永远不一样的,因此咱们要重写hashcode,你能够令你的hashcode是object中的一个恒量,这样永远能够经过你的object的hashcode来找到key的地址,而后你要重写你的equals方法,使内存中的内容也相等。。。

HashMap相关 的源码以下:


public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);//调用putVal
}

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//使用对象key的hashCode计算出hash值
    //上文中所说的使用这个hash值去内存中找
}


final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //若是table为空或者长度为0,则resize()
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null) //对应的hash值在集合中没找到
        tab[i] = newNode(hash, key, value, null);//new 了一个Node,直接插入。应该是第一个
    else {
        Node<K,V> e; K k;
        //第一个node的hash值即为要加入元素的hash
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
        
            e = p;
        else if (p instanceof TreeNode)//第一个节点是TreeNode,即tree-bin
       
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {//不是TreeNode,即为链表,遍历链表
            for (int binCount = 0; ; ++binCount) {
             /*到达链表的尾端也没有找到key值相同的节点,使用equals方法判断
              *则生成一个新的Node,而且判断链表的节点个数是否是到达转换成红黑树的上界
              *达到,则转换成红黑树
              */
                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);
            //返回旧的value值
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
相关文章
相关标签/搜索