1、概念
#== : 此運算符產生的是一個boolean結果,它計算的是運算元的值之間的關係
整數型:byte(1 byte), short(2 byte), int(4 byte) , long(8 byte)
字元型: char(2 byte) 布林型: boolean(JVM規範沒有明確規定其所佔的空間大小,僅規定其只能夠取字面值”true”和”false”) 對於這八種基本資料類型的變量,變數直接儲存的是「值」。因此,在使用關係運算子 == 來進行比較時,比較的就是「值」本身。 要注意的是,浮點型和整數型都是有符號類型的(最高位元只用來表示正負,不參與計算【以byte 為例,其範圍為-2^7 ~ 2^7 - 1,-0即-128】),而char是無符號類型的(所有位元均參與計算,所以char類型取值範圍為0~2^16-1)
引用類型的變數儲存的並不是「值」本身,而是與其關聯的物件在記憶體中的位址。例如下面這行程式碼, String str1;
str1= new String("hello");
引用變數str1中儲存的是它指向的物件在記憶體中的儲存位址,並不是「值」本身,也就是說並不是直接儲存的字串」hello」
。這裡面的引用和 C/C++ 中的指標很類似。 #########2、小結###### 因此,對於關係運算子==:############若運算元的型別是##### #基本資料型別######,則該關係運算子判斷的是左右兩邊運算元的#######值######是否相等########### #若運算元的型別是######引用資料型別######,則該關係運算子判斷的是左右兩邊運算元的######記憶體位址#######是否相同。 ######也就是說,若此時傳回true,則該運算元作用的一定是同一個物件。 #####################三、equals方法######1、來源### equals方法是基底類別Object中的實例方法,因此對所有###繼承###於Object的類別都會有該方法。 ### ### 在Object 中的宣告:###public boolean equals(Object obj) {}
public boolean equals(Object obj) { return (this == obj); }
但我们都知道,下面代码输出为 true:
public class Main { public static void main(String[] args) { String str1 = new String("hello"); String str2 = new String("hello"); System.out.println(str1.equals(str2)); } }
原来是 String 类重写了 equals 方法:
public boolean equals(Object anObject) { // 方法签名与 Object类 中的一致 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; }
即对于诸如“字符串比较时用的什么方法,内部实现如何?”之类问题的回答即为:
使用equals方法,内部实现分为三个步骤:
先 比较引用是否相同(是否为同一对象),
再 判断类型是否一致(是否为同一类型),
最后 比较内容是否一致
Java 中所有内置的类的 equals 方法的实现步骤均是如此,特别是诸如 Integer,Double 等包装器类。
3、equals 重写原则
对象内容的比较才是设计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(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
任何情况下,x.equals(null)【应使用关系比较符 ==】,永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”
4、小结
因此,对于 equals 方法:
其本意 是 比较两个对象的 content 是否相同
必要的时候,我们需要重写该方法,避免违背本意,且要遵循上述原则
1、hashCode 的来源
hashCode 方法是基类Object中的 实例native方法,因此对所有继承于Object的类都会有该方法。
在 Object类 中的声明(native方法暗示这些方法是有实现体的,但并不提供实现体,因为其实现体是由非java语言在外面实现的):
public native int hashCode();
2、哈希相关概念
我们首先来了解一下哈希表:
概念 : Hash 就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出(int),该输出就是散列值。这种转换是一种 压缩映射,也就是说,散列值的空间通常远小于输入的空间。不同的输入可能会散列成相同的输出,从而不可能从散列值来唯一的确定输入值。简单的说,就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
应用–数据结构 : 数组的特点是:寻址容易,插入和删除困难; 而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入和删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法——拉链法,我们可以理解为 “链表的数组”,如图:
图1 哈希表示例
左邊很明顯是個數組,數組的每個成員都是一個鍊錶。此資料結構所容納的所有元素均包含一個指針,用於元素間的連結。我們根據元素的自身特徵把元素分配到不同的鍊錶中去,也是根據這些特徵,找到正確的鍊錶,再從鍊錶中找出這個元素。 其中,將根據元素特徵計算元素數組下標的方法就是雜湊法。
拉鍊法的適用範圍 : 快速查找,刪除的基本資料結構,通常需要總資料量可以放入記憶體。
重點:
hash函數選擇,針對字串,整數,排列,具體對應的hash方法;
碰撞處理,一種是open hashing,也稱為拉鍊法,另一種是closed hashing,也稱為開地址法,opened addressing。
3、hashCode 簡述
在 Java 中,由 Object 類別定義的 hashCode 方法會針對不同的物件傳回不同的整數。 (這是透過將該物件的內部位址轉換成一個整數來實現的,但是 JavaTM 程式語言不需要這種實作技巧)。
hashCode 的常規協定是:
在Java 應用程式執行期間,在對相同物件多次調用hashCode 方法時,必須一致地傳回相同的整數,前提是將物件進行equals 比較時所使用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。
如果根據 equals(Object) 方法,兩個物件是相等的,那麼對這兩個物件中的每個物件呼叫 hashCode 方法都必須產生相同的整數結果。
如果根據equals(java.lang.Object) 方法,兩個物件不相等,那麼對這兩個物件中的任一物件上呼叫hashCode方法 不要求一定會產生不同的整數結果。但是,程式設計師應該意識到,為不相等的物件產生不同整數結果可以提高雜湊表的效能。
要想進一步了解hashCode 的作用,我們必須先要了解Java中的容器,因為HashCode 只是在需要用到哈希演算法的資料結構中才有用,例如HashSet, HashMap 和Hashtable。
Java中的集合(Collection)有三類,一類是List,一類是Queue,再有一類就是Set。 前兩個集合內的元素是有序的,元素可以重複;最後一個集合內的元素無序,但元素不可重複。
那麼, 這裡就有一個比較嚴重的問題:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢? 這就是 Object.equals 方法了。但是,如果每增加一個元素就檢查一次,那麼當元素很多時,後面加入集合中的元素比較的次數就非常多了。 也就是說,如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要呼叫1000次equals方法。這顯然會大大降低效率。於是,Java採用了哈希表的原理。 這樣,我們對每個要存入集合的元素使用雜湊演算法算出一個值,然後根據該值計算出元素應該在數組的位置。所以,當集合要加入新的元素時,可分為兩個步驟:
先呼叫這個元素的hashCode 方法,然後根據所所得的值計算出元素應該在數組的位置。如果這個位置上沒有元素,那麼直接將它儲存在這個位置上;
#如果這個位置上已經有元素了,那麼呼叫它的equals方法與新元素進行比較:相同的話就不存了,否則,將其存在這個位置對應的鍊錶中(Java 中HashSet, HashMap 和Hashtable的實作總會元素放到鍊錶的錶頭) 。
4、equals 與hashCode
前提: 談到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()设计,而且要遵守前面所说的几个原则。
5、实现例证
hashCode()在object类中定义如下:
public native int hashCode();
说明是一个本地方法,它的实现是根据本地机器相关的。
String 类是这样重写它的:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** The value is used for character storage. */ private final char value[]; //成员变量1 /** The offset is the first index of the storage that is used. */ private final int offset; //成员变量2 /** The count is the number of characters in the String. */ private final int count; //成员变量3 /** Cache the hash code for the string */ private int hash; // Default to 0 //非成员变量 public int hashCode() { int h = hash; int len = count; //用到成员变量3 if (h == 0 && len > 0) { int off = offset; //用到成员变量2 char val[] = value; //用到成员变量1 for (int i = 0; i < len; i++) { h = 31*h + val[off++]; //递推公式 } hash = h; } return h; } }
对程序的解释:h = s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
,由此可以看出,对象的hash地址不一定是实际的内存地址。
hashcode是系统用来快速检索对象而使用
equals方法本意是用来判断引用的对象是否一致
重写equals方法和hashcode方法时,equals方法中用到的成员变量也必定会在hashcode方法中用到,只不过前者作为比较项,后者作为生成摘要的信息项,本质上所用到的数据是一样的,从而保证二者的一致性
以上是詳解Java中的 ==, equals 與 hashCode的區別與聯繫的範例程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!