新聞中心
Java 8的默認(rèn)方法試圖嘗試更進(jìn)一步簡化Java API。不幸的是,這一最近的語言擴(kuò)展帶來了一系列復(fù)雜的規(guī)則,但只有少部分Java開發(fā)者意識到這一點(diǎn)。這篇文章告訴你為什么引入默認(rèn)方法會破壞你的(用戶的)代碼。

創(chuàng)新互聯(lián) - 成都電信服務(wù)器托管,四川服務(wù)器租用,成都服務(wù)器租用,四川網(wǎng)通托管,綿陽服務(wù)器托管,德陽服務(wù)器托管,遂寧服務(wù)器托管,綿陽服務(wù)器托管,四川云主機(jī),成都云主機(jī),西南云主機(jī),成都電信服務(wù)器托管,西南服務(wù)器托管,四川/成都大帶寬,機(jī)柜大帶寬、租用·托管,四川老牌IDC服務(wù)商
起初看來,默認(rèn)方法給Java虛擬機(jī)的指令集帶來了很多新的特性。最終,開發(fā)庫的人能夠在不帶來客戶端代碼的兼容性問題的情況下,升級API。使用 默認(rèn)方法,任何實(shí)現(xiàn)庫接口的類都自動適應(yīng)接口引入的默認(rèn)方法。一旦用戶更新了他實(shí)現(xiàn)的類,就能夠很簡單使用更有意義的方法來覆蓋原有默認(rèn)方法。更好的是, 用戶可以在覆蓋方法時候,調(diào)用接口的默認(rèn)實(shí)現(xiàn),同時增加業(yè)務(wù)邏輯。
到現(xiàn)在為止,一切都是很好。但是,在創(chuàng)建接口的時候增加默認(rèn)方法可能使得Java代碼不兼容。這個從下面的例子可以很容易弄明白。我們假設(shè)一個庫需要它的一個接口的作為輸入:
- interface SimpleInput {
- void foo();
- void bar();
- }
- abstract class SimpleInputAdapter implements SimpleInput {
- @Override
- public void bar() {
- // some default behavior ...
- }
- }
Java 8之前,類似于上面聯(lián)合使用一個接口和一個適配器類的方式,是Java程序語言中一種非常常用的設(shè)計(jì)模式。該適配器通常由庫提供者提供,用于節(jié)省庫的使用者的某些操作。但是,如果采用接口的方式提供,就類似允許多重繼承了。
我們進(jìn)一步假設(shè)一個用戶使用了如下的適配器:
- class MyInput extends SimpleInputAdapter {
- @Override
- public void foo() {
- // do something ...
- }
- @Override
- public void bar() {
- super.bar();
- // do something additionally ...
- }
- }
通過這種實(shí)現(xiàn)方式,我們最終可以和庫進(jìn)行交互。注意我們是怎樣覆蓋bar方法,并為默認(rèn)的實(shí)現(xiàn)增加額外的功能的。
如果將該庫移植到Java 8,將會發(fā)生什么呢?首先,該庫很大可能性會廢棄適配器類,而使用默認(rèn)方法提供該功能。最終,該接口的形式類似如下所示:
- interface SimpleInput {
- void foo();
- default void bar() {
- // some default behavior
- }
- }
使用這個新的接口,用戶可以更新他的代碼,采用默認(rèn)方法來代替原來的適配器類。通過使用接口代替適配器類的***的結(jié)果是,該類可以繼承 (extend)其它的類,而不是特定的適配器?,F(xiàn)在我們進(jìn)行實(shí)踐,移植MyInput類使其使用默認(rèn)方法。因?yàn)槲覀儸F(xiàn)在能繼承其它類了,所以我們繼承一 個第三方的基礎(chǔ)類。我們這里不需要關(guān)心這個基礎(chǔ)類的作用,我們可以假設(shè)這個對我們的功能是有意義的。
- class MyInput extends ThirdPartyBaseClass implements SimpleInput {
- @Override
- public void foo() {
- // do something ...
- }
- @Override
- public void bar() {
- SimpleInput.super.bar();
- // do something additionally ...
- }
- }
為了實(shí)現(xiàn)原始類相似的功能,我們使用Java 8的新的語法來調(diào)用指定接口的默認(rèn)方法。同時,將我們方法中的一些邏輯移到基礎(chǔ)類中去。此時,你可能拍著我的肩膀說,這是一次非常好的重構(gòu)!
我們相當(dāng)成功的使用了該庫。但是,維護(hù)人員需要增加另一個接口來提供更多的功能。該接口被 ComplexInput 接口所代替,這個接口繼承自 SimpleInput 接口,并增加了新的方法。因?yàn)槟J(rèn)方法通常來說是可以很安全的添加的,因此,維護(hù)人員覆蓋了 SimpleInput 的默認(rèn)方法,提供了一個更好的默認(rèn)方法。畢竟,這對于采用適配器類的方式來說是很平常的事情。
- interface ComplexInput extends SimpleInput {
- void qux();
- @Override
- default void bar() {
- SimpleInput.super.bar();
- // so complex, we need to do more ...
- }
- }
新的特性帶來了非常好的效果以至于維護(hù) ThirdPartyBaseClass 的人也決定依賴該庫。為了完成這項(xiàng)工作,它在 ThirdPartyLibrary 中實(shí)現(xiàn)了 ComplexInput 接口。
但是這對 MyInput 類來說意味著什么呢?為了隱式的實(shí)現(xiàn) ComplexInput 接口,可繼承 ThirdPartyBaseClass 類,但是調(diào)用 SimpleInput 的默認(rèn)方法突然變成非法的了。結(jié)果,用戶的代碼不能通過編譯。現(xiàn)在這種調(diào)用是被禁止的,因?yàn)镴ava認(rèn)為這種在非直接子類中調(diào)用父類的父類的方法是非法 的。你只能在 ComplexInput 中去調(diào)用該默認(rèn)方法,但是,這要求你顯示的在MyInput中實(shí)現(xiàn)該接口。對于庫的用戶來說,這種改變不是所預(yù)期的!
更奇怪的是,Java運(yùn)行時卻不做這種限制。JVM的校驗(yàn)器是允許一個編譯好的類去調(diào)用 SimpleInput::foo 方法的,即使該類是通過繼承更新后的 ThirdPartyBaseClass,從而隱式的實(shí)現(xiàn)了ComplexClass。這種限制只存在于編譯器中。
我們從這里能學(xué)到什么東西呢?簡單的說,確保不要在一個接口中覆蓋另一個接口的默認(rèn)方法,既不要用默認(rèn)方法覆蓋,也不要用抽象方法覆蓋??偟膩碚f, 請謹(jǐn)慎使用默認(rèn)方法。即使它使得Java的集合接口API輕易的發(fā)生了革命性的變化,但本質(zhì)上講,這種繼承層級之間的方法調(diào)用,增加系統(tǒng)的復(fù)雜性。而在 Java 7之前,你只需要沿著線性的類層級去查找真正調(diào)用的代碼。只有當(dāng)你覺得非常有必要的時候才去增加這種復(fù)雜性。
當(dāng)前名稱:Java8默認(rèn)方法會破壞你的(用戶的)代碼
標(biāo)題URL:http://m.fisionsoft.com.cn/article/dhcjici.html


咨詢
建站咨詢
