學完繼承、學完多態(tài),但面對洶涌而來??的接口,相信很多同學都不知所措,因此我耗費幾天幾夜的時間,搜尋大量書籍資料,苦心閉關鉆研,寫出了一篇關于Java的接口從入門小白到精通大佬的學習之路,相信這篇文章一定對您有所幫助??
??接口的基本概念引入
Java接口是一系列方法的聲明,是一些方法特征的集合
- 對于接口,它是
Java中一個新增的知識點
,而C++中沒有,因為Java有一個缺陷就是不可以實現多繼承,只可以單繼承,這就限制了有些功能的使用,于是為了讓Java也能有這種能力,因為提出了接口的概念 - 對于接口的基本概念,大家應該要回想一下我們上一文所講的abstract抽象類的概念,因為接口它與抽象類非常類似,在抽象類中我們可以了解到其實除了不可以實現一些抽象方法外,其余的和正常的類沒有什么本質的區(qū)別,一些常量、變量,私有、靜態(tài)方法都可以定義,
但是在接口中,就只能有抽象方法以及常量
,而且接口中所有抽象方法的訪問權限都是public公開的,因為它也算是static靜態(tài)方法,所以可以省略public和static這兩個關鍵字 - 但是從JDK8開始,就有了一些改變,接口中也可以定義default和private關鍵字修飾的方法,對于default關鍵字修飾的方法,不可以將此關鍵字省略,因為在接口體中不允許定義通常的帶方法體的public實例方法;而對于private修飾的方法,則是配合default默認方法進行使用,即將某些算法封裝在private方法中,供接口中的實例方法調用
??如何去定義和實現一個接口
了解了接口的基本概念之后,是不是很想知道一個接口時怎么去定義和實現呢,讓我們馬上來看一看吧
??【interface關鍵字】
- 首先最基本的定義一個接口,對于接口,如果你覺得有一些方法它們有一個功能的類名,那你就可以把它定義為一個接口,在這個接口中去定義一些抽象方法,比方是大家都會運動,運動的方式有很多,比方說跑步、游泳等等,但是你做這些事情的方式的方式和節(jié)奏和人家專業(yè)運動員又不一樣,所以可以由不同的類去繼承這個接口,然后去實現具體的對應的功能
public interface Sport {
void run(); //跑步
void swim(); //游泳
}
- 這和類的定義很相似,細心的小伙伴可以看出來這是將
class關鍵字換成inferface關鍵字
,但是變得可不止這一種哦,接口可是有它的專屬圖表的??
IDEA??
eclipse??
- 這兩個Java編譯器應該是大家用的最用的了,對于接口,細心的小伙伴應該可以發(fā)現,存在一個【大寫的I】,這個標志就是【interface】的首字母大寫了,相信這點很多人都沒有發(fā)現吧
??【implements關鍵字】
- 好,說完如何入定義一個接口,接下去就來講講怎么去實現一個接口吧
- 那就是用【implements】這個關鍵字,通過一個具體的類去實現
public class People implements Sport{
}
- 但是這樣的話就會出現報錯,這個我們在繼承抽象類的時候就有說過,繼承一個抽象類,就要去重寫其所有的抽象方法
public class People implements Sport{
@Override
public void run() {
System.out.println("我會慢跑???");
}
@Override
public void swim() {
System.out.println("我會自由泳???");
}
}
【注意事項】
- 對于抽象類的話,如果你用一個抽象類去繼承,那么你就不用重寫這個抽象方法,當然對于接口也是一樣
- 如果一個非abstract類實現了某個接口,那么這個類就必須重寫該接口的所有抽象方法
- 如果一個abstract類實現了某個接口,那么這個類可以選擇重寫接口中的抽象方法或者該接口的抽象方法
??接口特點及作用
了解了接口的定義和實現之后,接下來我們來說一說接口有哪些特點以及其具體的作用
??接口的特點
-
接口雖與抽象類相似,但是比抽象類更加抽象,卻不需要寫abstract關鍵字,因為接口中所有方法都是抽象的,因此可以省略這個關鍵字
-
接口中只可以有常量,而且都是
public、static、final關鍵字
修飾的【默認都有,可以不加】,但是不可以有變量 -
接口沒有構造方法,因此不可以用new關鍵字去創(chuàng)建接口的對象,而是要用一個具體的類去
implements實現
這個接口 -
一個類可以實現多個接口,一個接口可以繼承多個接口【有點抽象,上代碼】
public interface Sport {
void run();
void competition();
}
public interface Law {
void rule();
}
public class PingPongMan implements Sport,Law{
private String name;
public PingPongMan(String name) {
this.name = name;
}
@Override
public void rule() {
System.out.println(name + "必須遵紀守法");
}
@Override
public void run() {
System.out.println(name + "必須參加訓練");
}
@Override
public void competition() {
System.out.println(name + "必須參加比賽");
}
}
public class Test {
public static void main(String[] args) {
PingPongMan p = new PingPongMan("張繼科");
p.run();
p.competition();
p.rule();
}
}
- 上述代碼實現的是一個類實現多個接口,這個類不是一個抽象類,那就要重寫實現接口的中的所有抽象方法
public interface Law {
void rule();
}
public interface People {
void sleep();
void eat();
}
public interface Sport extends Law,People{
void run();
void competition();
}
- 好,具體測試不給出了,主要是看這個接口可以繼承多個接口,可以看出Sport接口使用了extends關鍵字繼承類Law和People這兩個接口
以上的這兩個特點很重要,也是彌補了Java類不能多繼承的缺陷
- 一個類,它可以在繼承父類的情況下同時實現接口
class Dog extends Animal implements Runnging,Drinking{
}
??接口的作用和意義
有些剛剛接觸Java接口的小伙伴就很疑惑,這個接口到底是用來干嘛的呢,它究竟有什么具體的作用
首先我們來總的概括一下,其實就是一句話:【定義一個公共規(guī)范,實現一個統(tǒng)一訪問】
- 對于公共規(guī)范這個概念,就是大家都認可的,是一種標準,就像是USB(通用串行總線)一樣,這個接口是很多家公司聯合提出的,因此屬于一個規(guī)范,在日常生活中,我們使用的很多設備都擁有USB接口,這個USB接口呢,其實就很像Java中所說的接口;
- 如果有一個物件,比如說筆記本,擁有這個接口,也相當于是實現了這個接口,那就說明筆記本這個類有了USB這個功能,外部設備便可以與它產生一個聯系,比如說最常見的U盤,只要是有USB接口的地方,那么這個U盤都可以使用,這么說大家應該有點清楚了吧,下面會更加詳細地深入了解接口
- 對于統(tǒng)一訪問,舉個例子,對于LOL這款游戲大家應該都玩過,一個英雄,是不是一定會有相同的功能,比如說
攻擊、點塔、補刀
這些,但是LOL中157個英雄,假設它們都對應一個類,難道在每個英雄類中都去寫這三個功能嗎,那一定不會,這是就可以定義一個基本英雄功能接口,里面封裝了所有英雄所具備的基本能力,然后所有英雄類都去訪問這個接口就可以
??接口的UML圖(類圖)
初步入門了接口后,接下去我們就要了解接口與它的實現類之間所存在的邏輯框架關系,也就是類圖,這可以進一步幫助我們去理解接口
??UML圖的基本概念
統(tǒng)一建模語言(Unified Modeling Language,UML)是用來設計軟件的可視化建模語言。可以幫助我們簡單、統(tǒng)一、圖形化、能表達軟件設計中的動態(tài)與靜態(tài)信息
??UML圖的作用
- 可以幫助我們清晰勾勒出一個類族的框架接口,繼而對此項目整體邏輯接口更加了解
- UML圖是系統(tǒng)分析和設計階段的重要產物,是系統(tǒng)編碼和測試的重要模型
??接口UML圖的基本結構和組成
- 對于接口的UML圖與類的UML圖很類似,主要是使用一個長方形去描述一個類或接口,將這個長方形垂直地分為3層
- 第一層是名字層,接口的字形必須是斜體字形,而且需要用<>修飾名字,格式為【接口修飾符\n接口名稱】
- 第二層是常量層,列出接口中的常量及類型,格式為【常量名字:類型】
- 第三層是方法層,也稱操作層,列出接口中的方法及返回類型,格式為【方法名字(參數列表):類型】
??繼承關系類圖與接口圖的區(qū)別
- 子類與父類的繼承關系所呈現的UML類圖
- 接口與實現類所呈現的UML類圖
- 相信通過這兩張UML圖的對比分析,你對UML類圖也有了一個基本的見解了
??接口回調與多態(tài)的聯系
在前面將多態(tài)的時候,講到上轉型對象時我有提到過接口回調這個東西,這在接口中是比較重要的,因此做一個區(qū)分
??權威解釋
- 對于向上轉型,就是父類引用去引用子類對象
- 而對于接口回調,就是把實現某一接口的類創(chuàng)建的對象的引用賦值給該接口【聲明的接口變量】,那么該接口就可以調用被類實現的接口方法以及接口提供的default方法
- 對于它們二者的區(qū)別,還要說到使用接口的核心原因:為了能夠向上轉型為多個基類型。即利用接口的多實現,可向上轉型為多個接口基類型,從實現了某接口的對象,得到對此接口的引用,與向上轉型為這個對象的基類,實質上效果是一樣的
- 所以對于接口回調,強調使用接口來實現回調對象方法使用權的功能;對于向上轉型,則牽涉到多態(tài)和運行期綁定的范疇
以上解釋來自《Tinking in Java》這本書
??具體案例分析
說了這么多概念,您對接口回調一定還沒有一個很清晰的認識,接下去我們通過一個小案例一起來看看
public interface ShowMessage {
void show(String s);
}
public class TV implements ShowMessage {
@Override
public void show(String s) {
System.out.println("tvtvtvtvtv");
System.out.println(s);
System.out.println("tvtvtvtvtv");
}
}
public class PC implements ShowMessage {
@Override
public void show(String s) {
System.out.println("pcpcpcpcpc");
System.out.println(s);
System.out.println("pcpcpcpcpc");
}
}
public class test {
public static void main(String[] args) {
ShowMessage sm;
sm = new TV();
sm.show("TCL電視機打開了");
System.out.println("-----------");
sm = new PC();
sm.show("Lenovo臺式機打開了");
}
}
- 從這個小案例可以看出,對于接口回調,就是將一個實現接口的類所定義的對象的引用給到一個接口所聲明的變量,然后上面說了,可以實現回調對象方法使用權的功能,也就是去調用子類重寫接口中抽象方法
??函數接口與Lambda表達式
講完了接口回調,我們再來說一下函數接口與Lambda表達式
??Lambda表達式
- 這個Lambda表達式的話也是JDK8新出的,當時出個這個概念的時候備受爭議,因為這簡直顛覆了大家的想象,都說居然可以這么去優(yōu)化一個表達式,對此表示非常地驚奇??
- 接著就讓我們先來了解一下這個Lambda表達式,了解一下它有什么優(yōu)缺點
Lambda表達式,也可稱為閉包。類似于JavaScript中的閉包,它是推動Java8發(fā)布的最重要的新特性
- 優(yōu)點
1、代碼更加簡潔
2、減少匿名內部類的創(chuàng)建,節(jié)省資源?
3、使用時不用去記憶所使用的接口和抽象函數
- 缺點
1、若不用并行計算,很多時候計算速度沒有比傳統(tǒng)的 for 循環(huán)快。(并行計算有時需要預熱才顯示出效率優(yōu)勢)
2、不容易調試。
3、若其他程序員沒有學過 lambda 表達式,代碼不容易讓其他語言的程序員看懂?
- 上面提到了一個叫做匿名內部類,這個我還沒講到,放在下一篇文章,大家可以先去了解一下,匿名內部類,這是內部類的一種
??函數式接口
然后我們再來了解一下函數式接口
- 首先必須是接口、其次接口中有且僅有一個抽象方法的形式?
- 通常我們會在接口上加上一個@FunctionalInterface注解,標記該接口必須是滿足函數式接口?
定義方法如下
@FunctionalInterface //一旦加上這個注解必須是函數式接口,里面只能有一個抽象方法
interface Swimming{
void swim();
//void run();
}
- 首先根據上面這個接口,我們去實現一個匿名內部類
public class LambdaDemo1 {
//Lambda表達式只能簡化函數式接口的匿名內部類的寫法形式
//Lambda表達式只能簡化接口中只有一個抽象方法的匿名內部類形式
public static void main(String[] args) {
//實現了Swimming這個接口
Swimming s1 = new Swimming() {
@Override
public void swim() {
System.out.println("老師游泳賊溜");
}
};
go(s1);
go(new Swimming() {
@Override
public void swim() {
System.out.println("學生游泳很開心");
}
});
}
public static void go(Swimming s){
System.out.println("開始。。。");
s.swim();
System.out.println("結束。。。");
}
}
對于如何去進行一個簡化,我們需要先了解其規(guī)則
??簡化規(guī)則定義
- 參數類型可以省略不寫
- 如果只有一個參數,參數類型可以省略,同時()也可以省略
- 如果Lambda表達式的方法體代碼只有一行代碼??梢允÷源罄ㄌ柌粚?,同時要省略分號!
- 如果Lambda表達式的方法體代碼只有一行代碼。可以省略大括號不寫。此時,如果這行代碼是return語句,必須省略return不寫,同時也省略“;”不寫
然后我們就對上面的代碼進行一個簡化
Swimming s1 = new Swimming() {
// Swimming s1 = () ->{ //簡化版
// System.out.println("老師游泳賊溜");
// };
Swimming s1 = () -> System.out.println("老師游泳賊溜"); //最終版
go(s1);
到這里大家可能還是沒有看懂,那我們再來多看幾個,就能懂了
public class LambdaDemo2 {
public static void main(String[] args) {
Integer[] ages = {66,99,33,78,12};
//Arrays.sort(ages); 默認升序
Arrays.sort(ages, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; //降序
}
});
// Arrays.sort(ages, (Integer o1, Integer o2) ->{
// return o2 - o1; //降序
// }
// );
// Arrays.sort(ages, (o1, o2) ->{
// return o2 - o1; //降序
// }
// );
Arrays.sort(ages, (o1, o2) -> o2 - o1);
System.out.println("排序后的內容為:" + Arrays.toString(ages));
}
}
- 好,我們來詳細地說明一下,對于Arrays這個API的數組排序,相信大家用的是最多的,默認是升序,這里是進行了一個重寫然后令其可以實現降序輸出
- 首先,應該去掉的就是,因為我們只需要這個匿名內部類的形參列表和方法體代碼,然后要加上一個【—>】箭頭,要注意,這個箭頭可不是C/C++里面的指針
new Comparator() {
@Override
public int 這段代碼 - 然后根據第一條規(guī)則可以知道, 參數類型可以省略不寫,所以只留下(o1, o2)
- 接著就是省略這個reutrn和語句后面的分號“;”
- 最終的簡化結果就是Arrays.sort(ages, (o1, o2) -> o2 - o1);
再來一個有關按鈕監(jiān)聽事件ActionListener的匿名內部類形式簡化
//Lambda表達式簡化按鈕監(jiān)聽器ActionListener的匿名內部類形式
public class LambdaDemo3 {
public static void main(String[] args) {
JButton btn = new JButton("登錄");
//給登錄按鈕綁定點擊時間監(jiān)聽器
// btn.addActionListener(new ActionListener() {
// @Override
// public void actionPerformed(ActionEvent e) {
// System.out.println("登錄一下~~~");
// }
// });
//
// btn.addActionListener((ActionEvent e) ->{
// System.out.println("登錄一下~~~");
// }
// );
//
// btn.addActionListener((e) ->{
// System.out.println("登錄一下~~~");
// }
// );
btn.addActionListener( e -> System.out.println("登錄一下~~~"));
}
}
- 首先是一樣,省略這一段代碼
new ActionListener() {
@Override
public void actionPerformed - 接著是省略形參值ActionEvent
- 最根據第二條規(guī)則,如果只有一個參數,參數類型可以省略,同時()也可以省略,省略e外的小括號
- 最終形式便是btn.addActionListener( e -> System.out.println(“登錄一下~~~”));
好,又看了兩個小案例,這些您對Lambda表達式簡化匿名內部類有了一個基本的認識了,接下來我們說一些小貼士
??細心小貼士
- 如果大家細心的話,對于有些方法,按住ctrl鍵鼠標點進去,就可以看到這是一個函數式接口,如果看到了【@FunctionalInterface】注解,那就表明這個匿名內部類可以使用Lambda表達式來簡化,點進我們剛才那個sort()排序的Comparator接口,就可以看到這個注解
??深入理解接口【面向接口的思維】
了解接口后,我們要開始第二層次,也就是理解接口,首先就是要進行思考,提出相應的問題
?提問一:為什么不在一個類中直接實現相應的方法,而是要先進行接口抽象呢?
- 答:這樣會造成代碼冗余,眾多類中都有相同的一個功能,只是調用的對象不同,繼而產生內存浪費。這時就可以使用接口去封裝這個功能,通過父類引用去接收子類對象,從而實現多態(tài)
?提問二:接口的真正用處在哪里,用接口可以幫助我們實現什么?
- 答:對于一個接口,上面在講概念的時候有提到一些,就類似于一個功能庫一般,在這個功能庫中呢,你可以定義許多別人可能會用的到的功能,這樣當別人有需要時,便無需去繼承一個抽象類導致類族群混亂,或者自己重寫定義一個方法導致增加內存。完全可以把大家都會用得到的功能,并且可以實現多態(tài)的功能放入此方法庫,在定義這個方法的時候完全無需去考慮它是如何實現的,只需要定義好其標準以及參數的設定
- 因此可以看出,在一個項目開發(fā)時,擁有一些實用的接口是多么重要,既能有一個統(tǒng)一的規(guī)范、有一個嚴格的標準,而且還可以提高開發(fā)的效率,減少類族的復雜性,上面也講到過,接口其實很好地彌補了Java無法多繼承這個缺陷,當你繼承了一個父類,但是又不想再添加一個祖先父類的抽象類,將類族混亂。接口就是一個很好的選擇,可以幫助我們實現想要實現的功能
?提問三:接口與抽象類如此地相似,為什么有的時候要使用接口而不用抽象類呢?
- 答:對于抽象類,它可以讓自己的引用去接收子類對象;對于接口,它可以讓自己聲明的變量,一樣去接收子類對象,它們都可以在獲取到子類對象后調用子類重寫的抽象方法,繼而實現多態(tài)。
- 但是對于抽象類,它始終都是一個類,是需要被繼承才能讓子類去重寫自己所擁有的抽象方法,但是當一些子類繼承了一些父類擁有但是自己卻不需要的功能時,這時候就會出問題,造成內存浪費。如果當我們僅僅是為了實現多態(tài),而且又是很多類都需要這個功能,卻不想要去繼承一個父類獲取這個方法,就可以將此方法寫入接口,通過【implements】這個關鍵字去實現這個接口,繼而在類內重寫這個抽象方法來實現多態(tài)
??接口的實戰(zhàn)項目演練
在本小節(jié)中,我會設置三個實戰(zhàn)項目,從淺入深,層層遞進,幫助大家來更好地理解接口在實際的應用中到底是如何去使用的
“Hello World”【?】
看到這個標題,你不會真的以為只是輸出【Helllo World】吧,那是不會的,只是這個案例作為第一個,比較簡易一些,幫助大家來回顧上面所學的知識點
上代碼
public interface SpeakHello {
public void speak();
}
public class CPeople implements SpeakHello{
@Override
public void speak() {
System.out.println("你好");
}
}
public class EPeople implements SpeakHello{
@Override
public void speak() {
System.out.println("Hello");
}
}
public class Hello {
public void lookHello(SpeakHello speakHello){
speakHello.speak(); //實現接口回調
}
}
public class test {
public static void main(String[] args) {
Hello hello = new Hello();
hello.lookHello(new CPeople());
hello.lookHello(new EPeople());
hello.lookHello(new SpeakHello() {
@Override //匿名內部類
public void speak() {
System.out.println("Hello World");
}
});
//簡化版
hello.lookHello(() -> System.out.println("H W")); //Lambda表達式
}
}
- 第一個實戰(zhàn)項目,作為我們對于上述所講的接口的基本定義和實現以及接口回調、Lambda表達式做一個總結
- 首先,說話是一個能力,將其封裝成接口的形式,無論是中國人還是英國人都會說話,因此都去實現了這個接口
- 然后通過一個普通類去搭建接口和實現類之間的關系,將接口所聲明的變量作為形參,在主方法中通過調用這個類的方法傳入實現類的對象去實現一個接口的回調,繼而去展現出多態(tài)
- 然后下面,我又使用了匿名內部類的形式去直接重寫了這個接口中的抽象方法,從運行結果可以看出,也能呈現出同樣的效果
- 最后一種,則是對匿名內部類做一個簡化操作,這就要使用到我們上面所提到的Lambda表達式了,具體規(guī)則不細講,如果有點遺忘的話可以再翻上去看看
新能源時代【???】
很夸張的一個標題,突然腦洞打開想到的,哈哈,和項目也是有一些聯系??
上代碼
public abstract class MotorVehicles {
abstract void brake(); //剎車功能
}
public interface ControlTemperature { //控制溫度
void controlAirTemperature();
}
public interface MoneyFare { //收取費用
void charge();
}
public class Bus extends MotorVehicles implements MoneyFare{
@Override
void brake() {
System.out.println("公交車正在剎車");
}
@Override
public void charge() {
System.out.println("公交車正在計費");
}
}
public class Taxi extends MotorVehicles implements MoneyFare,ControlTemperature{
@Override
void brake() {
System.out.println("出租車正在剎車");
}
@Override
public void charge() {
System.out.println("出租車正在計費");
}
@Override
public void controlAirTemperature() {
System.out.println("出租車正在調節(jié)空調溫度");
}
}
public class Cinema implements MoneyFare,ControlTemperature{
@Override
public void charge() {
System.out.println("電影院開始收費");
}
@Override
public void controlAirTemperature() {
System.out.println("電影院正在調節(jié)室內溫度");
}
}
public class test {
public static void main(String[] args) {
//1.三個類定義對象
Bus bus = new Bus();
Taxi taxi = new Taxi();
Cinema cinema = new Cinema();
//2.兩個接口聲明對象
MoneyFare moneyFare;
ControlTemperature controlTemperature;
moneyFare = bus;
bus.brake();
moneyFare.charge();
System.out.println("--------");
controlTemperature = taxi;
moneyFare = taxi;
taxi.brake();
moneyFare.charge();
controlTemperature.controlAirTemperature();
System.out.println("--------");
moneyFare = cinema;
controlTemperature = cinema;
moneyFare.charge();
controlTemperature.controlAirTemperature();
//上述操作有體現接口回調
}
}
- 這個項目,很明顯比上一個小項目稍微復雜了一些,進一步地帶大家理解接口,區(qū)分接口與抽象類之間的關系,明白接口為什么可以彌補多繼承的缺口
- 首先定義了一個機動車抽象類,里面含有一個剎車方法,對于公交車、出租車都是屬于機動車,所以應該讓這兩個類去繼承機動車這個父類,而且他們都可以有【剎車】這一個功能,但是對于電影院類,則不屬于機動車,只是一個獨立與機動車類族的一個單獨類,但是對于【控制溫度】和【收取費用】這兩個功能電影院也是需要有,可是呢,電影院不會剎車呀,比如放一些愛情片你說怎么剎車,就這么放下去了,管你下面有沒有小孩(??)
- 難道為此再去定義一個抽象類例如多功能電影院嗎,這如果實在我們這種小項目是沒關系,但如果這個場景放在實際企業(yè)開發(fā)中,新定義一個抽象類這種行為是具有風險性的,因為隨著抽象類的增加,就需要重新構建上層類族之間的關系,就會使得整個項目的框架邏輯變得更加復雜,這種想到增加功能就去多寫一個抽象類的行為是不可取的,
- 因此我們就想到了接口這個東西,我們可以將收取費用和控制空調溫度封裝到一個功能接口中,或者分開定義也可以。這樣的話,影院就不需要去繼承機動車類,而是只需要實現這兩個接口即可
- 對于主方法中的一些操作,我都清晰地將每個類分割開來,通過接口所聲明的變量去接收實現類的對象,繼而去呈現一個接口的回調,從運行結果也可以很清晰地看出
瘋狂農場【?????】
首先聲明,這個項目不是我的,是從一位大佬那里拿來的,我將其重新實現做一個講解,這是他的博客鏈接,大家可以去看看
上代碼
public abstract class Animal {
public abstract String getName();
public abstract void move(String destination);
public abstract void drink();
}
public abstract class Mammal extends Animal{
}
public abstract class Reptile extends Animal{
}
public interface Huntable <T>{ //泛型接口
//不僅是動物可以捕獵,其他生物也是可以捕獵(增加了廣泛性)
void hunt(T o);
public default int max(int a,int b){
return a > b ? a : b;
}
}
public class Tiger extends Mammal implements Huntable<Animal>{
private String name = "Tiger";
@Override
public String getName() {
return this.name;
}
@Override
public void move(String destination) {
System.out.println(name + " move to the " + destination);
}
@Override
public void drink() {
System.out.println("Tiger lower it's head and drinks");
}
@Override
public void hunt(Animal animal) {
System.out.println("Tiger catched the " + animal.getName() + " and eat it");
}
}
public class Goat extends Mammal{
private String name = "Goat";
@Override
public String getName() {
return this.name;
}
@Override
public void move(String destination) {
System.out.println(name + " move to the " + destination);
}
@Override
public void drink() {
System.out.println("Goat lower it's head and drinks");
}
}
public class Rabbit extends Mammal{
private String name = "Rabbit";
@Override
public String getName() {
return this.name;
}
@Override
public void move(String destination) {
System.out.println(name + " move to the " + destination);
}
@Override
public void drink() {
System.out.println("Rabbit put out it's tongue and drinks");
}
}
public class Snake extends Reptile implements Huntable<Animal>{
private String name = "Snake";
@Override
public String getName() {
return this.name;
}
@Override
public void move(String destination) {
System.out.println(name + " move to the" + destination);
}
@Override
public void drink() {
System.out.println(name + "dived into water and drinks");
}
@Override
public void hunt(Animal animal) {
System.out.println("Snake catched the " + animal.getName() + " and eat it");
}
}
public class Farmer {
public void BringWater(String desination)
{
System.out.println("Farmer bring the water to the " + desination);
}
public void FeefWater(Animal animal)
{
BringWater("Farm");
animal.move("Farm");
animal.drink();
System.out.println("----------");
}
public void work()
{
Farmer farmer = new Farmer();
Tiger tiger = new Tiger();
Goat goat = new Goat();
Rabbit rabbit = new Rabbit();
farmer.FeefWater(tiger);
farmer.FeefWater(goat);
farmer.FeefWater(rabbit);
}
public void BringAnimal(Animal animal,String desination)
{
System.out.println("Farmer bring the " + animal.getName() + " to the " + desination);
}
// public void FeedAnimal(Animal hunter,Animal prey)
// {
// BringAnimal(prey,"Farm");
// hunter.move("Farm");
// Huntable huntable = (Huntable) hunter;
// huntable.hunt(prey);
// System.out.println("----------");
// }
public void FeedAnimal(Huntable huntable,Animal prey)
{
BringAnimal(prey,"Farm");
Animal animal = (Animal) huntable;
animal.move("Farm"); //多態(tài)
huntable.hunt(prey); //接口回調
System.out.println("----------");
}
}
public class test {
public static void main(String[] args) {
Farmer farmer = new Farmer();
farmer.work();
Tiger tiger = new Tiger();
Snake snake = new Snake();
Rabbit rabbit = new Rabbit();
Goat goat = new Goat();
farmer.FeedAnimal(tiger,goat);
farmer.FeedAnimal(snake,rabbit);
}
}
對于這個項目,我覺得對于接口的深入理解非常好的一個項目,我這里給大家做一個詳細的分析
work()方法的運行結果
- 首先是Animal動物這個總的抽象類,擁有三個方法,一個是獲取名稱,第二個是移動到具體的地點,第三則是喝水
- 然后是它的兩個繼承抽象類,哺乳類動物Mammal和爬行類動物Reptile,因為他們均為抽象類,上面實現接口的注意事項時,有提到如果一個抽象類去繼承另一個抽象類,那么它不需要重寫其中的抽象方法
- 接著就是哺乳類動物的三個繼承類老虎類、山羊類以及兔子類,爬行類動物的一個繼承類蛇類,然后分別通過重寫它們的祖先類中的抽象方法實現自己獨有的功能
- 最后是一個農夫類,因為農夫可以將水帶到農場給這些動物喝,這些動物只需要移動到農場即可
- 通過看Farmer類【伐木累】中的work()方法,通過將各種動物所聲明的對象傳入FeedWater()方法,從而實現了一個多態(tài)的效果,只需要通過父類引用去接收子類對象就可以實現子類具體的功能
FeedAnimal方法的運行結果
思路分析
- 首先是思路方面的分析。上面只是引入,就下來就真正涉及到接口了,農夫除了可以將水帶到農場之外,還可以將一些獵物帶到農場供那些會狩獵的動物進食,那就需要就【狩獵】這個功能,但是對于狩獵,并不是每個動物都具有的,比如說山羊、兔子就不具有,因此不可以將此方法定義在抽象類Animal或Mammal中,否則會造成內存浪費
- 那該怎么辦?因為狩獵這個行為,老虎和蛇是不同的,那難道就在它們各自的類中新增這么一個方法體嗎,這就要分析了,因為不僅僅是老虎、蛇,后面這個農場可能還會新增其他的動物,比如說豹子、鱷魚、獅子這些,它們都會捕獵,難道之后新增動物類也要將這些方法重新寫在它們里面嗎,這只會造成程序的內存越來越大,就會產生一系列的棧溢出、內存溢出之類的問題
- 這個時候就要使用到我們的接口了,首先這個功能是很多類都會使用到的,而且這個功能并不是當前這個類族的每個類成員都會使用到的,所以僅僅是為了有這個功能,為了實現一個多態(tài)性,將其定義為一個接口
代碼分析
- 其次是代碼層面的分析,為了方便觀看,將關鍵代碼繼續(xù)粘入此處??
public void BringAnimal(Animal animal,String desination)
{
System.out.println("Farmer bring the " + animal.getName() + " to the " + desination);
}
// public void FeedAnimal(Animal hunter,Animal prey)
// {
// BringAnimal(prey,"Farm");
// hunter.move("Farm");
// Huntable huntable = (Huntable) hunter;
// huntable.hunt(prey);
// System.out.println("----------");
// }
public void FeedAnimal(Huntable huntable,Animal prey)
{
BringAnimal(prey,"Farm");
Animal animal = (Animal) huntable;
animal.move("Farm"); //多態(tài)
huntable.hunt(prey); //接口回調
System.out.println("----------");
}
- 主要是對焦在Farmer農夫這個類,對于BringAnimal()這個方法,需要的參數是農夫需要攜帶的獵物,以及將獵物帶往的目的地
- 然后是FeedAnimal()這個方法,可以看到這個是有兩種方法,第一種被我注釋掉了,傳入的是狩獵的動物以及獵物,然后為什么要將其強轉成接口類型呢,因為捕獵這個方法是在Tiger()和Snake()里的,而原始的抽象類Animal類并沒有,因此要將其轉為接口類型,然后通過接口回調的方式去實現多態(tài),但是這里有一點漏洞,萬一傳入的這個捕獵者是其他沒有捕獵能力的動物,那就會出問題了
- 所以第二種方案,我就將此狩獵者的對象直接給到接口,直接用接口去實現一個回調,只是在狩獵者在移動的時候需要轉換成Animal類罷了,這樣就增加了安全性,外界就無法傳入沒有實現Huntable接口的實現類到這個FeedAnimal中了
- 最后相信大家對接口中的這個 <>有所異或吧,這個是Java里的泛型接口,這樣hunt(T o)便可以接收多種參數,因為不一定是動物可以捕獵,人也可以捕獵,有些植物像捕蠅草也是可以捕獵的,只是所捕的獵物不同罷了,所以當你將一個接口定義成泛型接口時,要在所實現這個接口的地方加上接口的泛型標志,也就是這里Tiger和Snake類的寫法
public class Tiger extends Mammal implements Huntable<Animal>
public class Snake extends Reptile implements Huntable<Animal>
OK,說完這三個實戰(zhàn)案例,相信你對Java中的接口如何使用變得更加清楚,知道了接口的真正作用,了解到定義接口的真正含義
??總結與提煉
看到這里【接口】,你一定是學會了,讓我們來做一個總結。
- 首先我們先了解了什么是接口以及如何是定義并實現一個接口,然后去初步了解了接口的特點及作用。
- 有了這些基本知識后,就需要的知識進行一個串聯,明白UML類圖、繼承類圖和UML類圖之間的細小差別。而且還要和前面的多態(tài)進行一個聯系,明白上轉型對象和回調函數之間的相似和差異性
- 當知識有了一個串聯之后,就要更加進一步去了解接口中的門道,首先我們是知道了用Lambda表達式簡化匿名內部類這個操作,提高了開發(fā)效率。接著我們便開始深入理解接口,對小伙伴們在日常中對接口可能會碰到的問題做了一個羅列,當前肯定還要其他的,可以在評論區(qū)繼續(xù)提出來,我會解答的
- 到了最后,學完了所有知識,就要上戰(zhàn)場了,我分別是淺到深設置了三個實戰(zhàn)項目,更好地幫助大家進一步地理解接口、真正靈活地去使用Java中的接口
以上就是本文所有展現的所有內容,感謝您對本文的觀看,如果疑問請于評論區(qū)留言或私信指出,謝謝??
以下是我開創(chuàng)的社區(qū),感興趣的小伙伴們可以加入進來一起交流學習,解決編程的難題文章來源:http://www.zghlxwxcb.cn/news/detail-827614.html
我的社區(qū):??烈火神盾??文章來源地址http://www.zghlxwxcb.cn/news/detail-827614.html
到了這里,關于Java | interface 和 implements關鍵字【接口,看這篇就夠了】的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!