申明:非原創(chuàng)加叁,轉(zhuǎn)載自 Java接口 詳解(一)
Java接口 詳解(二)
一循集、基本概念
接口(Interface)扫尺,在JAVA編程語(yǔ)言中是一個(gè)抽象類型约急,是抽象方法的集合零远。接口通常以interface來(lái)聲明。一個(gè)類通過(guò)繼承接口的方式厌蔽,從而來(lái)繼承接口的抽象方法牵辣。
如果一個(gè)類只由抽象方法和全局常量組成,那么這種情況下不會(huì)將其定義為一個(gè)抽象類奴饮。只會(huì)定義為一個(gè)接口纬向,所以接口嚴(yán)格的來(lái)講屬于一個(gè)特殊的類,而這個(gè)類里面只有抽象方法和全局常量戴卜,就連構(gòu)造方法也沒(méi)有逾条。
范例:定義一個(gè)接口
interface A{//定義一個(gè)接口
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
二、接口的使用
1投剥、由于接口里面存在抽象方法师脂,所以接口對(duì)象不能直接使用關(guān)鍵字new進(jìn)行實(shí)例化。接口的使用原則如下:
(1)接口必須要有子類江锨,但此時(shí)一個(gè)子類可以使用implements關(guān)鍵字實(shí)現(xiàn)多個(gè)接口吃警;
(2)接口的子類(如果不是抽象類),那么必須要覆寫接口中的全部抽象方法啄育;
(3)接口的對(duì)象可以利用子類對(duì)象的向上轉(zhuǎn)型進(jìn)行實(shí)例化酌心。
范例:
package com.wz.interfacedemo;
interface A{//定義一個(gè)接口A
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
interface B{//定義一個(gè)接口B
public abstract void get();
}
class X implements A,B{//X類實(shí)現(xiàn)了A和B兩個(gè)接口
@Override
public void print() {
System.out.println("接口A的抽象方法print()");
}
@Override
public void get() {
System.out.println("接口B的抽象方法get()");
}
}
public class TestDemo {
public static void main(String[] args){
X x = new X();//實(shí)例化子類對(duì)象
A a = x;//向上轉(zhuǎn)型
B b = x;//向上轉(zhuǎn)型
a.print();
b.get();
}
}
運(yùn)行結(jié)果:
接口A的抽象方法print()
接口B的抽象方法get()
以上的代碼實(shí)例化了X類的對(duì)象,由于X類是A和B的子類灸撰,那么X類的對(duì)象可以變?yōu)锳接口或者B接口對(duì)象谒府。我們把測(cè)試主類代碼改一下:
public class TestDemo {
public static void main(String[] args){
A a = new X();
B b = (B) a;
b.get();
}
}
運(yùn)行結(jié)果:
接口B的抽象方法get()
好,沒(méi)任何問(wèn)題浮毯,我們?cè)賮?lái)做個(gè)驗(yàn)證:
public class TestDemo {
public static void main(String[] args){
A a = new X();
B b = (B) a;
b.get();
System.out.println(a instanceof A);
System.out.println(a instanceof B);
}
運(yùn)行結(jié)果:
接口B的抽象方法get()
true
true
我們發(fā)現(xiàn),從定義結(jié)構(gòu)來(lái)講泰鸡,A和B兩個(gè)接口沒(méi)有任何直接聯(lián)系债蓝,但這兩個(gè)接口卻擁有同一個(gè)子類。我們不要被類型和名稱所迷惑盛龄,因?yàn)閷?shí)例化的是X子類饰迹,而這個(gè)類對(duì)象屬于B類的對(duì)象,所以以上代碼可行余舶,只不過(guò)從代碼的編寫規(guī)范來(lái)講啊鸭,并不是很好。
2匿值、對(duì)于子類而言赠制,除了實(shí)現(xiàn)接口外,還可以繼承抽象類挟憔。若既要繼承抽象類钟些,同時(shí)還要實(shí)現(xiàn)接口的話烟号,使用一下語(yǔ)法格式:
class 子類 [extends 父類] [implemetns 接口1,接口2,...] {}
范例:
interface A{//定義一個(gè)接口A
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
interface B{//定義一個(gè)接口B
public abstract void get();
}
abstract class C{//定義一個(gè)抽象類C
public abstract void change();
}
class X extends C implements A,B{//X類繼承C類,并實(shí)現(xiàn)了A和B兩個(gè)接口
@Override
public void print() {
System.out.println("接口A的抽象方法print()");
}
@Override
public void get() {
System.out.println("接口B的抽象方法get()");
}
@Override
public void change() {
System.out.println("抽象類C的抽象方法change()");
}
}
對(duì)于接口政恍,里面的組成只有抽象方法和全局常量汪拥,所以很多時(shí)候?yàn)榱藭?shū)寫簡(jiǎn)單,可以不用寫public abstract 或者public static final篙耗。并且迫筑,接口中的訪問(wèn)權(quán)限只有一種:public,即:定義接口方法和全局常量的時(shí)候就算沒(méi)有寫上public宗弯,那么最終的訪問(wèn)權(quán)限也是public铣焊,注意不是default。以下兩種寫法是完全等價(jià)的:
interface A{
public static final String MSG = "hello";
public abstract void print();
}
等價(jià)于
interface A{
String MSG = "hello";
void print();
}
但是罕伯,這樣會(huì)不會(huì)帶來(lái)什么問(wèn)題呢曲伊?如果子類子類中的覆寫方法也不是public,我們來(lái)看:
package com.wz.interfacedemo;
interface A{
String MSG = "hello";
void print();
}
class X implements A{
void print() {
System.out.println("接口A的抽象方法print()");
}
}
public class TestDemo {
public static void main(String[] args){
A a = new X();
a.print();
}
}
運(yùn)行結(jié)果:
Exception in thread "main" java.lang.IllegalAccessError: com.wz.interfacedemo.X.print()V
at com.wz.interfacedemo.TestDemo.main(TestDemo.java:22)
這是因?yàn)榻涌谥心J(rèn)是public修飾追他,若子類中沒(méi)用public修飾坟募,則訪問(wèn)權(quán)限變嚴(yán)格了,給子類分配的是更低的訪問(wèn)權(quán)限邑狸。所以懈糯,在定義接口的時(shí)候強(qiáng)烈建議在抽象方法前加上public ,子類也加上:
interface A{
String MSG = "hello";
public void print();
}
class X implements A{
public void print() {
System.out.println("接口A的抽象方法print()");
}
}
3单雾、在Java中赚哗,一個(gè)抽象類只能繼承一個(gè)抽象類,但一個(gè)接口卻可以使用extends關(guān)鍵字同時(shí)繼承多個(gè)接口(但接口不能繼承抽象類)硅堆。
范例:
interface A{
public void funA();
}
interface B{
public void funB();
}
//C接口同時(shí)繼承了A和B兩個(gè)接口
interface C extends A,B{//使用的是extends
public void funC();
}
class X implements C{
@Override
public void funA() {
}
@Override
public void funB() {
}
@Override
public void funC() {
}
}
由此可見(jiàn)屿储,從繼承關(guān)系來(lái)說(shuō)接口的限制比抽象類少:
(1)一個(gè)抽象類只能繼承一個(gè)抽象父類,而接口可以繼承多個(gè)接口渐逃;
(2)一個(gè)子類只能繼承一個(gè)抽象類够掠,卻可以實(shí)現(xiàn)多個(gè)接口(在Java中,接口的主要功能是解決單繼承局限問(wèn)題)
4茄菊、從接口的概念上來(lái)講疯潭,接口只能由抽象方法和全局常量組成绒障,但是內(nèi)部結(jié)構(gòu)是不受概念限制的唤锉,正如抽象類中可以定義抽象內(nèi)部類一樣,在接口中也可以定義普通內(nèi)部類怀跛、抽象內(nèi)部類和內(nèi)部接口(但從實(shí)際的開(kāi)發(fā)來(lái)講脊僚,用戶自己去定義內(nèi)部抽象類或內(nèi)部接口的時(shí)候是比較少見(jiàn)的)相叁,范例如下,在接口中定義一個(gè)抽象內(nèi)部類:
interface A{
public void funA();
abstract class B{//定義一個(gè)抽象內(nèi)部類
public abstract void funB();
}
}
在接口中如果使用了static去定義一個(gè)內(nèi)接口,它表示一個(gè)外部接口:
interface A{
public void funA();
static interface B{//使用了static钝荡,是一個(gè)外部接口
public void funB();
}
}
class X implements A.B{
@Override
public void funB() {
}
}
三街立、接口的實(shí)際應(yīng)用(標(biāo)準(zhǔn)定義)
在日常的生活之中,接口這一名詞經(jīng)常聽(tīng)到的埠通,例如:USB接口赎离、打印接口、充電接口等等端辱。
如果要進(jìn)行開(kāi)發(fā)梁剔,要先開(kāi)發(fā)出USB接口標(biāo)準(zhǔn),然后設(shè)備廠商才可以設(shè)計(jì)出USB設(shè)備舞蔽。
現(xiàn)在假設(shè)每一個(gè)USB設(shè)備只有兩個(gè)功能:安裝驅(qū)動(dòng)程序荣病、工作。
定義一個(gè)USB的標(biāo)準(zhǔn):
interface USB { // 操作標(biāo)準(zhǔn)
public void install() ;
public void work() ;
}
在電腦上應(yīng)用此接口:
class Computer {
public void plugin(USB usb) {
usb.install() ;
usb.work() ;
}
}
定義USB設(shè)備—手機(jī):
class Phone implements USB {
public void install() {
System.out.println("安裝手機(jī)驅(qū)動(dòng)程序渗柿。") ;
}
public void work() {
System.out.println("手機(jī)與電腦進(jìn)行工作个盆。") ;
}
}
定義USB設(shè)備—打印機(jī):
class Print implements USB {
public void install() {
System.out.println("安裝打印機(jī)驅(qū)動(dòng)程序。") ;
}
public void work() {
System.out.println("進(jìn)行文件打印朵栖。") ;
}
}
定義USB設(shè)備—MP3:
class MP3 implements USB {
public void install() {
System.out.println("安裝MP3驅(qū)動(dòng)程序颊亮。") ;
}
public void work() {
System.out.println("進(jìn)行MP3拷貝。") ;
}
}
測(cè)試主類:
public class TestDemo {
public static void main(String args[]) {
Computer c = new Computer() ;
c.plugin(new Phone()) ;
c.plugin(new Print()) ;
c.plugin(new MP3());
}
}
運(yùn)行結(jié)果:
安裝手機(jī)驅(qū)動(dòng)程序陨溅。
手機(jī)與電腦進(jìn)行工作终惑。
安裝打印機(jī)驅(qū)動(dòng)程序。
進(jìn)行文件打印门扇。
安裝MP3驅(qū)動(dòng)程序雹有。
進(jìn)行MP3拷貝。
可以看出臼寄,不管有多少個(gè)USB接口的子類霸奕,都可以在電腦上使用。
在現(xiàn)實(shí)生活中脯厨,標(biāo)準(zhǔn)的概念隨處可見(jiàn)铅祸,而在程序里標(biāo)準(zhǔn)使用接口定義的。
上一篇Java接口 詳解(一)講到了接口的基本概念合武、接口的使用和接口的實(shí)際應(yīng)用(標(biāo)準(zhǔn)定義)。我們接著來(lái)講涡扼。
一稼跳、接口的應(yīng)用—工廠設(shè)計(jì)模式(Factory)
我們先看一個(gè)范例:
package com.wz.factoryDemo;
interface Fruit{
public void eat();
}
class Apple implements Fruit{
@Override
public void eat() {
System.out.println("吃蘋果。吃沪。汤善。");
}
}
public class Client {
public static void main(String[] args) {
Fruit f = new Apple();
f.eat();
}
}
運(yùn)行結(jié)果:
吃蘋果。。红淡。
以上程序非常簡(jiǎn)單不狮,就是通過(guò)接口的子類為接口對(duì)象實(shí)例化,但這樣操作會(huì)存在什么樣的問(wèn)題呢在旱?在軟件開(kāi)發(fā)中摇零,我們強(qiáng)調(diào)以下兩點(diǎn):
(1)主方法或主類是一個(gè)客戶端,客戶端的操作應(yīng)該越簡(jiǎn)單越好桶蝎;
(2)客戶端之外的代碼修改驻仅,不影響用戶的使用。也就是說(shuō)登渣,用戶可以不用去關(guān)心代碼是否由變更噪服。
確實(shí),以上范例沒(méi)有任何語(yǔ)法錯(cuò)誤胜茧,但關(guān)鍵的問(wèn)題是客戶端中出現(xiàn)的new關(guān)鍵字上粘优。因?yàn)椋粋€(gè)接口會(huì)有多個(gè)子類呻顽,對(duì)于上面的Furit接口雹顺,也可能出現(xiàn)多個(gè)子類對(duì)象。
來(lái)看范例芬位,我們多加上一個(gè)接口子類:
class Orange implements Fruit{
@Override
public void eat() {
System.out.println("吃橘子无拗。。昧碉。");
}
}
客戶端是若要得到這個(gè)新的子類對(duì)象英染,需要修改代碼為:
public class Client {
public static void main(String[] args) {
//Fruit f = new Apple();
Fruit f = new Orange();
f.eat();
}
}
從上面我們發(fā)現(xiàn),如果直接在客戶端上產(chǎn)生一個(gè)實(shí)例化對(duì)象被饿,那么我們每次要更換對(duì)象時(shí)四康,都需要修改客戶端代碼,這樣的做法明顯是不好的狭握。而在整個(gè)代碼中闪金,我們最關(guān)心的是如何取得一個(gè)Fruit接口對(duì)象,然后進(jìn)行方法的調(diào)用论颅,至于這個(gè)接口對(duì)象時(shí)被誰(shuí)實(shí)例化的哎垦,不是客戶端關(guān)心的。這個(gè)問(wèn)題就是代碼耦合度太高恃疯!耦合度太高的產(chǎn)生的直接問(wèn)題是代碼不方便維護(hù)漏设。
在本程序之中,最大的問(wèn)題在于耦合上今妄,發(fā)現(xiàn)在主方法中郑口,一個(gè)接口和一個(gè)子類緊密耦合在一起鸳碧,這種方式比較直接,可以簡(jiǎn)單的理解為由A —>B犬性,但是這種緊密的方式不方便于維護(hù)瞻离,所以我們可以這樣改:A—> C—>B,中間經(jīng)歷了一個(gè)過(guò)渡乒裆,這樣一來(lái)套利,B改變,然后C去改變缸兔,但是A不需要改變日裙。這可以參考Java中JVM的設(shè)計(jì)思想:程序—> JVM—>適應(yīng)不同的操作系統(tǒng)。
于是惰蜜,本程序我們這么修改昂拂,加上一個(gè)工廠類:
class Factory{
public static Fruit getInstance(String className){
if("apple".equals(className)){
return new Apple();
}else if("orange".equals(className)){
return new Orange();
}else{
return null;
}
}
}
然后修改客戶端:
public class Client {
public static void main(String[] args) {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
運(yùn)行結(jié)果:
吃蘋果。抛猖。格侯。
這樣的話,客戶端不會(huì)看見(jiàn)具體的子類财著,客戶端不再和一個(gè)具體的子類耦合在一起了联四,就算以后增加了新的子類,那么只需要修改Factory類即可實(shí)現(xiàn)撑教,客戶端的調(diào)用不會(huì)改變朝墩。
工廠模式的關(guān)系圖如下:
從工廠模式關(guān)系圖看出,客戶端不和具體的子類耦合在一起伟姐,若要增加新的子類收苏,只需要修改Factory類即可實(shí)現(xiàn)。
二愤兵、接口的應(yīng)用—代理設(shè)計(jì)模式(Proxy)
Java代理設(shè)計(jì)模式單獨(dú)講解鹿霸,請(qǐng)移步到 Java設(shè)計(jì)模式之代理模式
所謂代理,就是一個(gè)人或者機(jī)構(gòu)代表另一個(gè)人或者機(jī)構(gòu)采取行動(dòng)秆乳。在一些情況下懦鼠,一個(gè)客戶不想或者不能夠直接引用一個(gè)對(duì)象,而代理對(duì)象可以在客戶端和目標(biāo)對(duì)象之間起到中介的作用屹堰。
關(guān)系圖如下:
代理設(shè)計(jì)模式的核心精髓就在于:有一個(gè)主題操作接口(接口中可能有多個(gè)方法)肛冶,而核心業(yè)務(wù)主題只完成核心功能,而代理主題負(fù)責(zé)完成所有與核心主題有關(guān)的輔助性操作扯键。
三淑趾、Java抽象類和接口的區(qū)別
通過(guò)上面的分析可以得出結(jié)論:在開(kāi)發(fā)之中,抽象類和接口實(shí)際上都是可以使用的忧陪,并且使用那一個(gè)都沒(méi)有明確的限制扣泊,可是抽象類有一個(gè)最大的缺點(diǎn) : 一個(gè)子類只能夠繼承一個(gè)抽象類,存在單繼承的局限嘶摊。所以當(dāng)遇到抽象類和接口都可以使用的情況下延蟹,優(yōu)先考慮接口,避免單繼承局限叶堆。
一些參考原則(根據(jù)自身情況參考):
(1)在進(jìn)行某些公共操作的時(shí)候一定要定義出接口阱飘;
(2)有了接口就需要利用子類完善方法;
(3)如果是我們自己寫的接口虱颗,盡量不要使用關(guān)鍵字new去直接實(shí)例化接口子類沥匈,要使用工廠類完成。