深入分析Comparable與Comparator及Clonable三個(gè)Java接口
1.Comparable
這個(gè)接口是用來(lái)給對(duì)象數(shù)組來(lái)排序的
在我學(xué)接口之前我用的排序方法是Arrays.sort(),我發(fā)現(xiàn)單單靠之前所學(xué)知識(shí)并不能解決給對(duì)象數(shù)組排序的問(wèn)題,后來(lái)學(xué)習(xí)過(guò)程中發(fā)現(xiàn)Comparable這一接口解決了我的疑惑,也感受到了這一接口的強(qiáng)大之處,但這也不是最好的,后續(xù)會(huì)說(shuō)到,畢竟學(xué)知識(shí)是個(gè)循序漸進(jìn)的過(guò)程嘛
首先,我們看一下我們之前學(xué)習(xí)時(shí)用的Arrays.sort
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,3,6,2,4};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
}
}它能將整形數(shù)組從小到大排序,,,下面我們?cè)賮?lái)看一下Arrays.sort給對(duì)象數(shù)組排序(錯(cuò)誤示范)
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",98,78.9);
students[1] = new Student("lisi",38,48.9);
students[2] = new Student("wangwu",18,88.9);
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}當(dāng)我們寫(xiě)出這樣一個(gè)代碼的時(shí)候,我們會(huì)發(fā)現(xiàn)運(yùn)行結(jié)果是個(gè)什么東西???

這時(shí)候不要慌,簡(jiǎn)單分析一下報(bào)錯(cuò)原因,我們可以看到它報(bào)了一個(gè)ClassCastException(類(lèi)型轉(zhuǎn)換異常),根據(jù)后面的稍微能看懂的幾個(gè)英文(小編自己英文水平太差,所以只能看懂幾個(gè)),可以大概的知道是說(shuō)Student不能轉(zhuǎn)換為java.lang包下的Comparable,這時(shí)候我們點(diǎn)進(jìn)去看一下源碼,不要害怕看源碼,有時(shí)候我們往往只需要看懂一點(diǎn)就能明白錯(cuò)誤的原因了

經(jīng)過(guò)粗略的分析if條件語(yǔ)句這一行,發(fā)現(xiàn)數(shù)組取下標(biāo)呢,元素與元素之間都用compareTo來(lái)比較,這時(shí)候,我們發(fā)現(xiàn)其中的貓膩了,,,我們打開(kāi)幫助手冊(cè)查一下Comparable,發(fā)現(xiàn)它是一個(gè)泛型接口,,并且它有一個(gè)抽象方法compareTo,這時(shí)候面紗就將要一層一層的揭開(kāi)了


compareTo方法中這一大段畫(huà),看到第一行我們就明白了,這東西可以幫我們解決數(shù)組對(duì)象的比較問(wèn)題,所以我們要拿Student這個(gè)類(lèi)去實(shí)現(xiàn)Comparable,并且實(shí)現(xiàn)compareTo方法,就能做到對(duì)象數(shù)組的排序了,看代碼:
class Student implements Comparable<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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
//return this.name.compareTo(o.name);
//return this.age - o.age;
return (int)(this.score-o.score);
}
}這時(shí)候Arrays.sort()就可以幫我們做到對(duì)象數(shù)組的排序了,在 sort 方法中會(huì)自動(dòng)調(diào)用 compareTo 方法. compareTo 的參數(shù)是 Object , 其實(shí)傳入的就是 Student 類(lèi)型的對(duì)象.
然后比較當(dāng)前對(duì)象和參數(shù)對(duì)象的大小關(guān)系 (例如按score ).
- 如果當(dāng)前對(duì)象應(yīng)排在參數(shù)對(duì)象之前 , 返回小于 0 的數(shù)字 ;
- 如果當(dāng)前對(duì)象應(yīng)排在參數(shù)對(duì)象之后 , 返回大于 0 的數(shù)字;
- 如果當(dāng)前對(duì)象和參數(shù)對(duì)象不分先后 , 返回 0;
如果你對(duì)姓名的比較存在疑惑,比如:為什么name也可以調(diào)用compareTo方法這種類(lèi)似的問(wèn)題,因?yàn)閚ame是String類(lèi)型的,我建議你先看一下String的源碼,里面也實(shí)現(xiàn)了Comparable接口,以及重寫(xiě)了compareTo方法,這里就不詳細(xì)介紹了,感興趣的小伙伴們可以嘗試一下哦,當(dāng)然,我相信你們都是大佬,一看就懂哈哈
執(zhí)行程序,看運(yùn)行結(jié)果,這下就能達(dá)到我們想要的效果了

前面說(shuō)了,這樣的代碼也不是最好的,存在局限性,我按分?jǐn)?shù)來(lái)排序,那代碼就寫(xiě)死了 ,那我以后想按姓名來(lái)排序,我又得回頭改???,以后進(jìn)公司了,你的代碼做改變,影響其他人的代碼,那不得把你罵死,所以我們需要做進(jìn)一步改進(jìn),引入Comparator接口
2.Comparator比較器
這個(gè)接口又叫比較器,那比較器又是個(gè)什么東西呢???下面我也是老套路啦,一步一步揭開(kāi)這東西的面紗
為了解決Comparable接口的局限性,我們這個(gè)比較器完美的展現(xiàn)了實(shí)現(xiàn)效果,它也是一個(gè)泛型接口,同樣只有一個(gè)抽象方法需要重寫(xiě),下面看代碼:
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;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
//比較器
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class StringComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
class ScoreComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score-o2.score);
}
}
public class TestDemo {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("zhangsan",98,78.9);
students[1] = new Student("lisi",38,48.9);
students[2] = new Student("wangwu",18,88.9);
/*AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students,ageComparator);
StringComparator stringComparator = new StringComparator();
Arrays.sort(students,stringComparator);*/
ScoreComparator scoreComparator = new ScoreComparator();
Arrays.sort(students,scoreComparator);
System.out.println(Arrays.toString(students));
}
}通過(guò)這段代碼,我們發(fā)現(xiàn)比較器是真真正正的做到了,想按什么排序就按什么排序,在對(duì)類(lèi)的侵入性以及代碼耦合度方面也算是不用太過(guò)擔(dān)心了
3.Clonable接口和深拷貝
Java 中內(nèi)置了一些很有用的接口 , Clonable 就是其中之一 .
Object 類(lèi)中存在一個(gè) clone 方法 , 調(diào)用這個(gè)方法可以創(chuàng)建一個(gè)對(duì)象的 " 拷貝 ". 但是要想合法調(diào)用 clone 方法 , 必須要先實(shí)現(xiàn) Clonable 接口 , 否則就會(huì)拋 CloneNotSupportedException 異常.
先來(lái)看一段代碼吧,我個(gè)人喜歡結(jié)合代碼看分析,這樣子就降低了云里霧里的可能性了,
class Student implements Cloneable{
public int id = 1234;
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
//重寫(xiě)Object父類(lèi)的clone()方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) {
Student student1 = new Student();
try {
Student student2 = (Student) student1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}實(shí)現(xiàn)克隆的兩個(gè)條件(結(jié)合上面代碼):
1.這個(gè)對(duì)象可以被克隆,也就是這個(gè)Student類(lèi)要實(shí)現(xiàn)這個(gè)Clonable接口
2.要在這個(gè)類(lèi)中重寫(xiě)父類(lèi)Object的clone()方法

我們現(xiàn)在的代碼只是達(dá)到了這樣一個(gè)效果,,重頭戲還在后邊,因?yàn)槲覀兊目截愑袝r(shí)候遠(yuǎn)遠(yuǎn)不止于此,這種只能算是一個(gè)淺拷貝,那什么才算是深拷貝呢??? 請(qǐng)看下面的代碼:
class Money implements Cloneable{
public double money = 19.9;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Student implements Cloneable{
public int id = 1234;
public Money m = new Money();
@Override
public String toString() {
return "Student{" +
"id=" + id +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student();
//為了代碼的好看,我這里不處理這個(gè)異常
Student student2 = (Student) student1.clone();
System.out.println(student1.m.money);
System.out.println(student2.m.money);
System.out.println("=========================");
student1.m.money = 99.99;
System.out.println(student1.m.money);
System.out.println(student2.m.money);
}
}我現(xiàn)在在之前的代碼的基礎(chǔ)上添加了一個(gè)Money類(lèi),并且實(shí)例化Money的對(duì)象作為Student的成員,這時(shí)候克隆之后,改變我money的值,會(huì)是一個(gè)什么的效果呢???
先看運(yùn)行結(jié)果吧,我就不兜圈子了

我們發(fā)現(xiàn),這并不是我們想要的結(jié)果,我們想要的結(jié)果是,通過(guò)student2去改變money的值,它并不會(huì)影響student1中的money,而我們剛剛的代碼并沒(méi)有做到,它的效果圖如下:

那又如何做到深拷貝呢??這時(shí)我們就需要對(duì)我們剛才的代碼做一些改進(jìn)了
我們只需將原來(lái)的Student類(lèi)中的Object的克隆方法改成下面這份代碼就能做到我們想要的效果:
@Override
protected Object clone() throws CloneNotSupportedException {
Student tmp = (Student) super.clone();//將id變量克隆一份,tmp指向它
tmp.m = (Money) this.m.clone();//將m對(duì)象中的money變量克隆一份 tmp中的m指向它
return tmp;
//return super.clone();
}看到這個(gè)代碼先不要慌,如果注釋沒(méi)看明白,咱還有板書(shū)可以參照
其實(shí)分析起來(lái)也就那么回事

當(dāng)我們tmp返回的時(shí)候,就把0x99給到了student2,克隆完成之后,tmp是局部變量,也就被回收了,,,就變成了下面這副摸樣:

這個(gè)時(shí)候,我們?cè)偃ネㄟ^(guò)student2去改變我們的money,就不會(huì)影響student1中的money的值了,廢話不多說(shuō),運(yùn)行結(jié)果為證

以上就是三個(gè)重要接口的全部分析了,下次再見(jiàn)?。?!
到此這篇關(guān)于深入分析Comparable與Comparator及Clonable三個(gè)Java接口的文章就介紹到這了,更多相關(guān)Java Comparable Comparator Clonable內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot yml中profiles的巧妙用法(小白必看多環(huán)境配置)
這篇文章主要介紹了springboot yml中profiles的巧妙用法,非常適合多環(huán)境配置場(chǎng)景,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
spring boot項(xiàng)目導(dǎo)入依賴(lài)后代碼報(bào)錯(cuò)問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于spring boot項(xiàng)目導(dǎo)入依賴(lài)后代碼報(bào)錯(cuò)問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
解決mysql字符串類(lèi)型的數(shù)字排序出錯(cuò):cast(year as signed)
這篇文章主要介紹了解決mysql字符串類(lèi)型的數(shù)字排序出錯(cuò)問(wèn)題 :cast(year as signed),如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
mybatis如何設(shè)置useGeneratedKeys=true
這篇文章主要介紹了mybatis如何設(shè)置useGeneratedKeys=true,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-01-01
Java中Runnable和Callable分別什么時(shí)候使用
提到 Java 就不得不說(shuō)多線程了,就算你不想說(shuō),面試官也得讓你說(shuō)呀,那說(shuō)到線程,就不得不說(shuō)Runnable和Callable這兩個(gè)家伙了,二者在什么時(shí)候使用呢,下面就來(lái)和簡(jiǎn)單講講2023-08-08
springboot中rabbitmq實(shí)現(xiàn)消息可靠性機(jī)制詳解
這篇文章主要介紹了springboot中rabbitmq實(shí)現(xiàn)消息可靠性機(jī)制詳解,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
詳解XML,Object,Json轉(zhuǎn)換與Xstream的使用
這篇文章主要介紹了詳解XML,Object,Json轉(zhuǎn)換與Xstream的使用的相關(guān)資料,需要的朋友可以參考下2017-02-02
Springboot升級(jí)至2.4.0中出現(xiàn)的跨域問(wèn)題分析及修改方案
這篇文章主要介紹了Springboot升級(jí)至2.4.0中出現(xiàn)的跨域問(wèn)題分析及修改方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12

