面試的時候,經常被問到,總結一下,如有不正確的地方,多多指教。
1 、關於“==”
關於“!=”,看完下面的內容,大家應該可以自己腦補;“==”可以進行下面三種類型的比較;
- 數值類型==
可以在基本類型之間進行比較,比如整型類型,int,long;浮點類型float,double;
(1)基本類型之間可以相互比較,如果對應的值相等,則返回true,否則,返回false;
(2)基本類型與對應的包裝類型之間也可以進行比較,比較的時候,包裝類型要進行拆箱操作;
(3) 布爾類型與數值類型(整型、浮點型)是不能直接用“==”比較的。
示例代碼:
public static void main(String []args){
int i = 9;
float f = 9f;
long l = 9l;
double d = 9d;
Long L = new Long(9l);
double D = new Double(9d);
double D1 = new Double(9.1d);
System.out.println("int == float : " +(i==f));
System.out.println("int == long : " +(i==l));
System.out.println("int == double : " +(i==d));
System.out.println("Long == Double : " +(L==D));
System.out.println("Long != Double : " +(L==D1));
}
運行結果:
int == float : true
int == long : true
int == double : true
Long == Double : true
Long != Double : false
- 布爾類型==
(1) 兩個操作數操作數類型爲boolean,或者是其包裝類型Boolean;
(2) 如果其中一個操作數是類型Boolean,則進行拆箱轉換。
(3) 如果兩個操作數都爲true或者false,結果爲true,否則,結果爲false;
示例代碼:
public static void main(String []args){
boolean t = true;
Boolean t1 = true;
boolean f = false;
Boolean f1 = false;
System.out.println("boolean t == Boolean t1 : " +(t==t1));
System.out.println("boolean t == Boolean f : " +(t==f));
System.out.println("boolean f == Boolean f1 : " +(f==f1));
System.out.println("Boolean t1 == Boolean f1 : " +(t1==f1));
}
運行結果:
boolean t == Boolean t1 : true
boolean t == Boolean f : false
boolean f == Boolean f1 : true
Boolean t1 == Boolean f1 : false
- 引用類型
運算符的操作數是引用類型或者是null類型,引用類型可以是對象或者數組;
(1)在比較時,如果兩個操作數都是null或者都引用同一個對象或數組,則結果爲true,否則,結果爲false;
(2)雖然“==”可以用於比較類型的引用String,但是這樣的相等性測試是確定兩個操作數是否引用相同的String 對象;因此,可能會出現兩個new的String對象字符串序列相同,但是“==”結果卻是false的情況;
(3)兩個字符串的內容s和t是否相等可以通過s.equals(t)方法進行計較。
示例代碼:
public static void main(String []args){
Object o1 = new Object();
Object o2 = o1; //o2與o1引用同一對象
Object o3 = new Object();
Object o4 = null;
Object o5 = null;
System.out.println("o1 == o2 : " +(o1==o2));
System.out.println("o1 == o3 : " +(o1==o3));
System.out.println("o1 == o4 : " +(o1==o4));
System.out.println("o4 == o5 : " +(o4==o5));
}
運行結果:
o1 == o2 : true
o1 == o3 : false
o1 == o4 : false
o4 == o5 : true
2 、關於“equals”
boolean equals(Object obj) 方法來自java.lang.Object類,代碼如下:
public boolean equals(Object obj) {
return (this == obj);
}
是不是感覺有點眼熟,實際上,如果某個類沒有覆蓋equals()方法,當它的通過equals()比較兩個對象時,是在比較兩個對象是不是同一個對象。這時,等價於通過“==”去比較這兩個對象。
我們可以覆蓋類的equals()方法,來讓equals()通過其它方式比較兩個對象是否相等。通常的做法是:若兩個對象的內容相等,則equals()方法返回true;否則,返回false;
由於 Java類,String 、Math、Integer、Double等這些封裝類在使用equals()方法時,已經覆蓋了object類的equals()方法,因此,是比較對象的內容是否相等;自定義類的話就需要自己覆蓋equals()方法來判斷兩個對象的內容是否相等。
示例代碼:
package com.example.demo;
import com.sun.tools.doclets.formats.html.SourceToHTMLConverter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.ConcurrentHashMap;
public class EqualsTest {
public static void main(String []args){
Person p1 = new Person("金毛獅王",100);
Person p2 = new Person("金毛獅王",100);
System.out.println("未覆蓋equals: "+p1.equals(p2));
Person1 p3 = new Person1("金毛獅王",100);
Person1 p4 = new Person1("金毛獅王",100);
System.out.println("覆蓋equals: "+p3.equals(p4));
}
}
class Person{
String name;
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Person1{
String name;
int age;
public Person1(String name, int age) {
this.name = name;
this.age = 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;
}
//覆蓋之後,如果兩個對象的內容相同,或者是屬於同一個對象的引用,都返回true,否則,返回false
@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
//如果是同一個對象返回true
if(this == obj)
return true;
//判斷是否類型相同
if(this.getClass()!=obj.getClass())
return false;
Person1 p = (Person1)obj;
return this.name == p.name && this.age == p.age;
}
}
運行結果:
未覆蓋equals: false
覆蓋equals: true
equals方法在非空(null)對象引用上有如下的等價關係:
- 自反性: 對於任何非空引用值x , x.equals(x)應該返回true;
- 對稱性:對於任何非空引用值x和y,如果x.equals(y) 返回true,那麼 y.equals(x)也一定返回true;
- 傳遞性:對於任何非空引用值x,y,z,如果x.equals(y)返回true,y.equals(z)返回true, 那麼x.equals(z)也一定返回true;
- 一致性:對於任何非空引用值 x和y,在多次調用 x.equals(y)始終返回true 或始終返回false,除非equals的比較對象被修改(這裏多說一句,所謂的“修改”就是說你在覆蓋equals方法時,進行對象內容比較,用到的對象中的某個成員的值發生了改變)。
- 對於任何非空引用值x, x.equals(null)應返回false。
3 、關於“hashCode”
public int hashCode()也是來自java.lang.Object類,它返回對象的哈希碼(也叫散列碼,一個整型數字);
哈希碼是用來確定該對象在哈希表中的索引位置,在Java集合中,HashMap,HashTable,HashSet等都屬於哈希(散列)表;因此,如果當前對象不會在散列表中被用到(就是不會作爲HashSet的元素,或者HashMap的Key),那麼完全可以忽略hashCode;可能你現在在想我平時用HashMap的時候也沒有考慮hashCode的問題啊,不也用的好好的;因爲,一般情況下,都用String或者Java已有類型作爲Key,已有類型在默認繼承Object的時候已經覆蓋了hashCode方法,因此在使用HashSet或者HashMap的時候,不會出現重複元素;看幾個例子就明白了:
下面分別用HashSet和HashMap做測試,要做的就是保證HashSet裏面元素不同,HashMap的key也不相同;
先看HashSet,
示例代碼:
package com.example.demo;
import java.util.HashSet;
import java.util.Iterator;
public class HashTest {
public static void main(String[] args) {
HashSet<MyString> stringHashSet = new HashSet<>();
HashSet<MyString1> myStringHashSet = new HashSet<>();
stringHashSet.add(new MyString("hello"));
stringHashSet.add(new MyString("hello"));
stringHashSet.add(new MyString("hello1"));
Iterator it = stringHashSet.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("===========");
myStringHashSet.add(new MyString1("hello"));
myStringHashSet.add(new MyString1("hello"));
myStringHashSet.add(new MyString1("hello1"));
Iterator<MyString1> it1 = myStringHashSet.iterator();
while(it1.hasNext()){
System.out.println(it1.next());
}
}
}
class MyString{
String value;
public MyString(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
class MyString1{
String value;
public MyString1(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.toUpperCase().hashCode();
}
@Override
public boolean equals(Object obj) {
MyString1 s = (MyString1)obj;
return this.value ==s.value;
}
}
- 運行結果
hello
hello
hello1
===========
hello1
hello
可以看出在沒有覆蓋hashcode()方法時,HashSet中的元素可能會出現重複的情況,這就違背了Set集合的設計初衷,而覆蓋了hashcode()方法,HashSet中的元素就不重複了;
原因就是一般情況下,不同的對象會產生不同的hashcode,即使對象的內容相同;按照hashcode的約定規則:如果兩個對象equals()相等,那麼應該有相同的hashcode,因此,覆蓋後的hashcode()要符合這一規則。
hashcode()的生成有如下的約定規則:
- 在Java應用程序執行期間,多次在同一對象上調用它時,hashCode()必須始終返回相同的整數,除非當前對象的內容被修改;從應用程序的一次執行到同一應用程序的另一次執行,該整數不需要保持一致。
- 如果兩個對象equals()後相等,它們在使用hashcode()方法後,生成的hashcode(散列碼)應該相同(這就解釋瞭如果要覆蓋equals()方法的話,就要覆蓋hashcode()方法)。
- 如果兩個對象equals()後不相等,它們在使用hashCode()方法後,產生的散列碼可以相同,也可以不同;但是,要明白:爲不同的對象生成不同的hashcode(散列碼)能夠改善散列表的性能。
HashMap的例子不貼了,大家可以自己試一下,只要保證key滿足上述特性就可以了;
關於散列表的內容,數據結構與算法(要找經典教材看)中有詳細介紹,不太提倡看博客,有時候大神的思路不一定你能跟的上,書上的內容更加全面一些,更通俗易懂。
參考資料
https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.21
https://docs.oracle.com/javase/10/docs/api/java/lang/Object.html#equals(java.lang.Object)
https://www.cnblogs.com/skywang12345/p/3324958.html
https://www.jianshu.com/p/1acdfac2b4e4?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
http://www.cnblogs.com/skywang12345/p/3311899.html