您好,登錄后才能下訂單哦!
Java 中接口存在的意義是什么,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
在講解接口之前,抽象類是繞不過去的一個(gè)概念,接口可以認(rèn)為是一個(gè)比抽象類還要抽象的類。
什么是抽象類?「包含一個(gè)或多個(gè)抽象方法的類就是抽象類,抽象方法即沒有方法體的方法」,抽象方法和抽象類都必須聲明為 abstract
。例如:
// 抽象類
public abstract class Person {
// 抽象方法
public abstract String getDescription();
}
切記!「除了抽象方法之外,抽象類還可以包含具體數(shù)據(jù)和具體方法」。例如, 抽象類 Person
還保存著姓名和一個(gè)返回姓名的具體方法:
public abstract class Person{
private String name;
public Person(String name){
this.name = name ;
}
public abstract String getDescription();
public String getName(){
return name;
}
}
?許多程序員都會「錯(cuò)誤」的認(rèn)為,在抽象類中不能包含具體方法。其實(shí)這也是接口和抽象類的不同之處,接口中是不能包含具體方法的。
?
「抽象類不能被實(shí)例化」。也就是說,如果將一個(gè)類聲明為 abstract
, 就不能創(chuàng)建這個(gè)類的對象。
new Person("Jack"); // Error
可以定義一個(gè)抽象類的對象變量, 但是它只能引用非抽象子類的對象。假設(shè) Student
類是 Person
的非抽象子類:
Person p = new Student("Jack"); // Right
所謂非抽象子類就是說,如果創(chuàng)建一個(gè)繼承抽象類的子類并為之創(chuàng)建對象,那么就「必須為父類的所有抽象方法提供方法定義」。如果不這么做(可以選擇不做),子類仍然是一個(gè)抽象類,編譯器會強(qiáng)制我們?yōu)樾骂惣由?abstract
關(guān)鍵字。
下面定義擴(kuò)展抽象類 Person
的具體子類 Student
:
public class Student extends Person {
private String major;
public Student(String name, String major) {
super(name);
this.major = major;
}
@Override
public String getDescription(){ // 實(shí)現(xiàn)父類抽象方法
return "a student majoring in " + major;
}
}
在 Student
類中實(shí)現(xiàn)了父類中的抽象方法 getDescription
。因此,「在 Student
類中的全部方法都是非抽象的, 這個(gè)類不再是抽象類」。
???? 調(diào)用如下:
Person p = new Student("Jack","Computer Science");
p.getDescription();
由于不能構(gòu)造抽象類 Person
的對象, 所以變量 p
永遠(yuǎn)不會引用 Person
對象, 而是引用諸如 Student
這樣的具體子類對象, 而這些對象中都重寫了 getDescription
方法。
接口的本質(zhì)其實(shí)也是一個(gè)類,而且是一個(gè)比抽象類還要抽象的類。怎么說呢?抽象類是能夠包含具體方法的,而接口杜絕了這個(gè)可能性,「在 Java 8 之前,接口非常純粹,只能包含抽象方法,也就是沒有方法體的方法」。而 Java 8 中接口出現(xiàn)了些許的變化,開始允許接口包含默認(rèn)方法和靜態(tài)方法,這個(gè)下文會講解。
Java 使用關(guān)鍵字 interface
而不是 class
來創(chuàng)建接口。和類一樣,通常我們會在關(guān)鍵字 interface
前加上 public
關(guān)鍵字,否則接口只有包訪問權(quán)限,只能在接口相同的包下才能使用它。
public interface Concept {
void idea1();
void idea2();
}
同樣的,接口中既然存在抽象方法,那么他就需要被擴(kuò)展(繼承)。使用 implements
關(guān)鍵字使一個(gè)類擴(kuò)展某個(gè)特定接口(或一組接口),通俗來說:接口只是外形,現(xiàn)在這個(gè)擴(kuò)展子類要說明它是如何工作的。
class Implementation implements Concept {
@Override
public void idea1() {
System.out.println("idea1");
}
@Override
public void idea2() {
System.out.println("idea2");
}
}
這里需要注意的是,你可以選擇顯式地聲明接口中的方法為 public
,但是「即使你不這么做,它們也是 public
的」。所以當(dāng)實(shí)現(xiàn)一個(gè)接口時(shí),來自接口中的方法必須被定義為 public
。否則,它們只有包訪問權(quán)限,這樣在被繼承時(shí),它們的可訪問權(quán)限就被降低了,這是 Java 編譯器所不允許的。
另外,接口中是允許出現(xiàn)常量的,與接口中的方法都自動(dòng)地被設(shè)置為 public
—樣,「接口中的域?qū)⒈蛔詣?dòng)被設(shè)置為 public static final
類型」,例如:
public interface Concept {
void idea1(); // public void idea1();
// 靜態(tài)屬性
double item = 95; // a public static final constant
}
?可以將接口方法標(biāo)記為
?public
,將域標(biāo)記為public static final
。有些程序員出于習(xí)慣或提高清晰度的考慮, 愿意這樣做。但 Java 語言規(guī)范卻「建議不要書寫這些多余的關(guān)鍵字」。
接口和類其中不同的一點(diǎn)就是,我們「無法像類一樣使用 new
運(yùn)算符來實(shí)例化一個(gè)接口」:
x = new Concept(. . .); // ERROR
原因也很簡單,接口連具體的構(gòu)造方法都沒有,肯定是無法實(shí)例化的。
當(dāng)然, 盡管不能構(gòu)造接口的對象,聲明接口的變量還是可以的:
Concept x; // OK
接口變量必須引用實(shí)現(xiàn)了接口的類對象:
x = new Implementation(. . .); // OK provided Implementation implements Concept
接下來, 如同使用 instanceof
檢查一個(gè)對象是否屬于某個(gè)特定類一樣, 也可以使用 instanceof
檢查一個(gè)對象是否實(shí)現(xiàn)了某個(gè)特定的接口:
if(x instanceof Concept){
...
}
另外,與可以建立類的繼承關(guān)系一樣,「接口也可以被繼承」:
public interface Concept1 {
void idea1();
void idea2();
}
-------------------------------------------
public interface Concept2 extends Concept1{
double idea3();
}
當(dāng)然,讀到這里大家可能依然無法理解,既然有了抽象類,為什么 Java 程序設(shè)計(jì)語言還要不辭辛苦地引入接口這個(gè)概念?
很重磅!因?yàn)?strong>「一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但是一個(gè)類只能繼承一個(gè)父類」。正是接口的出現(xiàn)打破了 Java 這種單繼承的局限,為定義類的行為提供了極大的靈活性。
class Implementation implements Concept1, Concept2 // OK
有一條實(shí)際經(jīng)驗(yàn):在合理的范圍內(nèi)盡可能地抽象。顯然,接口比抽象類還要抽象。因此,一般更傾向使用接口而不是抽象類。
上文提過一嘴,「在 Java 8 中,允許在接口中增加靜態(tài)方法和默認(rèn)方法」。理論上講,沒有任何理由認(rèn)為這是不合法的,只是這有違于將接口作為抽象規(guī)范的初衷。舉個(gè)例子:
public interface Concept {
// 靜態(tài)方法
public static void get(String name){
System.out.println("hello " + name);
}
// 默認(rèn)方法
default void idea1(){
System.out.println("this is idea1");
};
}
用 default
修飾符標(biāo)記的方法就是默認(rèn)方法,這樣子類就不需要去實(shí)現(xiàn)這個(gè)方法了。
不過,引入默認(rèn)方法后,就出現(xiàn)了一個(gè)「默認(rèn)方法沖突」的問題。如果先在一個(gè)接口 A 中將一個(gè)方法 idea
定義為默認(rèn)方法, 然后又在另一個(gè)接口 B 或者超類 C 中定義了同樣的方法 idea
,然后類 D 實(shí)現(xiàn)了這兩個(gè)接口 A 和 B(或超類 C)。于是類 D 中就有了方法 idea
的兩個(gè)默認(rèn)實(shí)現(xiàn),出現(xiàn)了沖突,為此,Java 制定了一套規(guī)則來解決這個(gè)二義性問題:
1 ) 「超類優(yōu)先」。如果超類提供了一個(gè)具體方法,接口中的同名且有相同參數(shù)類型的默認(rèn)方法會被忽略。
2 ) 「接口沖突」。如果一個(gè)父類接口提供了一個(gè)默認(rèn)方法,另一個(gè)父類接口也提供了一個(gè)同名而且參數(shù)類型相同的方法,子類必須覆蓋這個(gè)方法來解決沖突。例如:
interface A {
default void idea(){
System.out.println("this is A");
}
}
interface B {
default void idea(){
System.out.println("this is B");
}
}
// 需要在 D 類中覆蓋 idea 方法
class D implements A, B{
public void getName(){
System.out.println("this is D");
}
}
現(xiàn)在假設(shè) B
接口沒有為 idea
提供默認(rèn)實(shí)現(xiàn):
interface B {
void idea();
}
那么 D 類會直接從 A 接口繼承默認(rèn)方法嗎?這好像挺有道理, 不過,Java 設(shè)計(jì)者更強(qiáng)調(diào)一致性。兩個(gè)接口如何沖突并不重要,「只要有一個(gè)接口提供了一個(gè)默認(rèn)實(shí)現(xiàn),編譯器就會報(bào)告錯(cuò)誤, 我們就必須解決這個(gè)二義性」。
當(dāng)然,如果兩個(gè)接口都沒有為共享方法提供默認(rèn)實(shí)現(xiàn), 那么就與 Java 8 之前的情況一樣,這里不存在沖突。
在我自己早期學(xué)習(xí)編程的時(shí)候,對接口存在的意義實(shí)在困惑,我自己亂寫代碼的時(shí)候基本上不可能意識到需要去寫接口,不知道接口到底有什么用,為什么要定義接口,感覺定義接口只是提前做了個(gè)多余的工作。
其實(shí)不是,定義接口并非多余,「接口是用來提供公用的方法,規(guī)定子類的行為的」。舉個(gè)例子,讓大家直觀的感受下接口的作用:
比如有個(gè)網(wǎng)站, 需要保存不同客戶的信息, 有些客戶從 Web 網(wǎng)站來, 有些客戶從手機(jī)客戶端來, 有些客戶直接從后臺管理系統(tǒng)錄入。假設(shè)不同來源的客戶有不同的處理業(yè)務(wù)流程, 這個(gè)時(shí)候我們定義接口來提供一個(gè)保存客戶信息的方法,然后不同的平臺實(shí)現(xiàn)我們這個(gè)保存客戶信息的接口,以后保存客戶信息的話, 我們只需要知道這個(gè)接口就可以了,具體調(diào)用的方法被封裝成了黑盒子,這也就是 Java 的多態(tài)的體現(xiàn),「接口幫助我們對這些有相同功能的方法做了統(tǒng)一管理」。
看完上述內(nèi)容,你們掌握J(rèn)ava 中接口存在的意義是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。