Java語(yǔ)法之 Java 的多態(tài)、抽象類(lèi)和接口
上一篇文章:Java 基礎(chǔ)語(yǔ)法之解析 Java 的包和繼承
今天這章主要介紹多態(tài)和抽象類(lèi),希望接下來(lái)的內(nèi)容對(duì)你有幫助!
一、多態(tài)
在了解多態(tài)之前我們先了解以下以下的知識(shí)點(diǎn)
1. 向上轉(zhuǎn)型
什么是向上轉(zhuǎn)型呢?簡(jiǎn)單講就是
把子類(lèi)對(duì)象賦值給了父類(lèi)對(duì)象的引用
這是什么意思呢,我們可以看下列代碼
// 假設(shè) Animal 是父類(lèi),Dog 是子類(lèi)
public class TestDemo{
public static void main(String[] args){
Animal animal=new Animal("動(dòng)物");
Dog dog=new Dog("二哈");
animal=dog;
}
}
其中將子類(lèi)引用 dog 的對(duì)象賦值給了父類(lèi)的引用,而上述代碼也可以簡(jiǎn)化成
public class TestDemo{
public static void main(String[] args){
Animal animal=new Dog("二哈");
}
}
這個(gè)其實(shí)和上述代碼一樣,這種寫(xiě)法都叫“向上轉(zhuǎn)型”,將子類(lèi)對(duì)象的引用賦值給了父類(lèi)的引用
其實(shí)向上轉(zhuǎn)型以后可能用到的比較多,那么我們什么時(shí)候需要用它呢?
- 直接賦值
- 方法傳參
- 方法返回
其中直接賦值就是上述代碼的樣子,接下來(lái)讓我們看一下方法傳參的實(shí)例
// 假設(shè) Animal 是父類(lèi),Dog 是子類(lèi)
public class TestDemo{
public static void main(String[] args){
Animal animal=new Dog("二哈");
func(animal);
}
public static void func1(Animal animal){
}
}
我們寫(xiě)了一個(gè)函數(shù),形參就是父類(lèi)的引用,而傳遞的實(shí)參就是子類(lèi)引用的對(duì)象。也可以寫(xiě)成
public class TestDemo{
public static void main(String[] args){
Animal animal=new Animal("動(dòng)物");
Dog dog=new Dog("二哈");
func(dog);
}
public static void func1(Animal animal){
}
}
那么方法返回又是啥樣的呢?其實(shí)也很簡(jiǎn)單,如
// 假設(shè) Animal 是父類(lèi),Dog 是子類(lèi)
public class TestDemo{
public static void main(String[] args){
}
public static Animal func2(){
Dog dog=new Dog("二哈");
return dog;
}
}
其中在 func2 方法中,將子類(lèi)的對(duì)象返回給父類(lèi)的引用。還有一種也算是方法返回
public class TestDemo{
public static void main(String[] args){
Animal animal=func2();
}
public static Dog func2(){
Dog dog=new Dog("二哈");
return dog;
}
}
方法的返回值是子類(lèi)的引用,再將其賦值給父類(lèi)的對(duì)象,這種寫(xiě)法也叫“向上轉(zhuǎn)型”。
那么既然我們父類(lèi)的引用指向了子類(lèi)引用的對(duì)象,那么父類(lèi)可以使用子類(lèi)的一些方法嗎?試一試
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("動(dòng)物");
Animal animal2=new Dog("二哈");
animal1.eat();
animal2.eatdog();
}
}
結(jié)果是不可以

因?yàn)楸举|(zhì)上 animal 的引用類(lèi)型是 Animal,所以只能使用自己類(lèi)里面的成員和方法
2. 動(dòng)態(tài)綁定
那么我們的 animal2 可以使用 Dog 類(lèi)中的 eatDog 方法嗎?其實(shí)是可以的,只要我們將這個(gè) eatDog 改名叫 eat 就行
class Dog extends Animal{
public Dog(String name){
super(name);
}
public void eat(){
System.out.println(this.name+"吃東西"+"(Dog)");
}
}
修改后的部分代碼如上,此時(shí),我們之前的 animal2 直接調(diào)用 eat,就可以得到下面的結(jié)果

這也就是說(shuō)明此時(shí)
animal1.eat()實(shí)際調(diào)用的是父類(lèi)的方法animal2.eat()實(shí)際調(diào)用的是子類(lèi)的方法
那么為什么將 eatDog 改成 eat 之后,animal2.eat 調(diào)用的就是子類(lèi)的方法呢?
這就是我們接下來(lái)要講的重寫(xiě)
3. 方法重寫(xiě)
什么叫做重寫(xiě)呢?
子類(lèi)實(shí)現(xiàn)父類(lèi)的同名方法,并且
- 方法名相同
- 方法的返回值一般相同
- 方法的參數(shù)列表相同
滿足上述的情況就稱(chēng)為:重寫(xiě)、覆寫(xiě)、覆蓋(Override)
注意事項(xiàng):
- 重寫(xiě)的方法不能為密封方法(即被 final 修飾的方法)。我們之前了解過(guò)關(guān)鍵字 final,而被他修飾的方法就叫做密封方法,該方法則不能再被重寫(xiě),如
// 假如這是父類(lèi)中的方法 public final void eat(){ System.out.println(this.name+"要吃東西"); }此類(lèi)方法是不能被重寫(xiě)的
- 子類(lèi)的訪問(wèn)修飾限定符權(quán)限一定要大于等于父類(lèi)的權(quán)限,但是父類(lèi)不能是被
private修飾- 方法不能被 static 修飾
- 一般針對(duì)重寫(xiě)的方法,可以使用
@Override注解來(lái)顯示指定。加了他有什么好處呢?看下面代碼// 假如下面的 eat 是被重寫(xiě)的方法 class Dog extends Animal{ @Override private void eat(){ // ... } }當(dāng)我們?nèi)绯霈F(xiàn) eat 被寫(xiě)成了 ate 時(shí)候,那么編譯器就會(huì)發(fā)現(xiàn)父類(lèi)中是沒(méi)有 ate 方法的,就會(huì)編譯報(bào)錯(cuò),提示無(wú)法構(gòu)成重寫(xiě)
- 重寫(xiě)時(shí)可以修改返回值,方法名和參數(shù)類(lèi)型及個(gè)數(shù)都不可以修改。僅當(dāng)返回值為類(lèi)類(lèi)型時(shí),重寫(xiě)的方法才可以修改返回值類(lèi)型,且必須是父類(lèi)方法返回值的子類(lèi);要么就不修改,與父類(lèi)返回值類(lèi)型相同
了解到這,大家對(duì)于重寫(xiě)肯定有了一個(gè)概念。此時(shí)我們?cè)倩貞浺幌轮皩W(xué)過(guò)的重載,可以做一個(gè)表格來(lái)進(jìn)行對(duì)比
| 區(qū)別 | 重載(Overload) | 重寫(xiě)(Override) |
|---|---|---|
| 概念 | 方法名稱(chēng)相同、參數(shù)列表不同、返回值無(wú)要求 | 方法名稱(chēng)相同、參數(shù)列表相同、返回類(lèi)型一般相同 |
| 范圍 | 重載不是必須在一個(gè)類(lèi)當(dāng)中(繼承) | 繼承關(guān)系 |
| 限制 | 沒(méi)有權(quán)限要求 | 被覆寫(xiě)的方法不能擁有比父類(lèi)更嚴(yán)格的訪問(wèn)控制權(quán)限 |
比較結(jié)果就是,兩者沒(méi)啥關(guān)系呀
講到這里,我們好像一直沒(méi)有說(shuō)明上一小節(jié)的標(biāo)題動(dòng)態(tài)綁定是啥
那么什么叫做動(dòng)態(tài)綁定呢?發(fā)生的條件如下
- 發(fā)生向上轉(zhuǎn)型(父類(lèi)引用需要引用子類(lèi)對(duì)象)
- 通過(guò)父類(lèi)引用,來(lái)調(diào)用子類(lèi)和父類(lèi)的同名覆蓋方法
那為啥是叫動(dòng)態(tài)的呢?經(jīng)過(guò)反匯編我們可以發(fā)現(xiàn)
編譯的時(shí)候: 調(diào)用的是父類(lèi)的方法
但是運(yùn)行的時(shí)候: 實(shí)際上調(diào)用的是子類(lèi)的方法
因此這其實(shí)是一個(gè)動(dòng)態(tài)的過(guò)程,也可以叫其運(yùn)行時(shí)綁定
4. 向下轉(zhuǎn)型
既然介紹了向上轉(zhuǎn)型,那肯定也缺不了向下轉(zhuǎn)型呀!什么時(shí)向下轉(zhuǎn)型呢?想想向上轉(zhuǎn)型就可以猜到它就是
把父類(lèi)對(duì)象賦值給了子類(lèi)對(duì)象的引用
那么換成代碼就是
// 假設(shè) Animal 是父類(lèi),Dog 是子類(lèi)
public class TestDemo{
public static void main(String[] args){
Animal animal=new Animal("動(dòng)物");
Dog dog=animal;
}
}
但是只是上述這樣寫(xiě)是不行的,會(huì)報(bào)錯(cuò)

為什么呢?我們可以這樣想一下
狗是動(dòng)物,但是動(dòng)物不能說(shuō)是狗,這相當(dāng)于是一個(gè)包含的關(guān)系。
因此可以將狗的對(duì)象直接賦值給動(dòng)物,但是不能將動(dòng)物的對(duì)象賦值給狗
我們就可以使用強(qiáng)制類(lèi)型轉(zhuǎn)換,這樣上述代碼就不會(huì)報(bào)錯(cuò)了
public class TestDemo{
public static void main(String[] args){
Animal animal=new Animal("動(dòng)物");
Dog dog=(Dog)animal;
}
}
我們接著用 dog 引用去運(yùn)行一下 eat 方法
public class TestDemo{
public static void main(String[] args){
Animal animal=new Animal("動(dòng)物");
Dog dog=(Dog)animal;
dog.eat();
}
}
運(yùn)行后出現(xiàn)了錯(cuò)誤

動(dòng)物不能被轉(zhuǎn)換成狗!
那我們?cè)撛趺醋瞿兀课覀円涀∫稽c(diǎ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();
}
}
這樣就沒(méi)問(wèn)題啦!
像上述我們提到使用向下轉(zhuǎn)型的前提是要發(fā)生向上轉(zhuǎn)型。我們其實(shí)可以理解為,我們?cè)谑褂孟蛏限D(zhuǎn)型的時(shí)候,有些功能無(wú)法做到,故我們?cè)偈褂孟蛳罗D(zhuǎn)型來(lái)完善代碼(emmm,純屬個(gè)人愚見(jiàn)啦)。就比如
// 假設(shè)我的 Dog 類(lèi)中有一個(gè)看家的方法 guard
public class TestDemo{
public static void main(String[] args){
Animal animal=new Dog("二哈");
animal.guard();
}
}
上述代碼就會(huì)報(bào)錯(cuò),因?yàn)?Animal 類(lèi)中是沒(méi)有 guard 方法的。因此我們就要借用向下轉(zhuǎn)型
public class TestDemo{
public static void main(String[] args){
Animal animal=new Dog("二哈");
Dog dog =animal;
dog.guard();
}
}
注意:
其實(shí)向下轉(zhuǎn)型不常使用,使用它可能會(huì)不小心犯一些錯(cuò)誤。如果我們上述的代碼又要繼續(xù)使用一些其他動(dòng)物的特有方法,如果忘了它們沒(méi)有發(fā)生向上轉(zhuǎn)型,就會(huì)報(bào)錯(cuò)。
為了避免這種錯(cuò)誤: 我們可以使用 instanceof
instanceof:可以判定一個(gè)引用是否是某個(gè)類(lèi)的實(shí)例,如果是則返回 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 的實(shí)例,我們知道它應(yīng)該是 Dog 的實(shí)例,故返回 false
5. 關(guān)鍵字 super
其實(shí)上章就講解過(guò)了 super 關(guān)鍵字,這里我再用一個(gè)表格比較下 this 和 super,方便理解

6. 在構(gòu)造方法中調(diào)用重寫(xiě)方法(坑)
接下來(lái)我們看一段代碼,大家可以猜猜結(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é)果就是

如果沒(méi)猜對(duì)的,一般有兩個(gè)疑惑:
- 沒(méi)有調(diào)用 eat 方法,但為什么結(jié)果是這樣的?
- 為啥是 null?
解答:
疑惑一: 因?yàn)樽宇?lèi)繼承父類(lèi)需要幫父類(lèi)構(gòu)造方法,所以子類(lèi)創(chuàng)建對(duì)象時(shí),就構(gòu)造了父類(lèi)的構(gòu)造方法,就執(zhí)行了父類(lèi)的 eat 方法
疑惑二: 由于父類(lèi)構(gòu)造方法是先執(zhí)行 eat 方法,而 name 的賦值在后面一步,多以此時(shí)的 name 是 null
結(jié)論:
構(gòu)造方法中可以調(diào)用重寫(xiě)的方法,并且發(fā)生了動(dòng)態(tài)綁定
7. 理解多態(tài)
介紹到這里,我們終于要開(kāi)始正式介紹我們今天的一大重點(diǎn)多態(tài)了!那什么是多態(tài)呢?其實(shí)他和繼承一樣是一種思想,我們可以先看一段代碼
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("畫(huà)一個(gè)圓⚪");
}
}
class Rect extends Shape{
@Override
public void draw() {
System.out.println("畫(huà)一個(gè)方片♦");
}
}
class Flower extends Shape{
@Override
public void draw() {
System.out.println("畫(huà)一朵花❀");
}
}
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 這個(gè)方法被調(diào)用者使用時(shí),都是經(jīng)過(guò)父類(lèi)調(diào)用了其中的 draw 方法,并且最終的表現(xiàn)形式是不一樣的。而這種思想就叫做多態(tài)。
更簡(jiǎn)單的說(shuō),多態(tài)就是
一個(gè)引用能表現(xiàn)出多種不同的形態(tài)
而多態(tài)是一種思想,實(shí)現(xiàn)它的前提有兩點(diǎn)
- 向上轉(zhuǎn)型
- 調(diào)用同名的覆蓋方法
而一種思想的傳承總有它獨(dú)到的好處,那么使用多態(tài)有什么好處呢?
1)類(lèi)調(diào)用者對(duì)類(lèi)的使用成本進(jìn)一步降低
- 封裝是讓類(lèi)的調(diào)用者不需要知道類(lèi)的實(shí)現(xiàn)細(xì)節(jié)
- 多態(tài)能讓類(lèi)的調(diào)用者連這個(gè)類(lèi)的類(lèi)型是什么都不必知道,只需要這個(gè)對(duì)象具有某種方法即可
2)能夠降低代碼的“圈復(fù)雜度”,避免使用大量的 if-else 語(yǔ)句
圈復(fù)雜度:
是一種描述一段代碼復(fù)雜程度的方式??梢詫⒁欢未a中條件語(yǔ)句和循環(huán)語(yǔ)句出現(xiàn)的個(gè)數(shù)看作是“圈復(fù)雜度”,這個(gè)個(gè)數(shù)越多,就認(rèn)為理解起來(lái)更復(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)的話,我們一般就會(huì)寫(xiě)出上面這種方法。而使用多態(tài)的話,代碼就會(huì)顯得很簡(jiǎn)單,如
public static void drawShapes() {
// 我們創(chuàng)建了一個(gè) Shape 對(duì)象的數(shù)組.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
我們可以通過(guò)下面這種圖理解上面的代碼

而整體看起來(lái),使用了多態(tài)的代碼就簡(jiǎn)單了很多
3)可擴(kuò)展能力強(qiáng)
如上述畫(huà)圖的代碼,如果我們要新增一種新的形狀,使用多態(tài)的方式改動(dòng)成本也比較低,如
// 增加三角形 class Triangle extends Shape { @Override public void draw() { System.out.println("△"); } }運(yùn)用多態(tài)的話,我們擴(kuò)展的代碼增加一個(gè)新類(lèi)就可以。而對(duì)于不使用多態(tài)的情況,就還需要對(duì)
if-else語(yǔ)句進(jìn)行一定的修改,故改動(dòng)成本會(huì)更高
8. 小結(jié)
到此為止,面向?qū)ο蟮娜筇攸c(diǎn):封裝、繼承、多態(tài)已經(jīng)全部介紹完了。由于我個(gè)人的理解也有限,所以講的可能不好、不足,希望大家多多理解呀。
接下來(lái)將會(huì)介紹抽象類(lèi)和接口,其中也會(huì)進(jìn)一步運(yùn)用到多態(tài),大家可以多多練習(xí),加深思想的理解。
二、抽象類(lèi)
1. 概念
我們上面剛寫(xiě)過(guò)一個(gè)畫(huà)圖型的代碼,其中父類(lèi)的定義是這樣的
class Shape{
public void draw(){
}
}
我們發(fā)現(xiàn),父類(lèi)中的 draw 方法里面沒(méi)有內(nèi)容,而繪圖都是通過(guò)各種子類(lèi)的 draw 方法完成的。
像上述代碼,這種沒(méi)有實(shí)際工作的方法,我們可以通過(guò) abstract 來(lái)設(shè)計(jì)設(shè)計(jì)成一個(gè)抽象方法,而包含抽象方法的類(lèi)就是抽象類(lèi)
設(shè)計(jì)之后的代碼就是這樣的
abstract class Shape{
public abstract void draw();
}
2. 注意事項(xiàng)
方法和類(lèi)都要由 abstract 修飾
抽象類(lèi)中可以定義其他數(shù)據(jù)成員和成員方法,如
abstract class Shape{ public int a; public void b(){ // ... } public abstract void draw(); }但要使用這些成員和方法,需要靠子類(lèi)通過(guò) super 才能使用
- 抽象類(lèi)不可以被實(shí)例化
- 抽象方法不能是被 private 修飾的
- 抽象方法不能是被 final 修飾的,它與 abstract 不能被共存
- 如果子類(lèi)繼承了抽象類(lèi),但不需要重寫(xiě)父類(lèi)的抽象方法,則可以將子類(lèi)用 abstract 修飾,如
abstract class Shape{ public abstract void draw(); } abstract Color extends Shape{ }此時(shí)該子類(lèi)中既可以定義普通方法也可以定義抽象方法
一個(gè)抽象類(lèi) A 可以被另外的抽象類(lèi) B 繼承,但是如果有其他的普通類(lèi)繼承了抽象類(lèi) B,則該普通類(lèi)需要重寫(xiě) A 和 B 中的所有抽象方法
3. 抽象類(lèi)的意義
我們要知道抽象類(lèi)的意義就是為了被繼承
從注意事項(xiàng)中就知道抽象類(lèi)本身是不能被實(shí)例化的,要想使用它,只能創(chuàng)建子類(lèi)去繼承,就比如
abstract class Shape{
public int a;
public void b(){
// ...
}
public abstract void draw();
}
class Cycle extends Shape{
@Override
public void draw(){
System.out.println("畫(huà)一個(gè)⚪");
}
}
public class TestDemo{
public static void main(String[] args){
Shape shape=new Cycle();
}
}
要注意子類(lèi)需要重寫(xiě)父類(lèi)的所有抽象方法,不然代碼就會(huì)報(bào)錯(cuò)
3. 抽象類(lèi)的作用
那么抽象類(lèi)既然不能被實(shí)例化,那為什么要用它呢?
使用了抽象類(lèi)就相當(dāng)于多了一重編譯器的效驗(yàn)
啥意思呢?就比如按照上述畫(huà)圖的代碼,實(shí)際工作其實(shí)是由子類(lèi)完成的,如果不小心誤用了父類(lèi),父類(lèi)不是抽象類(lèi)的話是不會(huì)報(bào)錯(cuò)的,因此將父類(lèi)設(shè)計(jì)成抽象類(lèi),它會(huì)在父類(lèi)被實(shí)例化的時(shí)候報(bào)錯(cuò),讓我們盡早地發(fā)現(xiàn)錯(cuò)誤
三、接口
我們上面介紹了抽象類(lèi),抽象類(lèi)中除了抽象方法還可以包含普通的方法和成員。
而接口中也可包含方法和字段,但只能是抽象方法和靜態(tài)常量。
1. 語(yǔ)法規(guī)則
我們可以將上述 Shape 改寫(xiě)成一個(gè) 接口,代碼如下
interface IShape{
public static void draw();
}
具體的語(yǔ)法規(guī)則如下:
接口是使用
interface定義的接口的命名一般以大寫(xiě)字母
I開(kāi)頭接口中的方法一定是抽象的、被 public 修飾的方法,因此其中抽象方法可以簡(jiǎn)化代碼為
interface IShape{ void draw(); }這樣寫(xiě)默認(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; }接口不能被單獨(dú)實(shí)例化,和抽象類(lèi)一樣需要被子類(lèi)繼承使用,但是接口中使用
implements繼承,如interface IShape{ public static void draw(); } class Cycle implements IShape{ @Override public void draw(){ System.out.println("畫(huà)一個(gè)圓預(yù)⚪"); } }和
extends表達(dá)含義是”擴(kuò)展“不同,implements表達(dá)的是”實(shí)現(xiàn)“,即表示當(dāng)前什么都沒(méi)有,一切需要從頭構(gòu)造基礎(chǔ)接口的類(lèi)需要重寫(xiě)接口中的全部抽象方法
一個(gè)類(lèi)可以使用
implements實(shí)現(xiàn)多個(gè)接口,每個(gè)接口之間使用逗號(hào)分隔開(kāi)就可以,如interface A{ void func1(); } interface B{ void func2(); } class C implements A,B{ @Override public void func1(){ } @Override public void func2{ } }注意這個(gè)類(lèi)要重寫(xiě)所有繼承的接口的所有抽象方法,在 IDEA 中使用 ctrl + i ,快速實(shí)現(xiàn)接口
接口和接口之間的關(guān)系可以使用 extends 來(lái)維護(hù),這是意味著”擴(kuò)展“,即某個(gè)接口擴(kuò)展了其他接口的功能,如
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 開(kāi)始,接口當(dāng)中的方法可以是普通方法,但前提是:這個(gè)方法是由 default 修飾的(即是這個(gè)接口的默認(rèn)方法),如
interface IShape{
void draw();
default public void func(){
System.out.println("默認(rèn)方法");
}
}
2. 實(shí)現(xiàn)多個(gè)接口
我們之前介紹過(guò),Java 中的繼承是單繼承,即一個(gè)類(lèi)只能繼承一個(gè)父類(lèi)
但是可以同時(shí)實(shí)現(xiàn)多個(gè)接口,故我們可以通過(guò)多接口去達(dá)到多繼承類(lèi)似的效果
接下來(lái)通過(guò)代碼來(lái)理解吧!
class Animal{
public String name;
public Animal(String name){
this.name=name;
}
}
class Bird extends Animal{
public Bird(String name){
super(name);
}
}
此時(shí)子類(lèi) Bird 繼承了父類(lèi) Animal,但是不能再繼承其他類(lèi)了,但是還可以繼續(xù)實(shí)現(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)于實(shí)現(xiàn)了多繼承,因此接口的出現(xiàn)很好的解決了 Java 單繼承的問(wèn)題
并且我們可以感受到,接口表達(dá)的好像是具有了某種屬性,因此有了接口以后,類(lèi)的使用者就不必關(guān)注具體的類(lèi)型了,而只要關(guān)注該類(lèi)是否具備某個(gè)能力,比如
public class TestDemo {
public static void fly(IFly flying){
flying.fly();
}
public static void main(String[] args) {
IFly iFly=new Bird("飛鳥(niǎo)");
fly(iFly);
}
}
因?yàn)轱w鳥(niǎo)本身具有飛的屬性,所以我們不必關(guān)注具體的類(lèi)型,因?yàn)橹灰獣?huì)飛的都可以實(shí)現(xiàn)飛的屬性,如超人也會(huì)飛,就可以定義一個(gè)超人的類(lèi)
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());
}
}
注意:
子類(lèi)先繼承父類(lèi)再實(shí)現(xiàn)接口
3. 接口的繼承
語(yǔ)法規(guī)則里面就介紹了,接口和接口之間可以使用 extends 來(lái)維護(hù),可以使某個(gè)接口擴(kuò)展其他接口的功能
這里就不再重述了
下面我們?cè)賹W(xué)習(xí)一些接口,來(lái)加深對(duì)于接口的理解
4. Comparable 接口
我們之前介紹過(guò) Arrays 類(lèi)中的 sort 方法,它可以幫我們進(jìn)行排序,比如
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));
}
}
而接下來(lái)我想要對(duì)一個(gè)學(xué)生的屬性進(jìn)行排序。
首先我實(shí)現(xiàn)一個(gè) Student 類(lèi),并對(duì) toString 方法進(jìn)行了重寫(xiě)
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 +
'}';
}
}
接下來(lái)我寫(xiě)了一個(gè)數(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);
}
}
那么我們可以直接通過(guò) sort 函數(shù)進(jìn)行排序嗎?我們先寫(xiě)如下代碼
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é)果卻是

我們來(lái)分析一下
ClassCastException:類(lèi)型轉(zhuǎn)換異常,說(shuō) Student 不能被轉(zhuǎn)換為
java.lang.Comparable這是什么意思呢?我們思考由于 Student 是我們自定義的類(lèi)型,里面包含了多個(gè)類(lèi)型,那么 sort 方法怎么對(duì)它進(jìn)行排序呢?好像沒(méi)有一個(gè)依據(jù)。
此時(shí)我通過(guò)報(bào)錯(cuò)找到了
Comparable
可以知道這個(gè)應(yīng)該是一個(gè)接口,那我們就可以嘗試將我們的 Student 類(lèi)繼承這個(gè)接口,其中后面的 < T > 其實(shí)是泛型的意思,這里改成 < 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 + '}'; } }但此時(shí)還不行,因?yàn)槔^承需要重寫(xiě)接口的抽象方法,所以經(jīng)過(guò)查找,我們找到了
增加的重寫(xiě)方法就是
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 }這里應(yīng)該就是比較規(guī)則的設(shè)定地方了,我們?cè)倏纯?sort 方法中的交換
也就是說(shuō)如果此時(shí)的左值大于右值,則進(jìn)行交換
那么如果我想對(duì)學(xué)生的年齡進(jìn)行排序,重寫(xiě)后的方法應(yīng)該就是
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return this.age-o.age; }此時(shí)再運(yùn)行代碼,結(jié)果就是
而到這里我們可以更深刻的感受到,接口其實(shí)就是某種屬性或者能力,而上述 Student 這個(gè)類(lèi)繼承了這個(gè)比較的接口,就擁有了比較的能力
缺點(diǎn):
當(dāng)我們比較上述代碼的姓名時(shí),就要將重寫(xiě)的方法改為
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return this.name.compareTo(o.name); }當(dāng)我們比較上述代碼的分?jǐn)?shù)時(shí),就要將重寫(xiě)的方法改為
@Override public int compareTo(Student o) { // 新的比較的規(guī)則 return int(this.score-o.score); }我們發(fā)現(xiàn)當(dāng)我們要修改比較的東西時(shí),就可能要重新修改重寫(xiě)的方法。這個(gè)局限性就比較大
為了解決這個(gè)缺陷,就出現(xiàn)了下面的接口 Comparator
4. Comparator 接口
我們進(jìn)入 sort 方法的定義中還可以看到一個(gè)比較方法,其中有兩個(gè)參數(shù)數(shù)組與 Comparator 的對(duì)象

這里就用到了 Comparator 接口
這個(gè)接口啥嘞?我們可以先定義一個(gè)年齡比較類(lèi) AgeComparator,就是專(zhuān)門(mén)用來(lái)比較年齡,并讓他繼承這個(gè)類(lèi)
class AgeCompartor implements Comparator<Student>{
}
再通過(guò)按住 ctrl 并點(diǎn)擊它,我們可以跳轉(zhuǎn)到它的定義,此時(shí)我們可以發(fā)現(xiàn)它里面有一個(gè)方法是

這個(gè)與上述 Comparable 中的 compareTo 不同,那我先對(duì)它進(jìn)行重寫(xiě)
class AgeCompartor implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age-o2.age;
}
}
我們?cè)侔凑?sort 方法的描述,寫(xiě)如下代碼
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));
}
}
這樣就可以正常的對(duì)學(xué)生的年齡進(jìn)行比較了,而此時(shí)我們要再對(duì)姓名進(jìn)行排序,我們就可以創(chuàng)建一個(gè)姓名比較類(lèi) 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 這個(gè)接口比 Comparable 的局限性小很多,我們?nèi)绻獙?duì)某個(gè)屬性進(jìn)行比較只要增加它的比較器即可
5. Cloneable 接口和深拷貝
首先我們可以看這樣的代碼
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)該就是搞一個(gè)副本出來(lái),比如

那么既然這次講 Cloneable 接口,我就對(duì)其進(jìn)行繼承唄!
class Person implements Cloneable{
public String name ="LiXiaobo";
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
但是我們發(fā)現(xiàn)就算繼承之后,我們也不能通過(guò)創(chuàng)建的引用去找到一個(gè)克隆的方法。此時(shí)我們可以點(diǎn)到 Cloneable的定義看看

太牛了,啥都沒(méi)有!
- 我們發(fā)現(xiàn),
Cloneable這個(gè)接口是一個(gè)空接口(也叫標(biāo)記接口),而這個(gè)接口的作用就是:如果一個(gè)類(lèi)實(shí)現(xiàn)了這個(gè)接口,就證明它是可以被克隆的 - 而在使用它之前,我們還需要重寫(xiě)
Object的克隆方法(所有的類(lèi)默認(rèn)繼承于 Object 類(lèi))
怎樣重寫(xiě)克隆方法呢?通過(guò) ctrl + o,就可以看到

再選擇 clone 就🆗,重寫(xiě)后的代碼就變成
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();
}
}
而此時(shí)我們就可以看到一個(gè) clone 方法

點(diǎn)擊之后,我們發(fā)現(xiàn)居然還是報(bào)錯(cuò)

原因是由于重寫(xiě)的 clone 方法會(huì)拋出異常,針對(duì)這個(gè)就有兩種方式,今天介紹簡(jiǎn)單一點(diǎn)的方式
方式一: 將鼠標(biāo)放到 clone 上,按住 Alt + enter,你就會(huì)看到
點(diǎn)擊紅框框就行,但是你會(huì)發(fā)現(xiàn)點(diǎn)擊后還是報(bào)錯(cuò),這是由于重寫(xiě)的方法的返回值是 Object,而編譯器會(huì)認(rèn)為這是不安全的,因此將它強(qiáng)制轉(zhuǎn)換成 Person 就可以了。此時(shí)我們?cè)賹⒖寺〉母北据敵霭l(fā)現(xiàn)結(jié)果沒(méi)問(wèn)題
并且通過(guò)地址的打印,副本和原來(lái)的地址是不一樣的
介紹到這里,簡(jiǎn)單的克隆流程就已經(jīng)介紹完了。但是接下來(lái)我們?cè)偕钊胍稽c(diǎn)思考,在 Person 類(lèi)原有代碼的基礎(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();
}
}
此時(shí)我們?cè)偻ㄟ^(guò) 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 就完全是一個(gè)副本,對(duì)它進(jìn)行修改是與 person 無(wú)關(guān)的。
但是我們?cè)倏聪旅孢@種情況,我們定義一個(gè) Money 類(lèi),并在 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();
}
}
然后我們?cè)傩薷?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 的對(duì)象,所以只克隆了(0x123)的 money,而(0x456)的 money 沒(méi)有被克隆,所以就算前面的 money 被克隆的副本也指向它,所以改變副本的 money,它也會(huì)被改變
而上述這種情況其實(shí)叫做淺拷貝,那么怎么將其變成深拷貝呢?
我們只要將 money 引用所指向的對(duì)象也克隆一份
步驟:
將 Money 類(lèi)也實(shí)現(xiàn) Cloneable 接口,并重寫(xiě)克隆方法
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; }
到此這篇關(guān)于Java語(yǔ)法之 Java 的多態(tài)、抽象類(lèi)和接口的文章就介紹到這了,更多相關(guān) Java 的多態(tài)、抽象類(lèi)和接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java中使用Filter控制用戶登錄權(quán)限具體實(shí)例
java中使用Filter控制用戶登錄權(quán)限具體實(shí)例,需要的朋友可以參考一下2013-06-06
Java中LinkedHashMap的實(shí)現(xiàn)詳解
LinkedHashMap是Java中的一個(gè)Map容器,它繼承自HashMap,并且還可以對(duì)元素進(jìn)行有序存儲(chǔ),本文將介紹LinkedHashMap的實(shí)現(xiàn)原理以及使用方法,并且提供相應(yīng)的測(cè)試用例和全文小結(jié),需要的可以參考下2023-09-09
Spring Batch遠(yuǎn)程分區(qū)的本地Jar包模式的代碼詳解
這篇文章主要介紹了Spring Batch遠(yuǎn)程分區(qū)的本地Jar包模式,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09







