目录
- Java中的数据类型
- 什么时候用关系操作符
==
,什么时候用equals办法? equals
办法,为什么报了 空指针 java.lang.NullPointerException?hashCode
办法有什么作用?hashCode 和 equals 的关系?- 为什么每个笼罩了equals办法的类中,也必须笼罩hashCode办法?
数据类型
java中的数据类型,可分为两类:
1.根本数据类型(原始数据类型)
byte,short,char,int,long,float,double,boolean 他们之间的比拟,利用双等号(==),根底数据类型比拟的是他们的值
。
2.援用类型(类、接口、数组)
当他们用(==)进行比拟的时候,比拟的是他们在内存中的寄存地址,
对象是放在堆中的,栈中寄存的是对象的援用(地址)。由此可见'=='在比拟的对象是援用类型时,是对栈中的地址值进行比拟的
。
关系操作符 ==
java中蕴含的关系操作符有小于(<)
、大于(>)
、小于或等于(<=)
、大于或等于(>=)
、等于(==)
以及不等于(!=)
。
==
和!=
实用所有对象,然而这两个操作符通常在比拟对象的时候会出问题:
在这里==和!=比拟的是对象的援用
。只管对象的内容雷同,然而对象的援用却是不同的,说以n1==n2是false。
Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1 == n2); //false System.out.println(n1 != n2); //true
在这里 == 比拟的是根本数据类型
, 那么他会比拟数值是否相等.所以此时 n1 == n2输入 true.
int n1 = 100; int n2 = 100; System.out.println(n1 == n2); //true System.out.println(n1 != n2); //false
equals办法
默认状况
,对象的equals办法 调用的是Object类中equals办法.源码如下:
public boolean equals(Object obj) { return (this == obj); }
留神这里相当于还是用的==,在这里比拟的是援用对象,所以是比拟地址(是不是同一个对象)
第二种状况
,重写了对象的equals办法. 例如String对象.源码如下:
public boolean equals(Object anObject) { if (this == anObject) { return true;//如果是同一个对象间接返回 } if (anObject instanceof String) {//是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) {//一一字符比拟,若有不相等字符,返回false if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
此时,equals办法的重写实现不尽相同,然而重写后个别都是通过对象的内容是否相等 来判断 对象是否相等,对于大多数Java类库都实现了equals()办法,以便用来比拟对象的内容,而非比拟对象的援用
防止equals
办法,报空指针
防止equals办法报空指针,先通知大家,答案是应用Objects.equals(a,b)
,在JDK7增加了一个Objects工具类,它提供了一些办法来操作对象,它由一些动态的实用办法组成,这些办法是null-save(空指针平安的)或null-tolerant(容忍空指针的),用于计算对象的hashcode、返回对象的字符串示意模式、比拟两个对象。
在默认状况下,对象的equals办法没有重写 调用的是Object类中equals办法
那么咱们来写个报错的例子:
A a = null;//假如我接管到config对象,我并不知道是否为空,就进行比拟 boolean r = a.equals(new B()); System.out.println(r); //输入 java.lang.NullPointerException
此时因为咱们的忽略,接管到参数后,并没有对参数进行校验,导致调用equals办法报出空指针.
//其它的例子有:null.equals("java宝典"); //NullPointerException"java宝典".equals(null); //false 只有equals右边的对象不为Null时,才有后果null.equals(null); //NullPointerException
应用Objects.equals(a,b)
,左右 两边都为Null也不会报空指针
Objects.equals(null,"java宝典"); //false Objects.equals("java宝典",null); //false Objects.equals(null,null); //true
看一下Objects.equals办法的源码,它是容忍空指针的
public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
hashCode() 办法
哈希(Hash)实际上是 人名,因为他提出一哈希算法的概念,所以就以他的名字命名了。 哈希算法也称为散列算法,是指将数据依特定算法间接指定到一个地址上,艰深了解就是一种从任何一种数据中创立小的数字“指纹”的办法。
在java中,默认状况下,对象没有重写hashCode()办法.应用的是Object类中的.
public native int hashCode(); //它是一个native办法.
Object 类定义的 hashCode 办法会针对不同的对象返回不同的整数。(这是通过将该对象的外部地址转换成一个整数来实现的)
例子:
Config config1 = new Config(); Config config2 = new Config(); System.out.println(config1.hashCode()); //1128032093 System.out.println(config2.hashCode()); //1066516207 System.out.println(config1.equals(config2)); //false
hashCode 和 equals 的关系
二者均是Object类里的办法,因为Object类是所有类的基类,所以所有类里都能够重写这两个办法。
- 准则 1 : 如果 x.equals(y) 返回 “true”,那么 x 和 y 的 hashCode() 必须相等 ;
- 准则 2 : 如果 x.equals(y) 返回 “false”,那么 x 和 y 的 hashCode() 有可能相等,也有可能不等 ;
- 准则 3 : 如果 x 和 y 的 hashCode() 不相等,那么 x.equals(y) 肯定返回 “false” ;
- 准则 4 : 一般来讲,equals 这个办法是给用户调用的,而 hashcode 办法个别用户不会去调用 ;
- 准则 5 : 当一个对象类型作为汇合对象的元素时,那么这个对象应该领有本人的equals()和hashCode()设计,而且要恪守后面所说的几个准则。
在 Java 应用程序执行期间,在对同一对象屡次调用 hashCode 办法时,必须统一地返回雷同的整数,前提是将对象进行 equals 比拟时所用的信息没有被批改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果依据 equals(Object) 办法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 办法都必须生成雷同的整数后果。
如果依据 equals(java.lang.Object) 办法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 办法 不要求 肯定生成不同的整数后果。然而,程序员应该意识到,为不相等的对象生成不同整数后果能够进步哈希表的性能。
为什么每个笼罩了equals办法的类中,也必须笼罩hashCode办法?
在每个笼罩了equals办法的类中,也必须笼罩hashCode办法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无奈联合所有基于散列的汇合一起失常运作
下面咱们介绍了hashCode是什么,进一步理解 hashCode 的利用,咱们必须先要理解Java中的容器,因为 HashCode 只是在须要用到哈希算法的数据结构中才有用,比方 HashSet, HashMap ..
咱们以hashMap为例:
HashMap是由数组和链表组成的存储数据的构造。
确定一个数据存储在数组中的哪个地位
就是通过hashCode办法进行计算出存储在哪个地位,
产生抵触的话就会调用equals办法进行比对,
如果不同,那么就将其退出链表尾部,如果雷同就替换原数据。
计算地位当然不是下面简略的一个hashCode办法就计算出来,两头还有一些其余的步骤,这里能够简略的认为是hashCode确定了地位,代码如下:
public V put(K key, V value) { // 如果哈希表没有初始化就进行初始化 if (table == EMPTY_TABLE) { // 初始化哈希表 inflateTable(threshold); } // 当key为null时,调用putForNullKey办法,保留null于table的第一个地位中,这是HashMap容许为null的起因 if (key == null) { return putForNullKey(value); } // 计算key的hash值 int hash = hash(key); // 依据key的hash值和数组的长度定位到entry数组的指定槽位 int i = indexFor(hash, table.length); // 获取寄存地位上的entry,如果该entry不为空,则遍历该entry所在的链表 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; // 通过key的hashCode和equals办法判断,key是否存在, 如果存在则用新的value取代旧的value,并返回旧的value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 批改次数减少1 modCount++; // 如果找不到链表 或者 遍历完链表后,发现key不存在,则创立一个新的Entry,并增加到HashMap中 addEntry(hash, key, value, i); return null; }
在下面的办法中,调用了一个办法能够看到,数组的下标是依据传入的元素hashCode办法的返回值再和特定的值异或决定的:
static int indexFor(int h, int length) { // 对hash值和length-1进行与运算来计算索引 return h & (length - 1); }
再回到咱们的问题:为什么每个笼罩了equals办法的类中,也必须笼罩hashCode办法?
如果你重写了equals, 而hashCode的实现不重写,那么类的hashcode办法就是Object默认的hashcode办法,因为默认的hashcode办法是依据对象的内存地址经哈希算法得来的一个值
,那么很可能某两个对象明明是“相等”,而hashCode却不一样。
这样,当你用其中的一个作为键保留到hashMap、hasoTable或hashSet中,再以“相等的”找另一个作为键值去查找他们的时候 ,则基本找不到。导致HashSet、HashMap不能失常的运作.
比方:有个A类重写了equals办法,然而没有重写hashCode办法,对象a1和对象a2应用equals办法相等,依照下面的hashcode的用法,那么他们两个的hashcode必定相等,然而这里因为没重写hashcode办法,他们两个hashcode并不一样,所以,咱们在重写了equals办法后,尽量也重写了hashcode办法,通过肯定的算法,使他们在equals相等时,也会有雷同的hashcode值。
总结
- == 在比拟
根本数据类型
时,比拟的是值
- == 在比拟
援用数据类型
时,比拟的是对象的援用地址
- 对象的equals办法,在不重写的状况下,应用的是==,比拟的是
对象的援用地址
- 对象的equals办法,在重写当前,用于比拟对象的内容是否相等,实现能够应用IDE生成或者自定义实现.(例如,String类对equals办法的重写就是一一比拟字符)
- 不重写的状况下,对象的equals办法 调用的是Object类中equals办法,在条件右边为Null时会报空指针,应用
Objects.equals(a,b)
能够防止空指针 - hashcode是零碎用来疾速检索对象而应用的
- 重写了equals办法后,也要重写了hashcode办法,否则会导致HashSet、HashMap等依赖hashCode的容器不能失常的运作
关注公众号:java宝典