Java 8默認方法致代碼不兼容,淺析

默認方法給JVM的指令集增長了一個很是不錯的新特性。使用了默認方法以後,若是庫中的接口增長了新的方法,實現了這個接口的用戶類可以自動得到這個方法的默認實現。一旦用戶想更新他的實現類的話,只需覆蓋一下這個默認方法就能夠了,取而代之的是一個在特定場景下更有意義的實現。更棒的是,用戶能夠在重寫的方法裏面調用接口的默認實現來增長一些額外的功能。html

目前爲止一切都還不錯。然而,給現有的Java接口增長默認方法可能會致使代碼的不兼容。看個例子就很容易能明白了。假設有一個庫,它須要用戶實現它的一個接口做爲輸入:java

1
2
3
4
5
6
7
8
9
10
11
interface SimpleInput {
   void foo();
   void bar();
}
  
abstract class SimpleInputAdapter  implements SimpleInput {
   @Override
   public void bar() {
     // some default behavior ...
   }
}

在Java 8之前,上述這種接口和一個對應的適配器類的組合在Java語言中是一種很常見的模式。類庫的開發人員提供了一個適配器來減小庫使用者的編碼量。然而提供這個接口的目的實際上是爲了能實現某種相似多重繼承的關係。web

咱們假設有一個用戶使用了這個適配器:ide

1
2
3
4
5
6
7
8
9
10
11
class MyInput  extends SimpleInputAdapter{
   @Override
   public void foo() {
     // do something ...
   }
   @Override
   public void bar() {
     super .bar();
     // do something additionally ...
   }
}

有了這個實現,用戶能夠和庫進行交互了。注意這個實現是如何重寫bar方法來給默認的實現增長額外的功能的。工具

那若是這個庫遷移到Java 8的話會怎樣?首先,這個庫極可能會廢棄掉這個適配器類並將這個功能遷移到默認方法裏。最終這個接口看起來會是這樣的:開發工具

interface SimpleInput {
  void foo();
  default void bar() {
    // some default behavior
  }}

有了這個新接口後,用戶得更新他的代碼來使用這個默認方法,而再也不是適配器類了。使用新接口而非適配器類的一大好處就是,用戶能夠去繼承一個別的類而不是這個適配器類了。咱們來動手實踐一下,將MyInput類改形成使用默認方法。因爲如今咱們能夠繼承別的類了,咱們再額外地擴展一個第三方的基類試試。這個基類具體是作什麼的在這裏並不重要,咱們先假設一下這麼作對咱們這個用例來講是有意義的。測試

1
2
3
4
5
6
7
8
9
10
11
class MyInput  extends ThirdPartyBaseClass  implements SimpleInput {
   @Override
   public void foo() {
     // do something ...
   }
   @Override
   public void bar() {
     SimpleInput. super .foo();
     // do something additionally ...
   }
}

爲了實現和原先那個類一樣的功能,這裏咱們用到了Java 8的新語法來調用接口的默認方法。一樣的,咱們把myMethod的邏輯放到某個基類MyBase裏面。能夠捶捶肩膀放鬆下了。重構以後棒極了!編碼

咱們使用的這個庫獲得了很大的改進。然而,維護人員須要添加另外一個接口來實現一些額外的功能。這個接口叫作CompexInput ,它繼承了SimpleInput類,並增長了一個額外的方法。因爲一般都認爲默認方法是能夠放心地添加的,所以維護人員重寫了SimpleInput類的默認方法並添加了一些額外的動做來給用戶提供一個更好的默認實現。畢竟使用適配器類的時候這個作法也十分常見:spa

1
2
3
4
5
6
7
8
interface ComplexInput  extends SimpleInput {
   void qux();
   @Override
   default void bar() {
     SimpleInput. super .bar();
     // so complex, we need to do more ...
   }
}

這個新特性看起來很是不錯,所以ThirdPartyBaseClass類的維護人員也決定使用這個庫了。爲了實現這個,他將ThirdPartyBaseClass類實現了ComplexInput接口。code

但這樣的話對MyInput類意味着什麼?因爲它繼承了ThirdPartyBaseClass類,所以默認實現了ComplexInput接口,這樣的話調用SimpleInput的默認方法就不合法了。結果就是,用戶的代碼最後沒法經過編譯。還有就是,如今已經完全沒法調用這個方法了,由於Java把這種調用間接父類的super-super方法認爲是不合法的。你只能去調用ComplexInput接口的默認方法了。然而這首先須要你在MyInput類中顯式的實現一下這個接口。對於這個庫的用戶而言,這些改動徹底是意想不到的。

(注:簡單點說其實就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface A {
     default void  test() {
         
     }
}
 
interface B  extends A {
     default void test() {
         
     }
}
 
public class Test  implements B {
     public void test() {
         B. super .test();
         //A.super.test();  錯誤
     }
}

固然這麼寫的話是用戶主動選擇實現了B接口,而文中的例子因爲引入了一個基類,所以因爲庫和基類中都進行了一個看似沒有影響的改動,實際上卻致使用戶代碼沒法經過編譯)

很奇怪的是,Java在運行時並無對這個進行區分。JVM的校驗器容許一個編譯過的類進行SimpleInput::foo方法的調用,儘管加載的這個類繼承了ThirdPartyBaseClass的更新版本後隱式地實現了ComplexInput接口。要怪只能怪編譯器了。(注:編譯器與運行時的行爲不一致)

那咱們從中學到了什麼?簡單地說,不要在另外一個接口中重寫原接口的默認方法。不要用另外一個默認方法來重寫它,也不要某個抽象方法來重寫它。總而言之,使用默認方法時應當十分謹慎。雖然它們使得Java現有的集合庫的接口更容易改進了,但它容許你在類的繼承結構中進行方法調用,這本質上實際上是增長了複雜性。在Java 7之前,你只需遍歷線性的類層次結構看一下實際調用的代碼就能夠了。當你以爲的確須要的時候,再去使用默認方法。

舒適小提示:好的資源,在 Java開發中能事半功倍!

原文轉載至:http://it.deepinmind.com/java/2014/05/19/java-8-default-methods-can-break-your-code.html

業界被公認爲最好的Java開發平臺:IntelliJ IDEA 

最實惠、綜合全面的J2EE IDE與Web開發工具套件:MyEclipse

多平臺Java安裝文件生成工具:install4j

全面測試Java程序的工具:Parasoft Jtest

相關文章
相關標籤/搜索