【本文更新于2019年5月20日】
更新內(nèi)容是將例子講的更通俗易懂看政。
由于本文屬于中高階知識(shí)允蚣,其中涉及interface知識(shí)厉萝、類(lèi)的繼承、抽象類(lèi)榨崩、方法重載等基礎(chǔ)知識(shí)章母,同學(xué)可以先自行了解一下。
1. interface簡(jiǎn)介
interface是java中的一個(gè)關(guān)鍵字,用于定義接口類(lèi)秫逝,它最主要的作用封裝一定功能的集合违帆,被定義為接口的類(lèi)不能實(shí)例化刷后。
它的另外一個(gè)常見(jiàn)的用法尝胆,是用于回調(diào)功能含衔。
通俗點(diǎn)講贪染,接口類(lèi)它本身不具備具體的功能强经,只定義一些模版或者規(guī)范匿情,由子類(lèi)去實(shí)現(xiàn)具體的功能炬称。
Java 7 版本以前玲躯,接口類(lèi)只能定義一些抽象方法與常量,子類(lèi)必須重寫(xiě)接口類(lèi)中定義的全部方法朽缴。
這樣就有一個(gè)明顯的弊端密强,不需要實(shí)現(xiàn)的方法,也要在子類(lèi)重寫(xiě)薪鹦。
在Java 8 的時(shí)候距芬,JDK支持接口類(lèi)定義靜態(tài)方法和默認(rèn)方法舀武。
在Java 9 的時(shí)候,又進(jìn)行了改進(jìn)寻馏,支持私有方法和私有靜態(tài)方法的定義诚欠。
注意,這里我說(shuō)的是方法左腔,而不是抽象方法。也就是說(shuō)JDK8與JDK9可以在接口類(lèi)中定義方法體了鞭莽。
2. 三個(gè)JDK版本的接口設(shè)計(jì)功能對(duì)比
Java 7 | Java 8 | Java 9 |
---|---|---|
常量 | 常量 | 常量 |
抽象方法 | 抽象方法 | 抽象方法 |
默認(rèn)方法 | 默認(rèn)方法 | |
靜態(tài)方法 | 靜態(tài)方法 | |
私有方法 | ||
私有靜態(tài)方法 |
2.1 常量與抽象方法
常量與抽象方法是接口類(lèi)的基礎(chǔ)功能摇锋。
接口中定義的變量默認(rèn)是public static final 型,也就是一個(gè)常量融求,且必須給其初值县昂,實(shí)現(xiàn)類(lèi)中不能重新定義倒彰,也不能改變其值
抽象方法使用abstract修飾待讳,通常abstract省略不寫(xiě)。
如下代碼,是演示定義一個(gè)接口類(lèi)Test以及它的常量與抽象方法露乏。
public interface Test {
String str = "abc";
int a = 10;
abstract void test();
abstract int sum();
void test2();
}
2.2 默認(rèn)方法
java 8開(kāi)始支持,也稱(chēng)擴(kuò)展方法。
默認(rèn)方法使用default 關(guān)鍵字修飾、定義嫂便。
默認(rèn)方法需要寫(xiě)方法體践樱,實(shí)現(xiàn)具體的邏輯袱院。
實(shí)現(xiàn)類(lèi)可以不重寫(xiě)默認(rèn)方法,在需要的時(shí)候進(jìn)行重寫(xiě)集灌。
以下是實(shí)現(xiàn)的示例代碼:
//接口定義,并定一個(gè)默認(rèn)方法
public interface Test {
//默認(rèn)方法
default void testDefault(){
System.out.println("default method");
}
}
//第一個(gè)實(shí)現(xiàn)類(lèi)寂恬,不重寫(xiě)默認(rèn)方法
public class TestImpl implements Test {
}
//第二個(gè)實(shí)現(xiàn)類(lèi),重寫(xiě)默認(rèn)方法
public class TestImpl2 implements Test {
@Override
public void testDefault() {
System.out.println("TestImpl2 method");
}
}
//測(cè)試運(yùn)行
public class Run {
public static void main(String[] args) {
Test test = new TestImpl();
test.testDefault();
Test test2 = new TestImpl2();
test2.testDefault();
}
}
運(yùn)行結(jié)果:
沒(méi)有重寫(xiě)父類(lèi)的方法:default method
重寫(xiě)了父類(lèi)的方法:TestImpl2 method
【代碼解釋說(shuō)明】
接口類(lèi)Test中定義了一個(gè)默認(rèn)的方法testDefault()妄壶,實(shí)現(xiàn)了一些具體的功能氨淌,示例代碼中是打印一句話“default method”屑埋。
接著续崖,TestImpl與TestImpl2都實(shí)現(xiàn)了接口類(lèi)Test莺丑。
其中只有TestImpl2重寫(xiě)了默認(rèn)方法并修改了它的功能,示例代碼中是打印一句話“TestImpl2 method”。
運(yùn)行后的結(jié)果洪鸭,也正如所見(jiàn)镇饮,默認(rèn)方法俱济,可以不被子類(lèi)重寫(xiě)辖源,被子類(lèi)重寫(xiě)后浮梢,會(huì)執(zhí)行子類(lèi)所實(shí)現(xiàn)的具體邏輯洲尊。
總結(jié)一下:
1.JDK后它允許我們?cè)诮涌陬?lèi)里添加一個(gè)默認(rèn)方法。
2.默認(rèn)方法不會(huì)破壞實(shí)現(xiàn)這個(gè)接口的已有類(lèi)的兼容性裁蚁,也就是不會(huì)強(qiáng)迫接口的實(shí)現(xiàn)類(lèi)去重寫(xiě)默認(rèn)方法室谚。
3.java.util.Collection包中添加的stream(), forEach()等方法就是最好的例子。
2.3 靜態(tài)方法
JDK 8開(kāi)始支持
靜態(tài)方法使用static關(guān)鍵字修飾憎瘸、定義入篮。
靜態(tài)方法需要寫(xiě)方法體,實(shí)現(xiàn)具體的邏輯含思。
靜態(tài)方法不可以被子類(lèi)實(shí)現(xiàn)或繼承崎弃。
//定義接口、定義靜態(tài)方法
public interface TestFactory {
static Test createTest(int type){
if(type == 1){
return new TestImpl();
}else{
return new TestImpl2();
}
}
}
//測(cè)試運(yùn)行
public class Run {
public static void main(String[] args) {
Test test = TestFactory.createTest(1);
test.testDefault();
Test test2 = TestFactory.createTest(2);
test2.testDefault();
}
}
運(yùn)行結(jié)果:
default method
TestImpl2 method
【代碼解釋說(shuō)明】
接口類(lèi)TestFactory 定義了一個(gè)靜態(tài)方法 createTest(),功能是創(chuàng)建Test的對(duì)象實(shí)例并返回給調(diào)用者含潘。Test類(lèi)的代碼定義見(jiàn)2.2節(jié)饲做。
測(cè)試運(yùn)行代碼中,通過(guò)得到的Test類(lèi)的對(duì)象實(shí)例遏弱,成功調(diào)用其內(nèi)部的方法盆均。
2.4 默認(rèn)方法與靜態(tài)方法的關(guān)系
- 他們都是一個(gè)完整的方法體,各自都有具體的功能實(shí)現(xiàn)漱逸。
- 靜態(tài)方法使用static修飾泪姨,默認(rèn)方法使用default修飾。
- 默認(rèn)方法內(nèi)部可以調(diào)用靜態(tài)方法饰抒,靜態(tài)方法內(nèi)部不可以調(diào)用默認(rèn)方法肮砾,因?yàn)殪o態(tài)方法只能調(diào)用靜態(tài)方法,這是語(yǔ)法上的限制袋坑,很容易理解仗处,下面是默認(rèn)方法調(diào)用靜態(tài)方法的示例。
public interface Test {
//調(diào)用靜態(tài)方法
default void testDefault(){
testStatic();
System.out.println("default method");
}
static void testStatic(){
System.out.println("static method");
}
}
//測(cè)試運(yùn)行
public class Run {
public static void main(String[] args) {
Test test = TestFactory.createTest(1);
test.testDefault();
}
}
運(yùn)行結(jié)果:
static method
default method
2.5 私有方法與私有靜態(tài)方法
- java 9開(kāi)始支持
- 私有方法使用private關(guān)鍵字修飾枣宫、定義婆誓。
- 私有靜態(tài)方法使用private static關(guān)鍵字修飾、定義也颤。
- private與private static方法洋幻,只能接口自身內(nèi)部調(diào)用,實(shí)現(xiàn)類(lèi)或子類(lèi)不可重寫(xiě)重載翅娶。
- 都需要寫(xiě)方法體文留,實(shí)現(xiàn)具體的邏輯好唯。
他們的定義示例如下:
public interface Test {
private void test(){
System.out.println(" private method");
}
private static void test2(){
System.out.println(" private static method");
}
}
如果你問(wèn)我,私有方法與私有靜態(tài)方法只能自身內(nèi)部調(diào)用厂庇,是不是沒(méi)什么意義渠啊?
答案肯定是,不是的权旷!
其實(shí),它同class類(lèi)型對(duì)象的私有方法功能是一樣的贯溅。
- 可以提高代碼的重用性
- 不讓子類(lèi)或?qū)崿F(xiàn)類(lèi)調(diào)用拄氯,也有很好的安全性。
就拿一個(gè)數(shù)字簽名來(lái)說(shuō)它浅,有PKCS#1與PKCS#7簽名译柏。他們有共性就是簽名與驗(yàn)簽,這兩個(gè)功能的實(shí)現(xiàn)都需要數(shù)字證書(shū)姐霍,其中數(shù)字證書(shū)的解析功能等鄙麦,不需要子類(lèi)去實(shí)現(xiàn),也不希望他們調(diào)用镊折,避免遭破壞胯府。那么就可以定義為私有方法。
大致過(guò)程如下:
- 1.需要實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)抽象方法恨胚,設(shè)置簽名的類(lèi)型骂因、證書(shū)等
- 2.接口將簽名的具體邏輯使用私有方法達(dá)到保護(hù)以及可重用
下面拿代碼說(shuō)明,首先定義簽名接口類(lèi)
public interface Signature {
int SIGN_PKCS1 = 0;
int SIGN_PKCS7 = 1;
//設(shè)置簽名類(lèi)型 p1或者P7
int setType();
//設(shè)置簽名的數(shù)字證書(shū)
String setCert();
//具體的簽名方法,簽名邏輯用私有方法
default String sign(){
if(!checkCert()) return null;
int type = setType();
if(type == SIGN_PKCS1){
return pkcs1();
}else {
return pkcs7();
}
}
private Object parseCert(){
//解析證書(shū)
return obj;
}
private boolean checkCert(){
//檢驗(yàn)證書(shū)是否過(guò)期赃泡、是否吊銷(xiāo)
parseCert();
return true;
}
private String pkcs1(){
return "P1 簽名結(jié)果";
}
private String pkcs7(){
return "P7 簽名結(jié)果";
}
}
接口類(lèi)定義了簽名類(lèi)型寒波、簽名方法、檢驗(yàn)證書(shū)等具體的私有方法升熊, sign()方法為默認(rèn)方法俄烁,實(shí)現(xiàn)類(lèi)可以重寫(xiě),也可不寫(xiě)级野。這樣子類(lèi)只需要設(shè)置類(lèi)型和證書(shū)即可页屠,沒(méi)有額外的代碼∩撞看代碼:
public class SignatureP1 implements Signature {
@Override
public int setType() {
return SIGN_PKCS1;
}
@Override
public String setCert() {
return null;
}
}
public class SignatureP7 implements Signature {
@Override
public int setType() {
return SIGN_PKCS7;
}
@Override
public String setCert() {
return null;
}
}
有了默認(rèn)方法和私有方法卷中,實(shí)現(xiàn)類(lèi)不需要強(qiáng)制實(shí)現(xiàn)各自公共的邏輯。交由接口類(lèi)來(lái)實(shí)現(xiàn)渊抽。最后使用的方式為:
public class Run {
public static void main(String[] args) {
Signature signature1 = new SignatureP1();
signature1.sign();
Signature signature7 = new SignatureP7();
signature7.sign();
}
}
運(yùn)行結(jié)果:
P1 簽名結(jié)果
P7 簽名結(jié)果
有人說(shuō)蟆豫,這他喵的不就是抽象類(lèi)嗎!別說(shuō)懒闷,還真像十减。
他們有什么區(qū)別呢栈幸?請(qǐng)看下面我總結(jié)的幾點(diǎn):
他們都是抽象類(lèi)型,都可以定義抽象方法帮辟,都有默認(rèn)方法速址,不強(qiáng)制實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)。
類(lèi)是單繼承由驹,接口可以多實(shí)現(xiàn)芍锚。
設(shè)計(jì)理念的不同,抽象類(lèi)所表現(xiàn)的關(guān)系是"is"關(guān)系蔓榄,接口所表現(xiàn)的是"like"關(guān)系并炮,有點(diǎn)像python中的鴨子類(lèi)型,當(dāng)看到一只鳥(niǎo)走起來(lái)像鴨子甥郑、游泳起來(lái)像鴨子逃魄、叫起來(lái)也像鴨子,那么這只鳥(niǎo)就可以被稱(chēng)為鴨子澜搅。而抽象類(lèi)不行伍俘,必須是鴨子。
前文中提到過(guò)勉躺,接口中定義的變量公開(kāi)的靜態(tài)常量癌瘾,且必須給其初值,實(shí)現(xiàn)類(lèi)中不能重新定義赂蕴,也不能改變其值柳弄;抽象類(lèi)中的變量其值可以在子類(lèi)中重新定義,也可以重新賦值概说。
說(shuō)到這里碧注,另外強(qiáng)調(diào)接口的兩個(gè)問(wèn)題,一個(gè)是自身方法調(diào)用以及多實(shí)現(xiàn)的規(guī)則。
3. 方法互相調(diào)用問(wèn)題
默認(rèn)方法(default)可以調(diào)用abstract/private/static/private static方法糖赔。
而static方法只能調(diào)用static/private static方法萍丐。
調(diào)用關(guān)系不對(duì),編譯會(huì)不過(guò)放典。
4. 多實(shí)現(xiàn)的沖突問(wèn)題
因?yàn)橐粋€(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口逝变,若是這些接口定義的方法存在一樣的,便會(huì)有沖突奋构。因?yàn)檫@樣壳影,子類(lèi)在調(diào)用的時(shí)候,不知道調(diào)用哪一個(gè)接口定義的方法弥臼。
像這樣宴咧,有兩種情況下會(huì)發(fā)生:
- 兩個(gè)接口沒(méi)有任何關(guān)系,有相同的方法径缅,一個(gè)類(lèi)同時(shí)實(shí)現(xiàn)了這兩個(gè)接口
- 一個(gè)接口繼承了另一個(gè)接口掺栅,一個(gè)類(lèi)同時(shí)實(shí)現(xiàn)這兩個(gè)接口
4.1 一個(gè)類(lèi)實(shí)現(xiàn)多個(gè)沒(méi)有任何關(guān)系的接口
如下代碼烙肺,TestC實(shí)現(xiàn)了TestA,TestB,而TestA,TestB中存在相同的方法
public interface TestA {
default void test(){
System.out.println("TestA");
}
}
public interface TestB{
default void test(){
System.out.println("TestB");
}
}
public class TestC implements TestA,TestB {
}
此時(shí)氧卧,會(huì)出現(xiàn)編譯錯(cuò)誤:
inherits unrelated defaults for test() from TestA and TestB
也就是說(shuō)TestA 和 TestB 都有這個(gè) test()方法桃笙,不知道要實(shí)現(xiàn)哪一個(gè)。
解決方法是重寫(xiě)這個(gè)相同的方法沙绝,方法內(nèi)部指定一個(gè)具體的接口作為實(shí)現(xiàn)方法搏明,下面代碼指定TestA 作為實(shí)現(xiàn)方法。
當(dāng)然也可以不指定父類(lèi)接口的實(shí)現(xiàn)闪檬,重寫(xiě)自己的邏輯熏瞄。
public class TestC implements TestA,TestB {
@Override
public void test() {
TestA.super.test();
}
}
進(jìn)行測(cè)試:
public class Run {
public static void main(String[] args) {
TestC testC = new TestC();
testC.test();
}
}
運(yùn)行結(jié)果
TestA
4.2 同時(shí)實(shí)現(xiàn)繼承關(guān)系的兩個(gè)接口
這種沖突,不能指定一個(gè)父接口來(lái)解決了谬以,要么重寫(xiě)實(shí)現(xiàn)邏輯,要么按照默認(rèn)的規(guī)則來(lái)調(diào)用由桌,默認(rèn)規(guī)則如下:
- 聲明在類(lèi)里面的方法優(yōu)先于任何默認(rèn)的方法为黎,也就是,在實(shí)現(xiàn)類(lèi)中重寫(xiě)這個(gè)相同的方法行您,調(diào)用的時(shí)候铭乾,優(yōu)先級(jí)最高。
- 如果默認(rèn)方法沒(méi)有在類(lèi)中實(shí)現(xiàn)娃循,優(yōu)先選取最具體的實(shí)現(xiàn)
重寫(xiě)的優(yōu)先級(jí)最高炕檩,我們來(lái)看優(yōu)先選取最具體的實(shí)現(xiàn),接口定義如下:
public interface TestA {
default void test(){
System.out.println("TestA");
}
}
public interface TestB extends TestA{
default void test(){
System.out.println("TestB");
}
}
public class TestC implements TestA,TestB {
}
TestC 實(shí)現(xiàn)了TestB,TestB并且沒(méi)用重寫(xiě)test()方法,TestB繼承了TestA捌斧。測(cè)試運(yùn)行:
public class Run {
public static void main(String[] args) {
TestC testC = new TestC();
testC.test();
}
}
運(yùn)行結(jié)果
TestB
鑒于此笛质,接口不能提供對(duì)Object類(lèi)的任何方法的默認(rèn)實(shí)現(xiàn),如接口里不能提供對(duì)equals捞蚂,hashCode以及toString的默認(rèn)實(shí)現(xiàn)妇押。
因?yàn)椋粋€(gè)類(lèi)實(shí)現(xiàn)了這個(gè)接口并且是Object的子類(lèi)姓迅,已經(jīng)有了equals/hashCode/toString等方法的實(shí)現(xiàn)敲霍,那么接口定義的就沒(méi)有意義了。
在類(lèi)里實(shí)現(xiàn)的方法丁存,調(diào)用優(yōu)先級(jí)最高肩杈。
5. 總結(jié)
- 掌握J(rèn)DK7\8\9三個(gè)版本中接口類(lèi)的變化
- JDK 9之后,接口中支持定義方法可以包括私有的解寝、抽象的扩然、默認(rèn)的、靜態(tài)的编丘。
- 一個(gè)類(lèi)實(shí)現(xiàn)了多接口時(shí)与学,若存在相同方法彤悔,注意他們的調(diào)用方式和沖突。