###前言 相信不少读者关于==
和equals
懂了又懵,懵了又懂,如此循环,事实上多是由于看到的博客文章之类的太多了,长篇大论,加上一段时间的洗礼以后就迷路了。本篇文章再一次理清楚。固然若是以为本文太啰嗦的话,固然我也考虑到了,由于我也不喜欢长篇大论啰里啰嗦比比叨叨胡搅蛮缠的文章,毕竟你们入门java 的时候就知道个大概了,所以记住一句话就行了:**equals自己和 == 没有区别,对于基本数据都是比较值,对于引用类型,则比较的是所指向的对象的地址!其余类在继承Object类以后对equals方法重写,因此表现的是比较里面的内容!**具体比较的则要看本身是怎么重写的。java
好了,若是有兴趣的就看下文,固然不感兴趣的大佬能够点个赞直接走了,不用看了,会了还看个*啊,楼主你个憨憨(皮一下很开心)程序员
从java语言本质上来说,"=="属于JAVA语言的运算符,而equals则是根类Object的一个方法。算法
关于Object类的equals()方法,咱们能够看看其源码bash
/* * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */public boolean equals(Object obj) { return (this == obj);} 复制代码
是的,equals底层其实就是“ == ”,也就是说,原生的equals()方法自己与 “ == ”没有任何区别!惟一的区别则是基本类型没有继承Object类,因此基本类型没有equals()方法,也就是说基本类型只能使用“ == ”判断值是否相等。markdown
既然原生的equals()方法自己与 “ == ”没有任何区别,那么咱们对运算符 “ == ”的使用有所了解便可!app
运算符 “ == ”其具体做用是用来比较值是否相等,这里分两中状况:框架
一、基本数据类型的变量,则直接比较其存储的 “值”是否相等;ide
二、引用类型的变量,则比较的是所指向的对象的地址是否相等;性能
到这里咱们能够初步确认原生的equals()方法自己与 “ == ”没有任何区别!做用正是如上。ui
可是重点来了,由于对于equals()
方法我一直在强调原生二字。是的,让不少初学者疑惑的点就在这里:equals()方法的重写!
在JDK中,诸如String
、Date
等类对equals
方法进行了重写,以String为例,这里感兴趣的读者能够一块儿看看String类中重写的equals()方法,固然跳过也问题不大
/** * 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. * * @param anObject * The object to compare this {@code String} against * * @return {@code true} if the given object represents a {@code String} * equivalent to this string, {@code false} otherwise * * @see #compareTo(String) * @see #equalsIgnoreCase(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; } 复制代码
从源码中能够看出,首先该方法判断比较的是所指向的对象地址是否相等,若是相同直接返回true
,若是不相同进行下一个if
判断,第二个if
判断大体意思则是比较其存储的 “值”是否相等,也就是比较内容值!相同就返回true,好比两个new String对象“AAA”
和“AAA”
,这里虽然对象地址不相等,可是内容相等,因此一样返回true。
这里我就想给各位出个典型的String例子了:
public static void main(String[] args) { String a = "宜春"; String b = new String("宜春"); String c = b; //注意这里是引用传递,意思是c也指向b指向的内存地址 System.out.println(a == b); //false System.out.println(a == c); //false System.out.println(b == c); //true System.out.println(a.equals(b)); //true System.out.println(a.equals(c)); //true System.out.println(b.equals(c)); //true } 复制代码
【特别注意:String类型属于引用类型】
解析:(1)a == b?意思是地址指向的是同一块地方吗?很明显不同。
(2)a == c?意思是地址指向的是同一块地方吗?很明显不同。
(3)b == c?意思是地址指向的是同一块地方吗?很明显内容同样,因此为true。
(4)a.equals( b )?意思是地址指向的内容同样嘛?同样。
(4)a.equals( c )?意思是地址指向的内容同样嘛?同样。
(4)b.equals( c )?意思是地址指向的内容同样嘛?同样。
固然,你可能仍是有点疑惑,那么结合下面这张图再理解上面的解析,你可能就恍然大悟了
OK。如今能理解嘛?你你你......不用回答,我知道你理解了(义正词严)。固然值得注意一点的是String中intern()
方法,先看一段程序:
public static void main(String[] args) { String a = "宜春"; String b = new String("宜春"); b=b.intern(); System.out.println(a == b); //true System.out.println(a.equals(b)); //true } 复制代码
intern
方法的意思是检查字符串池里是否存在,若是存在了那就直接返回为true。所以在这里首先a
会在字符串池里面有一个,而后 b.intern()
一看池子里有了,就再也不新建new了,直接把b
指向它。
不知道你们有没有想过这个问题。固然答案也是很简单的,由于程序员比较字符串通常比较其内容就行了,比较内存地址是否是同一个对象就好像没啥意义了,重写equals方法就很方便用来比较字符串的内容了。
其实除了诸如String、Date等类对equals方法进行重写,咱们在实际开发中,咱们也经常会根据本身的业务需求重写equals方法.
举个栗子:咱们的需求就是若是两个学生对象姓名、身份证号、性别相等,咱们认为两个学生对象相等,不必定须要学生对象地址相同。
学生A的我的信息(姓名:如花,性别:女,身份证号:123,住址:广州),学生A对象地址为0x11, 学生B的我的信息(姓名:如花,性别:女,身份证号:123,住址:深圳),学生A对象地址为0x12,
这时候若是不重写Object的equals方法,那么返回的必定是false不相等,这个时候就须要咱们根据本身的需求重写equals()方法了。具体equals方法的重写代码以下:
// 重写equals方法 @Override public boolean equals(Object obj) { if(!(obj instanceof Student)) { // instanceof 已经处理了obj = null的状况 return false; } Student stuObj = (Student) obj; // 对象地址相等 if (this == stuObj) { return true; } // 若是两个对象姓名、身份证号码、性别相等,咱们认为两个对象相等 if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.IDnumber.equals(this.IDnumber)) { return true; } else { return false; } } 复制代码
开发中这样设计,才能符合咱们的生活!到这里我就不信了你还搞不定==和equals!
但是一涉及重写equals方法的同时又衍生了下面一个问题。
固然这个问题要讨论,又要长篇大论哔哔一大堆了,有空写一篇这样的文章吧专门讨论讨论这个问题,固然园子里的大佬们也写了一大堆!能够自行去了解了解。本篇文章简单聊聊,点到便可。
首先hashCode()方法是Object类的一个方法,源码以下:
/** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> * The general contract of {@code hashCode} is: * <ul> * <li>Whenever it is invoked on the same object more than once during * an execution of a Java application, the {@code hashCode} method * must consistently return the same integer, provided no information * used in {@code equals} comparisons on the object is modified. * This integer need not remain consistent from one execution of an * application to another execution of the same application. * <li>If two objects are equal according to the {@code equals(Object)} * method, then calling the {@code hashCode} method on each of * the two objects must produce the same integer result. * <li>It is <em>not</em> required that if two objects are unequal * according to the {@link java.lang.Object#equals(java.lang.Object)} * method, then calling the {@code hashCode} method on each of the * two objects must produce distinct integer results. However, the * programmer should be aware that producing distinct integer results * for unequal objects may improve the performance of hash tables. * </ul> * <p> * As much as is reasonably practical, the hashCode method defined by * class {@code Object} does return distinct integers for distinct * objects. (This is typically implemented by converting the internal * address of the object into an integer, but this implementation * technique is not required by the * Java<font size="-2"><sup>TM</sup></font> programming language.) * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */ public native int hashCode(); 复制代码
能够看出hashCode()
方法返回的就是一个int
数值,从方法的名称上就能够看出,其目的是生成一个hash
码。hash码的主要用途就是在对对象进行散列的时候做为key
输入,据此很容易推断出,咱们须要每一个对象的hash码尽量不一样,这样才能保证散列的存取性能。
事实上,Object类提供的默认实现确实保证每一个对象的hash码不一样(在对象的内存地址基础上通过特定算法返回一个hash码)。Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。初学者能够这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际上不是)。
想要知道hashCode的做用,必需要先知道Java中的集合。
Java中的集合List的元素是有序的,元素能够重复;Set元素无序,但元素不可重复。这咱们都清楚。可是你有没有想过这样一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
每错这里就是用Object.equals
方法了。可是,若是每增长一个元素就检查一次,那么当元素不少时,后添加到集合中的元素比较的次数就很是多了。也就是说,若是集合中如今已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals
方法。这显然会大大下降效率。
那怎么解决呢?咱们能够在Java集合框架中获得验证。因为HashSet
是基于HashMap
来实现的,因此这里只看HashMap
的put
方法便可。源码以下:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); //这里经过哈希值定位到对象的大概存储位置 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; //if语句中,先比较hashcode,再调用equals()比较 //因为“&&”具备短路的功能,只要hashcode不一样,也无需再调用equals方法 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null;} 复制代码
正如源码中注释所述,“&&”具备短路的功能,只要hashcode不一样,也无需再调用equals方法。是的,Java采用了哈希表的原理。 哈希表具备优越的查询性能,就像九九乘法表2*3=6
你能很轻易知道,可是哈希表不免还会出现哈希冲突,只是几率极低。
如此设计,这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一会儿能定位到它应该放置的物理位置上。若是这个位置上没有元素,它就能够直接存储在这个位置上,不用再进行任何比较了;若是这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址。因此这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大下降了,几乎只须要一两次。
**所以并非重写了equals方法就必定要重写hashCode方法,只有用到HashMap,HashSet等Java集合的时候重写了equals方法就必定要重写hashCode方法。**用不到哈希表仅仅重写equals()方法也OK的。
Java官方建议 重写equals()就必定要重写hashCode()方法。毕竟实际开发场景中经常用到Java集合
Java对于eqauls方法和hashCode方法是这样规定的:
一、若是两个对象equals为true ,他们的hashcode必定相等。二、若是两个对象equals为false,他们的hashcode有可能相等。三、若是两个对象hashcode相等,equals不必定为true。四、若是两个对象hashcode不相等,equals必定为false。
最后,如有不足或者不正之处,欢迎指正批评,感激涕零!