日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
如何理解Java中接口存在的意義

 0. 前言

我們注重客戶(hù)提出的每個(gè)要求,我們充分考慮每一個(gè)細(xì)節(jié),我們積極的做好網(wǎng)站建設(shè)、做網(wǎng)站服務(wù),我們努力開(kāi)拓更好的視野,通過(guò)不懈的努力,創(chuàng)新互聯(lián)贏得了業(yè)內(nèi)的良好聲譽(yù),這一切,也不斷的激勵(lì)著我們更好的服務(wù)客戶(hù)。 主要業(yè)務(wù):網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)站設(shè)計(jì),微信小程序定制開(kāi)發(fā),網(wǎng)站開(kāi)發(fā),技術(shù)開(kāi)發(fā)實(shí)力,DIV+CSS,PHP及ASP,ASP.Net,SQL數(shù)據(jù)庫(kù)的技術(shù)開(kāi)發(fā)工程師。

在我自己早期學(xué)習(xí)編程的時(shí)候,對(duì)接口存在的意義實(shí)在困惑,我自己亂寫(xiě)代碼的時(shí)候基本上不可能意識(shí)到需要去寫(xiě)接口,不知道接口到底有什么用,為什么要定義接口,感覺(jué)定義接口只是 提前做了個(gè)多余的工作。

這里我先拋出一個(gè)形象的解釋?zhuān)蠹規(guī)е@個(gè)解釋結(jié)合全文來(lái)理解接口存在的意義是什么:

我們把電腦主板上的內(nèi)存插槽,顯卡插槽等類(lèi)比為接口,為什么在主板上搞這么多插槽呢?多浪費(fèi)機(jī)箱空間啊?直接用電烙鐵把顯卡和內(nèi)存的引腳一根一根焊到主板上不就得了(手動(dòng)滑稽)。估計(jì)讀到這里大伙兒心里也大概明白了接口的大致作用,焊死了后,如果你焊錯(cuò)位置了或者拆電腦的時(shí)候,就需要使用電烙鐵進(jìn)行拆裝,多愚蠢哦。

全文脈絡(luò)思維導(dǎo)圖如下:

1. 什么是抽象類(lèi)

在講解接口之前,抽象類(lèi)是繞不過(guò)去的一個(gè)概念,接口可以認(rèn)為是一個(gè)比抽象類(lèi)還要抽象的類(lèi)。

什么是抽象類(lèi)?「包含一個(gè)或多個(gè)抽象方法的類(lèi)就是抽象類(lèi),抽象方法即沒(méi)有方法體的方法」,抽象方法和抽象類(lèi)都必須聲明為 abstract。例如:

 
 
 
 
  1. // 抽象類(lèi)  
  2. public abstract class Person {  
  3.     // 抽象方法  
  4.  public abstract String getDescription();  

切記!「除了抽象方法之外,抽象類(lèi)還可以包含具體數(shù)據(jù)和具體方法」。例如, 抽象類(lèi) Person 還保存著姓名和一個(gè)返回姓名的具體方法:

 
 
 
 
  1. public abstract class Person{  
  2.     private String name;  
  3.     public Person(String name){  
  4.      this.name = name ;  
  5.     }  
  6.     public abstract String getDescription();  
  7.     public String getName(){  
  8.      return name;  
  9.     }  

        許多程序員都會(huì)「錯(cuò)誤」的認(rèn)為,在抽象類(lèi)中不能包含具體方法。其實(shí)這也是接口和抽象類(lèi)的不同之處,接口中是不能包含具體方法的。   

「抽象類(lèi)不能被實(shí)例化」。也就是說(shuō),如果將一個(gè)類(lèi)聲明為 abstract, 就不能創(chuàng)建這個(gè)類(lèi)的對(duì)象。

 
 
 
 
  1. new Person("Jack"); // Error 

可以定義一個(gè)抽象類(lèi)的對(duì)象變量, 但是它只能引用非抽象子類(lèi)的對(duì)象。假設(shè) Student 類(lèi)是 Person 的非抽象子類(lèi):

 
 
 
 
  1. Person p = new Student("Jack"); // Right 

所謂非抽象子類(lèi)就是說(shuō),如果創(chuàng)建一個(gè)繼承抽象類(lèi)的子類(lèi)并為之創(chuàng)建對(duì)象,那么就「必須為父類(lèi)的所有抽象方法提供方法定義」。如果不這么做(可以選擇不做),子類(lèi)仍然是一個(gè)抽象類(lèi),編譯器會(huì)強(qiáng)制我們?yōu)樾骂?lèi)加上 abstract 關(guān)鍵字。

下面定義擴(kuò)展抽象類(lèi) Person 的具體子類(lèi) Student:

 
 
 
 
  1. public class Student extends Person {   
  2.     private String major;   
  3.     public Student(String name, String major) {   
  4.         super(name);   
  5.         this.major = major;   
  6.     }   
  7.     @Override  
  8.     public String getDescription(){ // 實(shí)現(xiàn)父類(lèi)抽象方法  
  9.      return "a student majoring in " + major;   
  10.     }   
  11. }  

在 Student 類(lèi)中實(shí)現(xiàn)了父類(lèi)中的抽象方法 getDescription 。因此,「在 Student類(lèi)中的全部方法都是非抽象的, 這個(gè)類(lèi)不再是抽象類(lèi)」。

調(diào)用如下:

 
 
 
 
  1. Person p = new Student("Jack","Computer Science");  
  2. p.getDescription(); 

由于不能構(gòu)造抽象類(lèi) Person的對(duì)象, 所以變量 p 永遠(yuǎn)不會(huì)引用 Person 對(duì)象, 而是引用諸如 Student這樣的具體子類(lèi)對(duì)象, 而這些對(duì)象中都重寫(xiě)了 getDescription方法。

2. 什么是接口

接口的本質(zhì)其實(shí)也是一個(gè)類(lèi),而且是一個(gè)比抽象類(lèi)還要抽象的類(lèi)。怎么說(shuō)呢?抽象類(lèi)是能夠包含具體方法的,而接口杜絕了這個(gè)可能性,「在 Java 8 之前,接口非常純粹,只能包含抽象方法,也就是沒(méi)有方法體的方法」。而 Java 8 中接口出現(xiàn)了些許的變化,開(kāi)始允許接口包含默認(rèn)方法和靜態(tài)方法,這個(gè)下文會(huì)講解。

Java 使用關(guān)鍵字 interface 而不是 class 來(lái)創(chuàng)建接口。和類(lèi)一樣,通常我們會(huì)在關(guān)鍵字 interface 前加上 public 關(guān)鍵字,否則接口只有包訪問(wèn)權(quán)限,只能在接口相同的包下才能使用它。

 
 
 
 
  1. public interface Concept {  
  2.     void idea1();  
  3.     void idea2();  

同樣的,接口中既然存在抽象方法,那么他就需要被擴(kuò)展(繼承)。使用 implements 關(guān)鍵字使一個(gè)類(lèi)擴(kuò)展某個(gè)特定接口(或一組接口),通俗來(lái)說(shuō):接口只是外形,現(xiàn)在這個(gè)擴(kuò)展子類(lèi)要說(shuō)明它是如何工作的。

 
 
 
 
  1. class Implementation implements Concept {  
  2.     @Override  
  3.     public void idea1() {  
  4.         System.out.println("idea1");  
  5.     }      
  6.     @Override  
  7.     public void idea2() {  
  8.         System.out.println("idea2");  
  9.     }  

這里需要注意的是,你可以選擇顯式地聲明接口中的方法為 public,但是「即使你不這么做,它們也是 public 的」。所以當(dāng)實(shí)現(xiàn)一個(gè)接口時(shí),來(lái)自接口中的方法必須被定義為 public。否則,它們只有包訪問(wèn)權(quán)限,這樣在被繼承時(shí),它們的可訪問(wèn)權(quán)限就被降低了,這是 Java 編譯器所不允許的。

另外,接口中是允許出現(xiàn)常量的,與接口中的方法都自動(dòng)地被設(shè)置為 public—樣,「接口中的域?qū)⒈蛔詣?dòng)被設(shè)置為 public static final 類(lèi)型」,例如:

 
 
 
 
  1. public interface Concept {  
  2.  void idea1(); // public void idea1();  
  3.     // 靜態(tài)屬性  
  4.  double item = 95; // a public static final constant  

        可以將接口方法標(biāo)記為 public,將域標(biāo)記為 public static final。有些程序員出于習(xí)慣或提高清晰度的考慮, 愿意這樣做。但 Java 語(yǔ)言規(guī)范卻「建議不要書(shū)寫(xiě)這些多余的關(guān)鍵字」。   

3. 接口的特性

接口和類(lèi)其中不同的一點(diǎn)就是,我們「無(wú)法像類(lèi)一樣使用 new 運(yùn)算符來(lái)實(shí)例化一個(gè)接口」:

 
 
 
 
  1. x = new Concept(. . .); // ERROR 

原因也很簡(jiǎn)單,接口連具體的構(gòu)造方法都沒(méi)有,肯定是無(wú)法實(shí)例化的。

當(dāng)然, 盡管不能構(gòu)造接口的對(duì)象,聲明接口的變量還是可以的:

 
 
 
 
  1. Concept x; // OK 

接口變量必須引用實(shí)現(xiàn)了接口的類(lèi)對(duì)象:

 
 
 
 
  1. x = new Implementation(. . .); // OK provided Implementation implements Concept 

接下來(lái), 如同使用 instanceof 檢查一個(gè)對(duì)象是否屬于某個(gè)特定類(lèi)一樣, 也可以使用 instanceof檢查一個(gè)對(duì)象是否實(shí)現(xiàn)了某個(gè)特定的接口:

 
 
 
 
  1. if(x instanceof Concept){  
  2.  ...  

另外,與可以建立類(lèi)的繼承關(guān)系一樣,「接口也可以被繼承」:

 
 
 
 
  1. public interface Concept1 {  
  2.     void idea1();  
  3.     void idea2();  
  4. }  
  5. -------------------------------------------    
  6. public interface Concept2 extends Concept1{  
  7.  double idea3();  

當(dāng)然,讀到這里大家可能依然無(wú)法理解,既然有了抽象類(lèi),為什么 Java 程序設(shè)計(jì)語(yǔ)言還要不辭辛苦地引入接口這個(gè)概念?

很重磅!因?yàn)椤敢粋€(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口,但是一個(gè)類(lèi)只能繼承一個(gè)父類(lèi)」。正是接口的出現(xiàn)打破了 Java 這種單繼承的局限,為定義類(lèi)的行為提供了極大的靈活性。

 
 
 
 
  1. class Implementation implements Concept1, Concept2 // OK 

有一條實(shí)際經(jīng)驗(yàn):在合理的范圍內(nèi)盡可能地抽象。顯然,接口比抽象類(lèi)還要抽象。因此,一般更傾向使用接口而不是抽象類(lèi)。

4. Java 8 接口新特性

上文提過(guò)一嘴,「在 Java 8 中,允許在接口中增加靜態(tài)方法和默認(rèn)方法」。理論上講,沒(méi)有任何理由認(rèn)為這是不合法的,只是這有違于將接口作為抽象規(guī)范的初衷。舉個(gè)例子:

 
 
 
 
  1. public interface Concept {  
  2.     // 靜態(tài)方法  
  3.  public static void get(String name){  
  4.      System.out.println("hello " + name);  
  5.     }  
  6.     // 默認(rèn)方法  
  7.     default void idea1(){  
  8.         System.out.println("this is idea1");  
  9.     };  

用 default 修飾符標(biāo)記的方法就是默認(rèn)方法,這樣子類(lèi)就不需要去實(shí)現(xiàn)這個(gè)方法了。

不過(guò),引入默認(rèn)方法后,就出現(xiàn)了一個(gè)「默認(rèn)方法沖突」的問(wèn)題。如果先在一個(gè)接口 A 中將一個(gè)方法 idea 定義為默認(rèn)方法, 然后又在另一個(gè)接口 B 或者超類(lèi) C 中定義了同樣的方法  idea,然后類(lèi) D 實(shí)現(xiàn)了這兩個(gè)接口 A 和 B(或超類(lèi) C)。于是類(lèi) D 中就有了方法 idea 的兩個(gè)默認(rèn)實(shí)現(xiàn),出現(xiàn)了沖突,為此,Java 制定了一套規(guī)則來(lái)解決這個(gè)二義性問(wèn)題:

1 )  「超類(lèi)優(yōu)先」。如果超類(lèi)提供了一個(gè)具體方法,接口中的同名且有相同參數(shù)類(lèi)型的默認(rèn)方法會(huì)被忽略。

2 )  「接口沖突」。如果一個(gè)父類(lèi)接口提供了一個(gè)默認(rèn)方法,另一個(gè)父類(lèi)接口也提供了一個(gè)同名而且參數(shù)類(lèi)型相同的方法,子類(lèi)必須覆蓋這個(gè)方法來(lái)解決沖突。例如:

 
 
 
 
  1. interface A {  
  2.  default void idea(){  
  3.   System.out.println("this is A");  
  4.  }  
  5. }  
  6. interface B {  
  7.  default void idea(){  
  8.   System.out.println("this is B");  
  9.  }  
  10. }  
  11. // 需要在 D 類(lèi)中覆蓋 idea 方法  
  12. class D implements A, B{  
  13.     public void getName(){  
  14.      System.out.println("this is D");  
  15.     }  

現(xiàn)在假設(shè) B接口沒(méi)有為 idea 提供默認(rèn)實(shí)現(xiàn):

 
 
 
 
  1. interface B {  
  2.  void idea();  

那么 D 類(lèi)會(huì)直接從 A 接口繼承默認(rèn)方法嗎?這好像挺有道理, 不過(guò),Java 設(shè)計(jì)者更強(qiáng)調(diào)一致性。兩個(gè)接口如何沖突并不重要,「只要有一個(gè)接口提供了一個(gè)默認(rèn)實(shí)現(xiàn),編譯器就會(huì)報(bào)告錯(cuò)誤, 我們就必須解決這個(gè)二義性」。

當(dāng)然,如果兩個(gè)接口都沒(méi)有為共享方法提供默認(rèn)實(shí)現(xiàn), 那么就與 Java 8 之前的情況一樣,這里不存在沖突。

5. 接口存在的意義

在我自己早期學(xué)習(xí)編程的時(shí)候,對(duì)接口存在的意義實(shí)在困惑,我自己亂寫(xiě)代碼的時(shí)候基本上不可能意識(shí)到需要去寫(xiě)接口,不知道接口到底有什么用,為什么要定義接口,感覺(jué)定義接口只是提前做了個(gè)多余的工作。

其實(shí)不是,定義接口并非多余,「接口是用來(lái)提供公用的方法,規(guī)定子類(lèi)的行為的」。舉個(gè)例子,讓大家直觀的感受下接口的作用:

比如有個(gè)網(wǎng)站, 需要保存不同客戶(hù)的信息, 有些客戶(hù)從 Web 網(wǎng)站來(lái), 有些客戶(hù)從手機(jī)客戶(hù)端來(lái), 有些客戶(hù)直接從后臺(tái)管理系統(tǒng)錄入。假設(shè)不同來(lái)源的客戶(hù)有不同的處理業(yè)務(wù)流程, 這個(gè)時(shí)候我們定義接口來(lái)提供一個(gè)保存客戶(hù)信息的方法,然后不同的平臺(tái)實(shí)現(xiàn)我們這個(gè)保存客戶(hù)信息的接口,以后保存客戶(hù)信息的話, 我們只需要知道這個(gè)接口就可以了,具體調(diào)用的方法被封裝成了黑盒子,這也就是 Java 的多態(tài)的體現(xiàn),「接口幫助我們對(duì)這些有相同功能的方法做了統(tǒng)一管理」。

再比如說(shuō),我們要做一個(gè)畫(huà)板程序,其中里面有一個(gè)面板類(lèi),主要負(fù)責(zé)繪畫(huà)功能,然后你就定義了這個(gè)類(lèi),可是在不久的將來(lái),你突然發(fā)現(xiàn)這個(gè)類(lèi)滿(mǎn)足不了你了,然后你又要重新設(shè)計(jì)這個(gè)類(lèi),更糟糕是你可能要廢棄這個(gè)現(xiàn)有的類(lèi),那么其他引用這個(gè)類(lèi)的地方也需要做出修改,顯然這樣非常麻煩。

如果你一開(kāi)始定義了一個(gè)接口,把繪畫(huà)功能放在這個(gè)接口里,然后定義類(lèi)時(shí)實(shí)現(xiàn)這個(gè)接口,那么你只需要用這個(gè)接口去引用實(shí)現(xiàn)它的類(lèi)就行了,以后要修改的話只不過(guò)是引用另一個(gè)類(lèi)而已?!附涌诘氖褂锰岣吡舜a的可維護(hù)性和可擴(kuò)展性」。

另外,從這兩個(gè)例子我們也能看出,接口不僅「降低了代碼的耦合度」,而且僅僅描敘了程序?qū)ν獾姆?wù),不涉及任何具體的實(shí)現(xiàn)細(xì)節(jié),這樣也就比較「安全」一些。


標(biāo)題名稱(chēng):如何理解Java中接口存在的意義
文章鏈接:http://www.5511xx.com/article/cccdhde.html