最開始接觸java接口時(shí)候焕刮,有個(gè)疑問『例如我定義了一個(gè)接口涎嚼,但是我在繼承這個(gè)接口的類中還要寫接口的實(shí)現(xiàn)方法,那我不如直接就在這個(gè)類中寫實(shí)現(xiàn)方法豈不是更便捷模捂,還省去了定義接口?』蜘矢。
Object-C類似與 interface的特性叫 protocol即協(xié)議狂男,意思更加明確。遵守協(xié)議就必須實(shí)現(xiàn)協(xié)議品腹。
一.基礎(chǔ)知識
接口(英文:Interface)岖食,在JAVA編程語言中是一個(gè)抽象類型,是抽象方法的集合舞吭,接口通常以interface來聲明泡垃。一個(gè)類通過繼承接口的方式,從而來繼承接口的抽象方法羡鸥。
接口并不是類蔑穴,編寫接口的方式和類很相似,但是它們屬于不同的概念惧浴。類描述對象的屬性和方法存和。接口則包含類要實(shí)現(xiàn)的方法。
除非實(shí)現(xiàn)接口的類是抽象類衷旅,否則該類要定義接口中的所有方法捐腿。
接口無法被實(shí)例化,但是可以被實(shí)現(xiàn)柿顶。一個(gè)實(shí)現(xiàn)接口的類茄袖,必須實(shí)現(xiàn)接口內(nèi)所描述的所有方法,否則就必須聲明為抽象類嘁锯。另外宪祥,在 Java 中,接口類型可用來聲明一個(gè)變量家乘,他們可以成為一個(gè)空指針品山,或是被綁定在一個(gè)以此接口實(shí)現(xiàn)的對象。
1.接口與類相似點(diǎn):
一個(gè)接口可以有多個(gè)方法烤低。
接口文件保存在 .java 結(jié)尾的文件中,文件名使用接口名笆载。
接口的字節(jié)碼文件保存在 .class 結(jié)尾的文件中扑馁。
接口相應(yīng)的字節(jié)碼文件必須在與包名稱相匹配的目錄結(jié)構(gòu)中涯呻。
2.接口與類的區(qū)別:
接口不能用于實(shí)例化對象。
接口沒有構(gòu)造方法腻要。
接口中所有的方法必須是抽象方法复罐。
接口不能包含成員變量,除了 static 和 final 變量雄家。
接口不是被類繼承了效诅,而是要被類實(shí)現(xiàn)。
接口支持多重繼承趟济。
3.接口特性:
接口中每一個(gè)方法也是隱式抽象的,接口中的方法會被隱式的指定為 public abstract(只能是 public abstract乱投,其他修飾符都會報(bào)錯(cuò))。
接口中可以含有變量顷编,但是接口中的變量會被隱式的指定為 public static final 變量(并且只能是 public戚炫,用 private 修飾會報(bào)編譯錯(cuò)誤。
接口中的方法是不能在接口中實(shí)現(xiàn)的媳纬,只能由實(shí)現(xiàn)接口的類來實(shí)現(xiàn)接口中的方法双肤。
4.抽象類和接口的區(qū)別:
- 抽象類中的方法可以有方法體,就是能實(shí)現(xiàn)方法的具體功能钮惠,但是接口中的方法不行茅糜。
- 抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是 public static final 類型的素挽。
- 接口中不能含有靜態(tài)代碼塊以及靜態(tài)方法(用 static 修飾的方法)蔑赘,而抽象類是可以有靜態(tài)代碼塊和靜態(tài)方法。
- 一個(gè)類只能繼承一個(gè)抽象類毁菱,而一個(gè)類卻可以實(shí)現(xiàn)多個(gè)接口米死。
二.個(gè)人理解
接口是一種規(guī)范,是一種統(tǒng)一的標(biāo)準(zhǔn)贮庞。
統(tǒng)一標(biāo)準(zhǔn)的目的峦筒,是大家都知道這個(gè)是做什么的,但是具體不用知道具體怎么做窗慎。比如說:我知道 Comparable 這個(gè)接口是用來比較兩個(gè)對象的物喷,那么如何去比較呢?數(shù)字有數(shù)字的比較方法遮斥,字符串有字符串的比較方法峦失,學(xué)生(自己定義的類)也有自己的比較方法。然后术吗,在另外一個(gè)負(fù)責(zé)對象排序(不一定是數(shù)字喔)的代碼里面尉辑,肯定需要將兩個(gè)對象比較。
這兩個(gè)對象是什么類型呢较屿?Object a,b隧魄?肯定不行卓练,a > b 這樣的語法無法通過編譯。int a,b购啄?也不行襟企?一開始就說了,不一定是數(shù)字狮含。....所以顽悼,Comparable 就來了。他告訴編譯器几迄,a b 兩個(gè)對象都滿足 Comparable 接口蔚龙,也就是他們是可以進(jìn)行比較的。具體怎么比較乓旗,這段程序不需要知道府蛇。所以,他需要一些具體的實(shí)現(xiàn)屿愚,Comparable 接口有一個(gè)方法汇跨,叫 compareTo。那么這個(gè)方法就是用來取代 <妆距、> 這樣的運(yùn)算符穷遂。因?yàn)檫\(yùn)算符是編譯器保留給內(nèi)置類型(整數(shù)、浮點(diǎn)數(shù))進(jìn)行比較用的娱据,而不是一個(gè)廣義的比較運(yùn)算蚪黑。如果你可以明白 JDK 自身庫里面諸如 Comparable 這樣已經(jīng)有的接口,那么就很容易理解自己在開發(fā)程序的時(shí)候?yàn)槭裁葱枰玫浇涌诹恕?/p>
接口是一種規(guī)范中剩,適合多人合作開發(fā)忌穿。當(dāng)你需要調(diào)用別人的代碼時(shí)候,可以規(guī)定好接口结啼。別人看到你的接口就知道該實(shí)現(xiàn)哪些內(nèi)容了掠剑。我寫個(gè)接口,再把調(diào)用函數(shù)寫好郊愧,你把接口實(shí)現(xiàn)了朴译,傳個(gè)實(shí)例進(jìn)來,任務(wù)就完成了属铁,雙方的工作都不會收到影響眠寿。interface在OC里面叫做protocol,即協(xié)議的意思焦蘑。實(shí)現(xiàn)了這個(gè)接口盯拱,代表完成接口規(guī)定的任務(wù)。A需要一些能做某些事情的東西,于是A要求坟乾,必須實(shí)現(xiàn)了xx接口迹辐,才能被我調(diào)用。實(shí)際上也就是個(gè)“規(guī)范”甚侣。
比如發(fā)送短信業(yè)務(wù)。一般發(fā)送短信的場景包括注冊用戶间学,找回密碼殷费,重要通知,修改交易密碼等低葫。短信現(xiàn)在的結(jié)構(gòu)是先接上各家短信通道公司详羡,再經(jīng)由聯(lián)通移動等發(fā)送出去。一般公司備用多個(gè)短信通道嘿悬。我們想要實(shí)現(xiàn)在更換短信通道方的時(shí)候实柠,不更改其他模塊中被引入的代碼?接口就能完美的實(shí)現(xiàn)了這一點(diǎn)善涨。在開始我們要規(guī)定好接口窒盐,每個(gè)短信通道都實(shí)現(xiàn)這些接口。對于我們上層我們不需要管各個(gè)通道是如何實(shí)現(xiàn)的钢拧,只需要在用到這個(gè)業(yè)務(wù)時(shí)候調(diào)用這個(gè)接口就可以了蟹漓。
3.為什么@Autowired使用在interface上而不是實(shí)現(xiàn)類上?
Firstly, it is always a good practice to code to interfaces in general. Secondly, in case of spring, you can inject any implementation at runtime. A typical use case is to inject mock implementation during testing stage.
首先源内,一般使用接口是很常用并且有益的變成技術(shù)葡粒。其次,在spring中膜钓,你可以在運(yùn)行過程中注入各種實(shí)現(xiàn)嗽交。一個(gè)很經(jīng)典的情況就是在測試階段,注入模擬的實(shí)現(xiàn)類颂斜。
如果你是一個(gè)有了那么點(diǎn)經(jīng)驗(yàn)的程序員夫壁,如果你還沒有習(xí)慣TDD的開發(fā)》傧剩可以體驗(yàn)一下這種寫法掌唾。還是拿短信為例。先寫一個(gè)SMSServiceTest忿磅。然后寫一個(gè)Test方法糯彬。 這個(gè)時(shí)候什么都沒有,不用管葱她。先直接這么寫撩扒。int code=SMSSevice.sendTextMessage(mobile,content,type);
這個(gè)時(shí)候IDE會提示你沒有這個(gè)SMSService,用代碼自動生成工具去創(chuàng)建這么一個(gè)接口出來。再根據(jù)提示把方法創(chuàng)建出來搓谆。再寫 SMSService smsService=new SMSServiceCorpaImpl();再根據(jù)代碼把實(shí)現(xiàn)類生成了炒辉。一般來說IDE會自動留一個(gè)空的方法。不用管泉手。這里只是一個(gè)簡單的例子黔寇,但是你發(fā)現(xiàn),當(dāng)你用TDD的這種方式去寫代碼的時(shí)候斩萌,完全不用關(guān)系SMSService是怎么內(nèi)部實(shí)現(xiàn)的缝裤。你只需要繼續(xù)寫你的單元測試代碼好了,明確的知道這個(gè)SMSService要做的功能是發(fā)送短信颊郎,需要傳遞手機(jī)號憋飞,內(nèi)容,類型姆吭,返回一個(gè)狀態(tài)碼榛做。那么接著說為什么對單元測試很方便?一般而言會用Spring配置Bean内狸,所以實(shí)際上你的單元測試代碼也不用有改動检眯,無論是測試哪一個(gè)實(shí)現(xiàn)類,都只通過更改配置文件就可以完成答倡。想想轰传,如果沒有接口呢?是不是要對每一個(gè)短信通道單獨(dú)寫一個(gè)單元測試的方法瘪撇?
3.對于不需要頻繁更變實(shí)現(xiàn)類的方法获茬,是不是就可以不用寫接口了?答案是No倔既。整個(gè)系統(tǒng)架構(gòu)的代碼可以單純認(rèn)為有四部分構(gòu)成恕曲。Model+Interface+Service+UtilModel是純粹的Pojo,貧血模型渤涌,Inteface和Service是接口和實(shí)現(xiàn)分開的佩谣,Util是全項(xiàng)目通用,或者是跨項(xiàng)目通用的实蓬,跟業(yè)務(wù)邏輯沒有任何關(guān)系的茸俭。寫接口最大的好處就是在你寫的Controller代碼,或者是Service里的主要業(yè)務(wù)邏輯代碼的時(shí)候安皱,屏蔽掉細(xì)節(jié)调鬓。寫一個(gè)業(yè)務(wù)邏輯的時(shí)候,比如說修真院的加入班級酌伊。第一步腾窝,做校驗(yàn),用戶是否為空,班級是否不存在虹脯,是否已經(jīng)加入了班級等等驴娃。第二步,更新班級和用戶的關(guān)系表循集,更新班級總?cè)藬?shù)唇敞,更新職業(yè)總?cè)藬?shù),更新用戶的最新班級ID暇榴。第三步厚棵,發(fā)送系統(tǒng)通知,告知用戶加入班級成功蔼紧。如果說不用接口,只用實(shí)現(xiàn)類的話狠轻,第一種方式就是把所有的代碼都寫在這個(gè)Controller里去奸例,代碼會非常非常繁瑣,一個(gè)函數(shù)突破幾千行輕輕松松向楼,而且改動起來很麻煩查吊。第二種方式就是抽象出來函數(shù)。這種方式在某種程度上能夠解決代碼塊大的問題湖蜕,但是你必須要New一個(gè)實(shí)現(xiàn)類出來逻卖,想想在上述邏輯中,需要new幾個(gè)實(shí)現(xiàn)類昭抒?這些實(shí)現(xiàn)類就會被New的各處都是评也,甚至改個(gè)名字都很蛋疼。但是如果你使用接口的話灭返,你會發(fā)現(xiàn)盗迟,接口是強(qiáng)制于你去將復(fù)雜的業(yè)務(wù)邏輯抽象成具體做的事兒。比如說熙含,if(user==null){ // to do something}就變成了CheckUser(uid)這么一個(gè)接口罚缕。實(shí)現(xiàn)類也明確了自已要做的事情。從某種程度上來說怎静,抽象成一個(gè)私有方法也能解決這個(gè)問題邮弹,但是一般都會推薦,如果你發(fā)現(xiàn)你寫了很多私有方法蚓聘,要么是他們可以繼續(xù)演化成一個(gè)util腌乡,要么是可以成為一個(gè)Service。