您好,登錄后才能下訂單哦!
小編給大家分享一下Java中多態(tài)、抽象類和接口的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
什么是向上轉(zhuǎn)型呢?簡單講就是
把子類對象賦值給了父類對象的引用
這是什么意思呢,我們可以看下列代碼
// 假設(shè) Animal 是父類,Dog 是子類 public class TestDemo{ public static void main(String[] args){ Animal animal=new Animal("動物"); Dog dog=new Dog("二哈"); animal=dog; } }
其中將子類引用 dog 的對象賦值給了父類的引用,而上述代碼也可以簡化成
public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); } }
這個其實和上述代碼一樣,這種寫法都叫“向上轉(zhuǎn)型”,將子類對象的引用賦值給了父類的引用
其實向上轉(zhuǎn)型以后可能用到的比較多,那么我們什么時候需要用它呢?
直接賦值
方法傳參
方法返回
其中直接賦值就是上述代碼的樣子,接下來讓我們看一下方法傳參的實例
// 假設(shè) Animal 是父類,Dog 是子類 public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); func(animal); } public static void func1(Animal animal){ } }
我們寫了一個函數(shù),形參就是父類的引用,而傳遞的實參就是子類引用的對象。也可以寫成
public class TestDemo{ public static void main(String[] args){ Animal animal=new Animal("動物"); Dog dog=new Dog("二哈"); func(dog); } public static void func1(Animal animal){ } }
那么方法返回又是啥樣的呢?其實也很簡單,如
// 假設(shè) Animal 是父類,Dog 是子類 public class TestDemo{ public static void main(String[] args){ } public static Animal func2(){ Dog dog=new Dog("二哈"); return dog; } }
其中在 func2 方法中,將子類的對象返回給父類的引用。還有一種也算是方法返回
public class TestDemo{ public static void main(String[] args){ Animal animal=func2(); } public static Dog func2(){ Dog dog=new Dog("二哈"); return dog; } }
方法的返回值是子類的引用,再將其賦值給父類的對象,這種寫法也叫“向上轉(zhuǎn)型”。
那么既然我們父類的引用指向了子類引用的對象,那么父類可以使用子類的一些方法嗎?試一試
class Animal{ public String name; public Animal(String name){ this.name=name; } public void eat(){ System.out.println(this.name+"吃東西"+"(Animal)"); } } class Dog extends Animal{ public Dog(String name){ super(name); } public void eatDog(){ System.out.println(this.name+"吃東西"+"(Dog)"); } } public class TestDemo{ public static void main(String[] args){ Animal animal1=new Animal("動物"); Animal animal2=new Dog("二哈"); animal1.eat(); animal2.eatdog(); } }
結(jié)果是不可以
因為本質(zhì)上 animal
的引用類型是 Animal
,所以只能使用自己類里面的成員和方法
那么我們的 animal2 可以使用 Dog 類中的 eatDog 方法嗎?其實是可以的,只要我們將這個 eatDog
改名叫 eat 就行
class Dog extends Animal{ public Dog(String name){ super(name); } public void eat(){ System.out.println(this.name+"吃東西"+"(Dog)"); } }
修改后的部分代碼如上,此時,我們之前的 animal2 直接調(diào)用 eat,就可以得到下面的結(jié)果
這也就是說明此時
animal1.eat()
實際調(diào)用的是父類的方法
animal2.eat()
實際調(diào)用的是子類的方法
那么為什么將 eatDog
改成 eat 之后,animal2.eat 調(diào)用的就是子類的方法呢?
這就是我們接下來要講的重寫
什么叫做重寫呢?
子類實現(xiàn)父類的同名方法,并且
方法名相同
方法的返回值一般相同
方法的參數(shù)列表相同
滿足上述的情況就稱為:重寫、覆寫、覆蓋(Override)
注意事項:
重寫的方法不能為密封方法(即被 final 修飾的方法)。我們之前了解過關(guān)鍵字 final,而被他修飾的方法就叫做密封方法,該方法則不能再被重寫,如
// 假如這是父類中的方法 public final void eat(){ System.out.println(this.name+"要吃東西"); }此類方法是不能被重寫的
子類的訪問修飾限定符權(quán)限一定要大于等于父類的權(quán)限,但是父類不能是被
private
修飾方法不能被 static 修飾
一般針對重寫的方法,可以使用
@Override
注解來顯示指定。加了他有什么好處呢?看下面代碼// 假如下面的 eat 是被重寫的方法 class Dog extends Animal{ @Override private void eat(){ // ... } }當(dāng)我們?nèi)绯霈F(xiàn) eat 被寫成了 ate 時候,那么編譯器就會發(fā)現(xiàn)父類中是沒有 ate 方法的,就會編譯報錯,提示無法構(gòu)成重寫
重寫時可以修改返回值,方法名和參數(shù)類型及個數(shù)都不可以修改。僅當(dāng)返回值為類類型時,重寫的方法才可以修改返回值類型,且必須是父類方法返回值的子類;要么就不修改,與父類返回值類型相同
了解到這,大家對于重寫肯定有了一個概念。此時我們再回憶一下之前學(xué)過的重載,可以做一個表格來進行對比
區(qū)別 | 重載(Overload) | 重寫(Override) |
---|---|---|
概念 | 方法名稱相同、參數(shù)列表不同、返回值無要求 | 方法名稱相同、參數(shù)列表相同、返回類型一般相同 |
范圍 | 重載不是必須在一個類當(dāng)中(繼承) | 繼承關(guān)系 |
限制 | 沒有權(quán)限要求 | 被覆寫的方法不能擁有比父類更嚴(yán)格的訪問控制權(quán)限 |
比較結(jié)果就是,兩者沒啥關(guān)系呀
講到這里,我們好像一直沒有說明上一小節(jié)的標(biāo)題動態(tài)綁定是啥
那么什么叫做動態(tài)綁定呢?發(fā)生的條件如下
發(fā)生向上轉(zhuǎn)型(父類引用需要引用子類對象)
通過父類引用,來調(diào)用子類和父類的同名覆蓋方法
那為啥是叫動態(tài)的呢?經(jīng)過反匯編我們可以發(fā)現(xiàn)
編譯的時候: 調(diào)用的是父類的方法
但是運行的時候: 實際上調(diào)用的是子類的方法
因此這其實是一個動態(tài)的過程,也可以叫其運行時綁定
既然介紹了向上轉(zhuǎn)型,那肯定也缺不了向下轉(zhuǎn)型呀!什么時向下轉(zhuǎn)型呢?想想向上轉(zhuǎn)型就可以猜到它就是
把父類對象賦值給了子類對象的引用
那么換成代碼就是
// 假設(shè) Animal 是父類,Dog 是子類 public class TestDemo{ public static void main(String[] args){ Animal animal=new Animal("動物"); Dog dog=animal; } }
但是只是上述這樣寫是不行的,會報錯
為什么呢?我們可以這樣想一下
狗是動物,但是動物不能說是狗,這相當(dāng)于是一個包含的關(guān)系。
因此可以將狗的對象直接賦值給動物,但是不能將動物的對象賦值給狗
我們就可以使用強制類型轉(zhuǎn)換,這樣上述代碼就不會報錯了
public class TestDemo{ public static void main(String[] args){ Animal animal=new Animal("動物"); Dog dog=(Dog)animal; } }
我們接著用 dog 引用去運行一下 eat 方法
public class TestDemo{ public static void main(String[] args){ Animal animal=new Animal("動物"); Dog dog=(Dog)animal; dog.eat(); } }
運行后出現(xiàn)了錯誤
動物不能被轉(zhuǎn)換成狗!
那我們該怎么做呢?我們要記住一點:
使用向下轉(zhuǎn)型的前提是:一定要發(fā)生了向上轉(zhuǎn)型
public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); Dog dog=(Dog)animal; dog.eat(); } }
這樣就沒問題啦!
像上述我們提到使用向下轉(zhuǎn)型的前提是要發(fā)生向上轉(zhuǎn)型。我們其實可以理解為,我們在使用向上轉(zhuǎn)型的時候,有些功能無法做到,故我們再使用向下轉(zhuǎn)型來完善代碼(emmm,純屬個人愚見啦)。就比如
// 假設(shè)我的 Dog 類中有一個看家的方法 guard public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); animal.guard(); } }
上述代碼就會報錯,因為 Animal 類中是沒有 guard 方法的。因此我們就要借用向下轉(zhuǎn)型
public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); Dog dog =animal; dog.guard(); } }
注意:
其實向下轉(zhuǎn)型不常使用,使用它可能會不小心犯一些錯誤。如果我們上述的代碼又要繼續(xù)使用一些其他動物的特有方法,如果忘了它們沒有發(fā)生向上轉(zhuǎn)型,就會報錯。
為了避免這種錯誤: 我們可以使用 instanceof
instanceof:可以判定一個引用是否是某個類的實例,如果是則返回 true,不是則返回 false,如
public class TestDemo{ public static void main(String[] args){ Animal animal=new Dog("二哈"); if(animal instanceof Bird){ Bird bird=(Bird)animal; bird.fly(); } } }
上述代碼就是先判斷 Animal
的引用是否是 Bird 的實例,我們知道它應(yīng)該是 Dog 的實例,故返回 false
其實上章就講解過了 super 關(guān)鍵字,這里我再用一個表格比較下 this 和 super,方便理解
接下來我們看一段代碼,大家可以猜猜結(jié)果是啥哦!
class Animal{ public String name; public Animal(String name){ eat(); this.name=name; } public void eat(){ System.out.println(this.name+"在吃食物(Animal)"); } } class Dog extends Animal{ public Dog(String name){ super(name); } public void eat(){ System.out.println(this.name+"在吃食物(Dog)"); } } public class TestDemo{ public static void main(String[] args){ Dog dog=new Dog("二哈"); } }
結(jié)果就是
如果沒猜對的,一般有兩個疑惑:
沒有調(diào)用 eat 方法,但為什么結(jié)果是這樣的?
為啥是 null?
解答:
疑惑一: 因為子類繼承父類需要幫父類構(gòu)造方法,所以子類創(chuàng)建對象時,就構(gòu)造了父類的構(gòu)造方法,就執(zhí)行了父類的 eat 方法
疑惑二: 由于父類構(gòu)造方法是先執(zhí)行 eat 方法,而 name 的賦值在后面一步,多以此時的 name 是 null
結(jié)論:
構(gòu)造方法中可以調(diào)用重寫的方法,并且發(fā)生了動態(tài)綁定
介紹到這里,我們終于要開始正式介紹我們今天的一大重點多態(tài)了!那什么是多態(tài)呢?其實他和繼承一樣是一種思想,我們可以先看一段代碼
class Shape{ public void draw(){ } } class Cycle extends Shape{ @Override public void draw() { System.out.println("畫一個圓?"); } } class Rect extends Shape{ @Override public void draw() { System.out.println("畫一個方片?"); } } class Flower extends Shape{ @Override public void draw() { System.out.println("畫一朵花?"); } } public class TestDemo{ public static void main(String[] args) { Cycle shape1=new Cycle(); Rect shape2=new Rect(); Flower shape3=new Flower(); drawMap(shape1); drawMap(shape2); drawMap(shape3); } public static void drawMap(Shape shape){ shape.draw(); } }
我們發(fā)現(xiàn) drawMap
這個方法被調(diào)用者使用時,都是經(jīng)過父類調(diào)用了其中的 draw 方法,并且最終的表現(xiàn)形式是不一樣的。而這種思想就叫做多態(tài)。
更簡單的說,多態(tài)就是
一個引用能表現(xiàn)出多種不同的形態(tài)
而多態(tài)是一種思想,實現(xiàn)它的前提有兩點
向上轉(zhuǎn)型
調(diào)用同名的覆蓋方法
而一種思想的傳承總有它獨到的好處,那么使用多態(tài)有什么好處呢?
1)類調(diào)用者對類的使用成本進一步降低
封裝是讓類的調(diào)用者不需要知道類的實現(xiàn)細(xì)節(jié)
多態(tài)能讓類的調(diào)用者連這個類的類型是什么都不必知道,只需要這個對象具有某種方法即可
2)能夠降低代碼的“圈復(fù)雜度”,避免使用大量的 if-else 語句
圈復(fù)雜度:
是一種描述一段代碼復(fù)雜程度的方式??梢詫⒁欢未a中條件語句和循環(huán)語句出現(xiàn)的個數(shù)看作是“圈復(fù)雜度”,這個個數(shù)越多,就認(rèn)為理解起來更復(fù)雜。
我們可以看一段代碼
public static void drawShapes(){ Rect rect = new Rect(); Cycle cycle = new Cycle(); Flower flower = new Flower(); String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; for (String shape : shapes) { if (shape.equals("cycle")) { cycle.draw(); } else if (shape.equals("rect")) { rect.draw(); } else if (shape.equals("flower")) { flower.draw(); } } }
這段代碼的意思就是要分別打印圓、方片、圓、方片、花,如果不使用多態(tài)的話,我們一般就會寫出上面這種方法。而使用多態(tài)的話,代碼就會顯得很簡單,如
public static void drawShapes() { // 我們創(chuàng)建了一個 Shape 對象的數(shù)組. Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; for (Shape shape : shapes) { shape.draw(); } }
我們可以通過下面這種圖理解上面的代碼
而整體看起來,使用了多態(tài)的代碼就簡單了很多
3)可擴展能力強
如上述畫圖的代碼,如果我們要新增一種新的形狀,使用多態(tài)的方式改動成本也比較低,如
// 增加三角形 class Triangle extends Shape { @Override public void draw() { System.out.println("△"); } }運用多態(tài)的話,我們擴展的代碼增加一個新類就可以。而對于不使用多態(tài)的情況,就還需要對
if-else
語句進行一定的修改,故改動成本會更高
到此為止,面向?qū)ο蟮娜筇攸c:封裝、繼承、多態(tài)已經(jīng)全部介紹完了。由于我個人的理解也有限,所以講的可能不好、不足,希望大家多多理解呀。
接下來將會介紹抽象類和接口,其中也會進一步運用到多態(tài),大家可以多多練習(xí),加深思想的理解。
我們上面剛寫過一個畫圖型的代碼,其中父類的定義是這樣的
class Shape{ public void draw(){ } }
我們發(fā)現(xiàn),父類中的 draw 方法里面沒有內(nèi)容,而繪圖都是通過各種子類的 draw 方法完成的。
像上述代碼,這種沒有實際工作的方法,我們可以通過 abstract
來設(shè)計設(shè)計成一個抽象方法,而包含抽象方法的類就是抽象類
設(shè)計之后的代碼就是這樣的
abstract class Shape{ public abstract void draw(); }
方法和類都要由 abstract 修飾
抽象類中可以定義其他數(shù)據(jù)成員和成員方法,如
abstract class Shape{ public int a; public void b(){ // ... } public abstract void draw(); }但要使用這些成員和方法,需要靠子類通過 super 才能使用
抽象類不可以被實例化
抽象方法不能是被 private 修飾的
抽象方法不能是被 final 修飾的,它與 abstract 不能被共存
如果子類繼承了抽象類,但不需要重寫父類的抽象方法,則可以將子類用 abstract 修飾,如
abstract class Shape{ public abstract void draw(); } abstract Color extends Shape{ }此時該子類中既可以定義普通方法也可以定義抽象方法
一個抽象類 A 可以被另外的抽象類 B 繼承,但是如果有其他的普通類繼承了抽象類 B,則該普通類需要重寫 A 和 B 中的所有抽象方法
我們要知道抽象類的意義就是為了被繼承
從注意事項中就知道抽象類本身是不能被實例化的,要想使用它,只能創(chuàng)建子類去繼承,就比如
abstract class Shape{ public int a; public void b(){ // ... } public abstract void draw(); } class Cycle extends Shape{ @Override public void draw(){ System.out.println("畫一個?"); } } public class TestDemo{ public static void main(String[] args){ Shape shape=new Cycle(); } }
要注意子類需要重寫父類的所有抽象方法,不然代碼就會報錯
那么抽象類既然不能被實例化,那為什么要用它呢?
使用了抽象類就相當(dāng)于多了一重編譯器的效驗
啥意思呢?就比如按照上述畫圖的代碼,實際工作其實是由子類完成的,如果不小心誤用了父類,父類不是抽象類的話是不會報錯的,因此將父類設(shè)計成抽象類,它會在父類被實例化的時候報錯,讓我們盡早地發(fā)現(xiàn)錯誤
我們上面介紹了抽象類,抽象類中除了抽象方法還可以包含普通的方法和成員。
而接口中也可包含方法和字段,但只能是抽象方法和靜態(tài)常量。
我們可以將上述 Shape 改寫成一個 接口,代碼如下
interface IShape{ public static void draw(); }
具體的語法規(guī)則如下:
接口是使用
interface
定義的接口的命名一般以大寫字母
I
開頭接口中的方法一定是抽象的、被 public 修飾的方法,因此其中抽象方法可以簡化代碼為
interface IShape{ void draw(); }這樣寫默認(rèn)是
public abstract
的接口中也可以包含被 public 修飾的靜態(tài)常量,并且可以省略
public static final
,如interface IShape{ public static final int a=10; public static int b=10; public int c=10; int d=10; }接口不能被單獨實例化,和抽象類一樣需要被子類繼承使用,但是接口中使用
implements
繼承,如interface IShape{ public static void draw(); } class Cycle implements IShape{ @Override public void draw(){ System.out.println("畫一個圓預(yù)?"); } }和
extends
表達含義是”擴展“不同,implements
表達的是”實現(xiàn)“,即表示當(dāng)前什么都沒有,一切需要從頭構(gòu)造基礎(chǔ)接口的類需要重寫接口中的全部抽象方法
一個類可以使用
implements
實現(xiàn)多個接口,每個接口之間使用逗號分隔開就可以,如interface A{ void func1(); } interface B{ void func2(); } class C implements A,B{ @Override public void func1(){ } @Override public void func2{ } }注意這個類要重寫所有繼承的接口的所有抽象方法,在 IDEA 中使用 ctrl + i ,快速實現(xiàn)接口
接口和接口之間的關(guān)系可以使用 extends 來維護,這是意味著”擴展“,即某個接口擴展了其他接口的功能,如
interface A{ void func1(); } interface B{ void func2(); } interface D implements A,B{ @Override public void func1(){ } @Override public void func2{ } void func3(); }
注意:
在 JDK1.8 開始,接口當(dāng)中的方法可以是普通方法,但前提是:這個方法是由 default 修飾的(即是這個接口的默認(rèn)方法),如
interface IShape{ void draw(); default public void func(){ System.out.println("默認(rèn)方法"); } }
我們之前介紹過,Java 中的繼承是單繼承,即一個類只能繼承一個父類
但是可以同時實現(xiàn)多個接口,故我們可以通過多接口去達到多繼承類似的效果
接下來通過代碼來理解吧!
class Animal{ public String name; public Animal(String name){ this.name=name; } } class Bird extends Animal{ public Bird(String name){ super(name); } }
此時子類 Bird 繼承了父類 Animal
,但是不能再繼承其他類了,但是還可以繼續(xù)實現(xiàn)其他的接口,如
class Animal{ public String name; public Animal(String name){ this.name=name; } } interface ISwing{ void swing(); } interface IFly{ void fly(); } class Bird extends Animal implements ISwing,IFly{ public Bird(String name){ super(name); } @Override public void swing(){ System.out.println(this.name+"在游"); } @Override public void fly(){ System.out.println(this.name+"在飛"); } }
上述代碼就相當(dāng)于實現(xiàn)了多繼承,因此接口的出現(xiàn)很好的解決了 Java 單繼承的問題
并且我們可以感受到,接口表達的好像是具有了某種屬性,因此有了接口以后,類的使用者就不必關(guān)注具體的類型了,而只要關(guān)注該類是否具備某個能力,比如
public class TestDemo { public static void fly(IFly flying){ flying.fly(); } public static void main(String[] args) { IFly iFly=new Bird("飛鳥"); fly(iFly); } }
因為飛鳥本身具有飛的屬性,所以我們不必關(guān)注具體的類型,因為只要會飛的都可以實現(xiàn)飛的屬性,如超人也會飛,就可以定義一個超人的類
class SuperMan implements IFly{ @Override public void fly(){ System.out.println("超人在飛"); } } public class TestDemo { public static void fly(IFly flying){ flying.fly(); } public static void main(String[] args) { fly(new SuperMan()); } }
注意:
子類先繼承父類再實現(xiàn)接口
語法規(guī)則里面就介紹了,接口和接口之間可以使用 extends
來維護,可以使某個接口擴展其他接口的功能
這里就不再重述了
下面我們再學(xué)習(xí)一些接口,來加深對于接口的理解
我們之前介紹過 Arrays 類中的 sort 方法,它可以幫我們進行排序,比如
public class TestDemo { public static void main(String[] args) { int[] array={2,9,4,1,7}; System.out.println("排序前:"+Arrays.toString(array)); Arrays.sort(array); System.out.println("排序后:"+Arrays.toString(array)); } }
而接下來我想要對一個學(xué)生的屬性進行排序。
首先我實現(xiàn)一個 Student
類,并對 toString 方法進行了重寫
class Student{ private String name; private int age; private 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 + '}'; } }
接下來我寫了一個數(shù)組,并賦予了學(xué)生數(shù)組一些屬性
public class TestDemo { public static void main(String[] args) { Student[] student=new Student[3]; student[0]=new Student("張三",18,96.5); student[0]=new Student("李四",19,99.5); student[0]=new Student("王五",17,92.0); } }
那么我們可以直接通過 sort 函數(shù)進行排序嗎?我們先寫如下代碼
public class TestDemo { public static void main(String[] args) { Student[] student=new Student[3]; student[0]=new Student("張三",18,96.5); student[1]=new Student("李四",19,99.5); student[2]=new Student("王五",17,92.0); System.out.println("排序前:"+student); Arrays.sort(student); System.out.println("排序后:"+student); } }
最終結(jié)果卻是
我們來分析一下
ClassCastException:類型轉(zhuǎn)換異常,說 Student 不能被轉(zhuǎn)換為
java.lang.Comparable
這是什么意思呢?我們思考由于 Student 是我們自定義的類型,里面包含了多個類型,那么 sort 方法怎么對它進行排序呢?好像沒有一個依據(jù)。
此時我通過報錯找到了
Comparable
可以知道這個應(yīng)該是一個接口,那我們就可以嘗試將我們的 Student 類繼承這個接口,其中后面的 < T > 其實是泛型的意思,這里改成 < Student > 就行
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 + '}'; } }但此時還不行,因為繼承需要重寫接口的抽象方法,所以經(jīng)過查找,我們找到了
增加的重寫方法就是
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 }這里應(yīng)該就是比較規(guī)則的設(shè)定地方了,我們再看看 sort 方法中的交換
也就是說如果此時的左值大于右值,則進行交換
那么如果我想對學(xué)生的年齡進行排序,重寫后的方法應(yīng)該就是
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return this.age-o.age; }此時再運行代碼,結(jié)果就是
而到這里我們可以更深刻的感受到,接口其實就是某種屬性或者能力,而上述 Student 這個類繼承了這個比較的接口,就擁有了比較的能力
缺點:
當(dāng)我們比較上述代碼的姓名時,就要將重寫的方法改為
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return this.name.compareTo(o.name); }當(dāng)我們比較上述代碼的分?jǐn)?shù)時,就要將重寫的方法改為
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return int(this.score-o.score); }我們發(fā)現(xiàn)當(dāng)我們要修改比較的東西時,就可能要重新修改重寫的方法。這個局限性就比較大
為了解決這個缺陷,就出現(xiàn)了下面的接口 Comparator
我們進入 sort 方法的定義中還可以看到一個比較方法,其中有兩個參數(shù)數(shù)組與 Comparator 的對象
這里就用到了 Comparator
接口
這個接口啥嘞?我們可以先定義一個年齡比較類 AgeComparator,就是專門用來比較年齡,并讓他繼承這個類
class AgeCompartor implements Comparator<Student>{ }
再通過按住 ctrl 并點擊它,我們可以跳轉(zhuǎn)到它的定義,此時我們可以發(fā)現(xiàn)它里面有一個方法是
這個與上述 Comparable
中的 compareTo
不同,那我先對它進行重寫
class AgeCompartor implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { return o1.age-o2.age; } }
我們再按照 sort 方法的描述,寫如下代碼
public class TestDemo { public static void main(String[] args) { Student[] student=new Student[3]; student[0]=new Student("張三",18,96.5); student[1]=new Student("李四",19,99.5); student[2]=new Student("王五",17,92.0); System.out.println("排序前:"+Arrays.toString(student)); AgeComparator ageComparator=new AgeComparator(); Arrays.sort(student,ageComparator); System.out.println("排序后:"+Arrays.toString(student)); } }
這樣就可以正常的對學(xué)生的年齡進行比較了,而此時我們要再對姓名進行排序,我們就可以創(chuàng)建一個姓名比較類 NameComparator
class NameComparator implements Comparator<Student>{ @Override public int compare(Student o1, Student o2) { return o1.name.compareTo(o2.name); } }
而我們也只需要將 sort 方法的參數(shù) ageComparator
改成 nameComparator
就可以了
我們可以將上述 AgeComparator
和 NameComparator
理解成比較器 ,而使用 Comparator 這個接口比 Comparable 的局限性小很多,我們?nèi)绻獙δ硞€屬性進行比較只要增加它的比較器即可
首先我們可以看這樣的代碼
class Person{ public String name ="LiXiaobo"; @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } } public class TestDemo { public static void main(String[] args) { Person person=new Person(); } }
那什么是克隆呢?應(yīng)該就是搞一個副本出來,比如
那么既然這次講 Cloneable
接口,我就對其進行繼承唄!
class Person implements Cloneable{ public String name ="LiXiaobo"; @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } }
但是我們發(fā)現(xiàn)就算繼承之后,我們也不能通過創(chuàng)建的引用去找到一個克隆的方法。此時我們可以點到 Cloneable的定義看看
太牛了,啥都沒有!
我們發(fā)現(xiàn),Cloneable
這個接口是一個空接口(也叫標(biāo)記接口),而這個接口的作用就是:如果一個類實現(xiàn)了這個接口,就證明它是可以被克隆的
而在使用它之前,我們還需要重寫 Object
的克隆方法(所有的類默認(rèn)繼承于 Object 類)
怎樣重寫克隆方法呢?通過 ctrl + o,就可以看到
再選擇 clone 就?,重寫后的代碼就變成
class Person implements Cloneable{ public String name ="LiXiaobo"; @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
而此時我們就可以看到一個 clone 方法
點擊之后,我們發(fā)現(xiàn)居然還是報錯
原因是由于重寫的 clone 方法會拋出異常,針對這個就有兩種方式,今天介紹簡單一點的方式
方式一: 將鼠標(biāo)放到 clone 上,按住 Alt + enter,你就會看到
點擊紅框框就行,但是你會發(fā)現(xiàn)點擊后還是報錯,這是由于重寫的方法的返回值是 Object,而編譯器會認(rèn)為這是不安全的,因此將它強制轉(zhuǎn)換成 Person 就可以了。此時我們再將克隆的副本輸出發(fā)現(xiàn)結(jié)果沒問題
并且通過地址的打印,副本和原來的地址是不一樣的
介紹到這里,簡單的克隆流程就已經(jīng)介紹完了。但是接下來我們再深入一點思考,在 Person 類原有代碼的基礎(chǔ)上增加整形 a
class Person implements Cloneable{ public String name ="LiXiaobo"; public int a=10; @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
此時我們再通過 person 和 person 分別打印,代碼如下
public class TestDemo3 { public static void main(String[] args) throws CloneNotSupportedException { Person person=new Person(); Person person1=(Person)person.clone(); System.out.println(person.a); System.out.println(person1.a); System.out.println("#############"); person1.a=50; System.out.println(person.a); System.out.println(person1.a); } }
結(jié)果如下
我們發(fā)現(xiàn)這種情況 person1 就完全是一個副本,對它進行修改是與 person 無關(guān)的。
但是我們再看下面這種情況,我們定義一個 Money 類,并在 Person 創(chuàng)建它
class Money{ public int money=10; } class Person implements Cloneable{ public String name ="LiXiaobo"; public Money money=new Money(); @Override public String toString() { return "Person{" + "name='" + name + '\'' + '}'; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
然后我們再修改 person1 中的 money 的值,代碼如下
public class TestDemo3 { public static void main(String[] args) throws CloneNotSupportedException { Person person=new Person(); Person person1=(Person)person.clone(); System.out.println(person.money.money); System.out.println(person1.money.money); System.out.println("#############"); person.money.money=50; System.out.println(person.money.money); System.out.println(person1.money.money); } }
這次的結(jié)果是
這是為什么呢?我們可以分析下面的圖片
由于克隆的是 person 的對象,所以只克隆了(0x123)的 money,而(0x456)的 money 沒有被克隆,所以就算前面的 money 被克隆的副本也指向它,所以改變副本的 money,它也會被改變
而上述這種情況其實叫做淺拷貝,那么怎么將其變成深拷貝呢?
我們只要將 money 引用所指向的對象也克隆一份
步驟:
將 Money 類也實現(xiàn) Cloneable 接口,并重寫克隆方法
class Money implements Cloneable{ public int money=10; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }修改 Person 中的克隆方法
@Override protected Object clone() throws CloneNotSupportedException { Person personClone=(Person)super.clone(); personClone.money=(Money)this.money.clone(); return personClone; }
以上是“Java中多態(tài)、抽象類和接口的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。