【Java】對象比較

前言

本篇博客主要梳理一下Java中對象比較的須要注意的地方,將分爲如下幾個方面進行介紹:<br>html

  • ==和equals()方法
  • hashCode()方法和equals()方法
  • Comparator接口和Comparable接口

==和equals()方法

在前面對String介紹時,談到過使用==equals()去比較對象是否相等。<br> 使用==比較的是兩個對象在內存中的地址是否一致,也就是比較兩個對象是否爲同一個對象。<br>使用equals()方法能夠依據對象的值來斷定是否相等。<br>編程

equals()方法是根類Object的默認方法,查看Object中equals()的默認實現:數組

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

能夠看出<u>沒有重寫過的equals()方法和==是同樣的,都是比較兩個對象引用指向的內存地址是否同樣判斷兩個對象是否相等</u>。<br> 在介紹String時,咱們發現並無重寫過equals()方法,可是可使用equals()正確判斷兩個字符串對象是否相等。查看String源碼能夠發現是String自己重寫了equals()方法。<br>eclipse

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;
}

Java中不少類都自身重寫了equals()方法,可是要使咱們自定義的對象能正確比較,咱們就須要重寫equals()方法。<br>ide

public class Student{  
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override  //此關鍵字能夠幫助咱們檢查是否重寫合乎要求
	public boolean equals(Object obj) {
		if (this == obj)    //檢測this與obj是否指向同一對象。這條語句是一個優化,避免直接去比較同一對象的各個域
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass()) // 比較this和obj是否屬於同一個類 如果兩個對象都不是同一個類的 則不相等
			return false;

		Student other = (Student) obj;  //將obj轉換成相應的Student類型
    //對全部須要比較的域進行比較 基本類型使用== 對象域使用equal 數組類型的域,可使用靜態的Arrays.equals方法檢測相應的數組元素是否相等
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	public static void main(String[] args) {
		Student stu1 = new Student("sakura",20);
		Student stu2 = new Student("sakura",20);
		System.out.println(stu1.equals(stu2));    //output: true
	}
}

以上重寫的equals方法是考慮最爲全面的,在重寫時最好是照着這種格式來。如果使用eclipse,則有快捷鍵幫助咱們自動生成此格式的equals方法。<br>優化

<br>this

hashCode()方法和equals()方法

能夠從上圖中看出,hashCode()equals()是配套自動生成的,爲何要附加生成hashCode()呢。<br> hashCode()是根類Object中的默認方法,查看JDK:spa

<b>hashCode()方法與equals()方法沒有任何關係</b>,hashCode()的存在是爲了服務於創建在散列表基礎上的類,如Java集合中的HashMap, HashSet等。hashCode()方法獲取對象的哈希碼(散列碼)。<b>哈希碼是一個int型的整數,用於肯定對象在哈希表(散列表)中的索引位置</b>。<br>.net

<u>hashCode()方法會根據不一樣的對象生成不一樣的哈希值,默認狀況下爲了確保這個哈希值的惟一性,是經過將該對象的內部地址轉換成一個整數來實現。</u><br> 下面咱們看一個例子:3d

public static void main(String[] args) {
	Student stu1 = new Student("sakura",20);
	Student stu2 = new Student("sakura",20);
	HashSet<Student> stuSet = new HashSet<>();
	stuSet.add(stu1);
	stuSet.add(stu2);
  System.out.println(stu1.equals(stu2));
	System.out.println(stu1);
	System.out.println(stu2);
	System.out.println(stuSet);
}
/*
output:
true
prcatice.Student@7852e922
prcatice.Student@4e25154f
[prcatice.Student@7852e922, prcatice.Student@4e25154f]
*/

HashSet不會存儲相同的對象。按理來講,stu1和stu2是相等的,不該該被重複放進stuSet裏面。可是結果顯示,出現了重複的對象。<br> 可是stu1和stu2的hashCode()返回值不一樣,那麼它們將會被存儲在stuSet中的不一樣的位置。<br> <i>對象存儲在HashSet中時,先會根據對象的哈希值來查看是否哈希表中相應的索引位置是否有對象,如果沒有則直接將對象插入;如果該位置有對象,則使用equals判斷該位置上的對象與待插入的對象是否爲相同對象,兩個對象相等則不插入,不相等就將待插入對象掛在已存在對象的後面(就像鏈表同樣掛載)。</i><br> 總結來講就是:<b>依據哈希值找位置,如果該位置沒有對象則直接插入;如果有則比較,相等則不插入,不相等則懸掛在後面。</b>

因此,要使stu1和stu2不能都被插入stuSet中,則要在Student中重寫hashCode()方法。

@Override
public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result + age;
	result = prime * result + ((name == null) ? 0 : name.hashCode());
	return result;
}

在hashCode中爲何加入31這個奇素數來計算哈希值,總的目的是爲了減小哈希衝突(在同一位置插入多個數)。詳細理由能夠參考此篇博文:爲何在定義hashcode時要使用31這個數呢?

而後咱們在運行一次程序的輸出以下:

/*
true
prcatice.Student@c9c6a694
prcatice.Student@c9c6a694
[prcatice.Student@c9c6a694]
*/

<br>

Comparator接口和Comparable接口

咱們使用equals()方法能夠實現比較咱們自定義類的對象是否相等,可是卻沒法獲得對象誰大誰小。Java中提供了兩種方式來使得對象能夠比較,實現Comparator接口或者Comparable接口。

<font color="brown"><b>Comparable接口</b></font><br> 以able結尾的接口都表示擁有某種能力。如果某個自定義類實現了comparable接口,則表示<b>該類的實例化對象擁有能夠比較的能力</b>。 實現comparable接口須要覆蓋其中的compareTo()方法(是一個泛型方法)。

int compareTo(T o)

<u>返回負數:當前對象小於指定比較的對象;返回0,兩個對象相等;返回正數,當前對象大於指定比較的對象。</u><br>

public class Student implements Comparable<Student>{
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

  //重寫comparaTo方法 以age做爲標準比較大小
	@Override
	public int compareTo(Student o) {
		return return (this.age<o.age ? -1 : (this.age == o.age ? 0 : 1));;//本類接收本類對象,對象能夠直接訪問屬性(取消了封裝的形式)
	}

	@Override
	public String toString() {
		return "name:" +name + " age:"+age;
	}

	public static void main(String[] args) {
		Student stu1 = new Student("sakura",20);
		Student stu2 = new Student("sakura",21);
		Student stu3 = new Student("sakura",19);
    //TreeSet會對插入的對象進行自動排序,因此要求知道對象之間的大小
		TreeSet<Student> stuSet = new TreeSet<>();  
		stuSet.add(stu1);
		stuSet.add(stu2);
		stuSet.add(stu3);
    //使用foreach(), lambda表達式輸出stuSet中的值 forEach()方法從JDK1.8纔開始有
		stuSet.forEach(stu->System.out.println(stu));
	}
}
/*
output:
name:sakura age:19
name:sakura age:20
name:sakura age:21
*/

實現了comparaTo()方法使用age爲標準升序排序。也能夠以name爲標準排序,或者其餘自定義的比較依據。<br> 可是當Student已經實現了以age爲依據從小到大排序後,咱們又想以name爲依據排序,在這個簡單的程序中能夠直接將return this.age-o.age變爲return this.name.compareTo(o.name)(name爲String對象),可是這樣修改類結構會顯得十分麻煩,萬一在之後的程序中遇到的是別人封裝好的類不能直接改類結構又該怎麼辦。<br> 有沒有其餘方便的比較方法,實現對象的大小比較。 辦法是有的,那就是實現Comparator接口。

<font color="brown"><b>Comparator接口</b></font><br> 實現Comparator接口須要重寫其中的compare()方法(一個泛型方法)。

int compare(T o1,T o2)

根據第一個參數小於、等於或大於第二個參數分別返回負整數、零或正整數,一般使用-1, 0, +1表示。<br>

須要注意,Comparator接口中也有一個equals方法,可是這是判斷<b>該比較器與其餘Comparator比較器是否相等。</b><br>

public class Student {  
	private String name;
	private int age;

	public Student(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "name:"+name + " age:"+age;
	}
	
	public static void main(String[] args) {
	    Student stu1 = new Student("sakuraamy",20);
		Student stu2 = new Student("sakurabob",21);
		Student stu3 = new Student("sakura",19);

		ArrayList<Student> stuList = new ArrayList<>();
		stuList.add(stu1);
		stuList.add(stu2);
		stuList.add(stu3);

    //沒有必要去建立一個比較器類 採用內部類的方式實現Comparator接口
		Collections.sort(stuList, new Comparator<Student>() {
			@Override
			public int compare(Student o1, Student o2) {
				return (o1.age<o2.age ? -1 : (o1.age == o2.age ? 0 : 1));
				//return o1.name.compareTo(o2.name);
			}
		});
    //或者使用lambda表達式
    //Collections.sort(stuList, (o1,o2)->o1.age-o2.age);
		System.out.println(stuList);
	}
}
/*
[name:sakura age:19, name:sakuraamy age:20, name:sakurabob age:21]
*/

由上可見,實現Comparator接口比較對象比實現Comparable接口簡單和靈活。<br> 使用這兩個接口比較對象都須要注意幾點:

  • 對稱性:若存在compare(x, y)>0 則 compare(y, x) <0,反之亦然
  • 傳遞性:((compare(x, y)>0) && (compare(y, z)>0)) 能夠推導出compare(x, z)>0
  • 相等替代性:compare(x, y)==0能夠推導出compare(x, z)==compare(y, z)

小結

簡單總結一下本篇關於Java中對象比較的內容:要比較自定義類的對象是否相等須要重寫equals()方法;<br>當對象要存儲在創建在哈希表基礎上的集合中時,還須要重寫hashCode()方法用於斷定對象在集合中的存儲位置;<br>以某種依據比較對象的大小,能夠實現Comparable接口或者Comparator接口,前者須要在類中實現表示該類擁有能夠比較的能力,後者是在類外實現一個比較器,可使用多種規則對對象進行比較,更靈活。<br>
在Comparable中沒有使用「簡潔明瞭」的this.age-o.age做爲返回值,是由於這是一個常見的編程錯誤,它至於在this.age和o.age都是無符號的int時才能正確工做。對於Java的有符號int,它就會出錯。this.age是很大的正整數而o.age是很大的負整數,兩者相減就會溢出從而產生負值,致使錯誤結果

參考博文:https://juejin.im/entry/586c6a6061ff4b006407e2b9

原文出處:https://www.cnblogs.com/myworld7/p/10050947.html

相關文章
相關標籤/搜索