個人主頁:兜里有顆棉花糖
歡迎 點贊👍 收藏? 留言? 加關注💓本文由 兜里有顆棉花糖 原創
收錄于專欄【JavaSE_primary】
本專欄旨在分享學習JavaSE的一點學習心得,歡迎大家在評論區交流討論💌
上篇(【Java基礎篇 | 面向對象】—— 聊聊什么是接口(上篇))中我們已經對Java接口中有了一定的了解。本篇中我們將對Java接口進行更進一步的學習。加油吧!!!
目錄
- 一、接口使用實例
- 比較器(Comparator)
- 二、Clonable接口和深拷貝
- 淺拷貝
- 深拷貝
- 三、Object類
- 對象比較equals()方法
- hashcode()方法
一、接口使用實例
首先我們要使用記住一句話,對象與對象之間進行比較的話一定要實現對應的接口。只有我們實現了對應的接口之后才能證明這兩個對象是可比較的
。
現在有一個整數數組,我們當然可以使用sort()
方法來對這個整數數組進行升序或者降序排序。但是如果我們現在有一個學生類對象呢
?我們是無法直接拿兩個學生類對象進行直接排序的。此時我們應該參照學生類中的某個屬性來對這個學生類對象進行排序以達到我們想要的排序效果。
現在我們就以學生類中的年齡屬性來進行排序吧:
我們在進行自定義類型的對象比較的時候,一定要實現可以比較的接口。比如如果我們的Student類
實現Comparable接口
, 并實現其中的compareTo方法
。否則的話自定義類型的對象是無法進行比較的。
如下圖就是我們實現的
Comparable接口
中的compareTo
方法。
如果我們要比較兩個對象的引用的話(兩個學生類對象按照年齡來進行排序),我們可以這樣來寫,請看:
如果我們要比較的是一個學生類對象數組的話(按照年齡來進行比較),我們可以這樣,請看:
運行結果如下:
現在我們來試著使用自己寫一個排序方法(冒泡排序)來對學生類對象進行排序。
請看下面我們自己實現的冒泡排序來對學生類對象按照年齡進行排序。代碼如下:
運行結果如下:
現在我們來對上述冒泡排序中的代碼進行解釋:
排序的時候我們排序的是一個學生數組(按照年齡來進行排序),所以我們在進行排序的時候底層一定會去調用compareTo方法
,所以冒泡排序中的參數一定為Comparable[] comparables
,即接口數組
。另外array數組(即學生類對象數組)中的每個元素都是一個學生類對象,而且每個學生類對象都實現了compareTo方法
。
比較器(Comparator)
好了,現在我們換一種排序的寫法。上述學生類中有年齡也有分數,如果我們一會想依據年齡進行排序,一會又想用分數進行排序的話,如果按照
compareTo()
方法完成上述排序的話,那么根據我們比較依據的不同那么compareTo()方法
中的內容也是不一樣的(即我們需要修改compareTo
方法中的內容)。
請看上圖,上圖中的compareTo()
只能對學生類對象中的年齡進行排序而無法對學生類中的成績進行排序,所以排序的內容就比較單一。此時我們就需要Comparator接口
。
好了,現在我們利用
Comparator接口
來實現學生類對象的排序工作,代碼如下圖,請看:
具體代碼如下,請看:
import com.sun.javaws.IconUtil;
import java.util.Comparator;class Student{public String name;public int age;public double score;public Student(String name, int age, double score) {this.name = name;this.age = age;this.score = score;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", score=" + score +'}';}
}
// 這里我們利用了解耦的思想
class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.age - o2.age;}
}
class ScoreComparator implements Comparator<Student>{@Overridepublic int compare(Student o1,Student o2) {return (int)(o1.score - o2.score);}
}
public class Test {public static void main(String[] args) {Student student1 = new Student("jkl",1,87.3);Student student2 = new Student("ajk",2,87.3);// 依據年齡進行比較AgeComparator ageComparator = new AgeComparator();int ret = ageComparator.compare(student1,student2);System.out.println("ret = " + ret);// 依據成績進行比較ScoreComparator scoreComparator = new ScoreComparator();int ret2 = scoreComparator.compare(student1,student2);System.out.println("ret = " + ret2);}
}
運行結果如下:
對比一下這兩種接口(Comparator接口
和Comparable接口
):經過上述的演示,我們不難發現Comparator接口
更加的靈活。
二、Clonable接口和深拷貝
淺拷貝
淺拷貝概念:淺拷貝是指在對一個對象進行拷貝時,只拷貝對象本身和其中的基本數據類型,而不拷貝對象內部的引用類型。因此,在淺拷貝的對象中,引用類型的變量指向的依舊是原始對象中的引用。
下面來進行舉例。
現在我們有一個
Student學生類
,如下圖:
同時新創建了一個學生類對象student1
,該對象對應的內存結構圖如下:
現在我們想要把student引用
所指向的對象克隆出來一份,如下圖的克隆方式是錯誤的:
要解決上述錯誤的話,我們需要修改三個地方。如下圖:
好了,現在重新運行一下程序,發現還是會報錯,請看:
我們需要實現一個接口以證明當前的類是可以進行克隆的。
運行結果如下:
淺拷貝的對象中,引用類型的變量指向的依舊是原始對象中的引用。請看舉例:
運行結果如下:
解釋:通過調用clone()方法,創建了student1的一個克隆對象student2。克隆的實現是通過調用Object類的clone()方法來完成的。
輸出結果顯示了兩次student1.m.money和student2.m.money的值,分別為52.0。這是因為淺拷貝只是簡單地復制字段的值,而對于引用類型的字段,只復制了引用地址,并沒有復制該引用指向的實際對象。
因此,student1和student2的m字段引用同一個Money對象。
深拷貝
深拷貝是指在對一個對象進行拷貝時,不僅拷貝對象本身和其中的基本數據類型,同時也拷貝對象內部的引用類型。因此,在深拷貝的對象中,引用類型的變量指向的是全新的對象。
好了,現在來總結一下:clone
方法是Object類
中的一個方法,調用這個方法可以創建一個對象的 “拷貝”. 但是要想合法調用 clone 方法,必須要先實現Clonable
接口, 否則就會拋出CloneNotSupportedException
異常.
下面是深拷貝的代碼舉例:
class Money implements Cloneable {public double money;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
class Student implements Cloneable {public int age;public Money m = new Money();public Student(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {//return super.clone();Student tmp = (Student)super.clone();tmp.m = (Money)this.m.clone();return tmp;}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Student student1 = new Student(21);student1.m.money = 52.0;Student student2 = (Student)student1.clone();System.out.println(student1.m.money);System.out.println(student2.m.money);System.out.println("分割線--------------");student1.m.money = 18.8;System.out.println(student1.m.money);System.out.println(student2.m.money);}
}
運行結果如下:
好了,現在我們再來回顧一下什么是深拷貝:深拷貝就是在拷貝對象的同時創建一個新的對象,并將原對象中的所有數據逐個拷貝到新對象中去,包括成員變量引用的其他對象。這樣可以確保原對象和拷貝對象之間的數據相互獨立,互不影響。
三、Object類
在Java中,所有的類都直接或間接地繼承自java.lang.Object類。Object類是Java類層次結構中的根類,它提供了一些通用的方法和功能,可以在所有類中使用。可以這么認為,Object類
是所有類的父類。所以在Java中,即使我們不顯式地在類聲明中使用extends關鍵字繼承Object類
,所有的類仍然會隱式地繼承Object類。這是因為Object類是Java類層次結構中的根類,所有的類都直接或間接繼承自它。
對象比較equals()方法
如上圖:使用equals()方法來比較兩個對象是否相等。
如果在Student類中沒有重寫equals()方法,則默認會使用Object類中的equals()方法,它執行的是比較對象引用的相等性(即比較兩個對象在內存中的地址是否相同)。
因此,上圖中的代碼的最終結果就是False
。但是,如果我想要按照我們自己的方式來比較這兩個對象是否相等的話我們就需要自己去重寫equals()方法
。
現在,如果以兩個對象的年齡是否相等為依據來判斷兩個對象是否相等的話,重寫的equals()方法
如下:
如果我們相比較對象中的內容是否相等的話,我們需要根據自己的判斷依據來重寫Object類
中的equals()方法
。
hashcode()方法
hashcode()方法用于返回對象的hash碼,相當于對象的標識符,它可以將對象轉換為整數以便更好的比較、存儲對象。
好了,現在來舉個栗子,假設當兩個對象的年齡是一樣的話,那么我們認為這兩個對象存儲的位置是相同的,請看代碼:
運行結果如下:
如上圖中的運行結果,雖然兩個對象的年齡是不同的,但是這兩個對象存儲的位置確實相同的,這與我們判斷兩個對象是否相同的判斷依據發生了沖突。
此時我們就需要自己重寫
hashcode()方法。
最終代碼如下,請看:
import java.util.Objects;class Money implements Cloneable {public double money;@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Money money1 = (Money) o;return Double.compare(money1.money, money) == 0;}@Overridepublic int hashCode() {return Objects.hash(money);}
}
class Student implements Cloneable {public int age;public Money m = new Money();public Student(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {//return super.clone();Student tmp = (Student)super.clone();tmp.m = (Money)this.m.clone();return tmp;}@Overridepublic boolean equals(Object obj) {Student student = (Student)obj;return age == student.age;}@Overridepublic int hashCode() {return Objects.hash(age, m);}
}
public class Test {public static void main(String[] args) {Student student1 = new Student(19);Student student2 = new Student(19);System.out.println(student1.hashCode());System.out.println(student2.hashCode());}
}
運行結果如下:
此時就說明這兩個對象的位置是相同的。
小總結:
- hashcode()方法用來確定對象在內存中存儲的位置是否相同。
- 實際上,hashcode()在散列表中才會用到(在散列表中
hashcode()
的作用就是獲取對象的散列碼,進而確定該對象在散列表中的位置);然而hashcode()在其它情況下沒多大用。 - 如果一個類沒有重寫
hashCode()
和equals()
方法,那么它將使用從Object類繼承而來的默認實現。如果默認實現的hashCode()
和equals()
方法不符合我們的需求,此時我們就需要自己重寫hashCode()
和equals()
方法。 - 定義一個自定義類型時,應該養成重寫
hashCode()
和equals()
方法的習慣。
好了,本文到這里就結束了,再見啦友友們!!!