2016-12-16 59 views
12

我有follwing爪哇9模塊:爲什麼泄漏內部類型的公共API的編譯不會失敗?

module com.example.a { 
    exports com.example.a; 
} 

與導出類型:

public class Api { 

    public static void foo(ImplDetail args) {} 
} 

和非導出的類型:

package com.example.b.internal; 

public class ImplDetail {} 

導出的類型使用非導出類型作爲公共方法中的方法參數類型。我假定編譯器會拒絕這種不一致的類配置,因爲其他模塊中的客戶端無法真正調用foo()方法,因爲它們不能實例化參數類型。

令我驚訝的是,這個模塊由javac編譯成功。我可以看到通過null的特例,我仍然認爲這樣的API定義格式不正確,認爲它不應該被支持,理想地由編譯器強制執行。

不禁止這種情況的原因是什麼?

回答

8

當然,在API中使用非導出類型是不好的風格,很可能是設計錯誤,但對我來說相當清楚的是,javac不能使其成爲編譯時錯誤。

請注意,始終可以在公共API中使用私有類型,一直回到Java 1.0。

您已經注意到模塊外部的代碼仍然可以調用Api.foo(null)

還有其他情況下,調用者仍然可以使用此API和非空引用。考慮包com.example.a中的類public class Sub extends ImplDetail。此類Sub是公開的,並且已導出,因此可用於模塊外部的代碼。因此,外部代碼可以使用從某處獲得的Sub實例調用Api.foo(sub)

但是,當然,javac可以告訴在任何導出的軟件包中是否有ImplDetail的子類型,並且如果沒有任何導出的軟件包會發出編譯時錯誤?不必要。由於可以單獨編譯,所以編譯步驟後可能會將新類引入到模塊中,其中包括Api。或者,對於這個問題,可以重新編譯module-info.class文件來更改導出包的集合。

由於這些原因,我認爲在編譯Api類時,javac提出錯誤是不合適的。 Javac確實有一個選項-Xlint:exports,但會將這種情況標記爲警告。

構建過程中的某些內容(如jmod工具或一些事後模塊審覈工具)也可以標記在導出的API中使用的非導出類型的使用。不過,我認爲目前沒有這樣做。

+0

感謝您的詳細解答,斯圖爾特!從一個非導出類型派生一個導出的類型似乎同樣不一致,所以我會說單獨應該保證編譯錯誤(對於Sub本身)。但是可能會有幾個編譯步驟中描述的問題。 – Gunnar

+0

@Gunnar在考慮了一段時間之後,我意識到擁有一個作爲私人類的子類的公共類是合理的(如果不常見)。例如,在JDK中,在java.lang中,'StringBuffer'和'StringBuilder'都是'AbstractStringBuilder'的公共子類,它是一個私有類。具有內部私有類的層次結構SB <:ASB <:Object有點奇怪,但它是爲了實現共享而完成的。但請注意,ASB不作爲API中的類型公開。 –

+0

感謝您的跟進。就我個人而言,我覺得這樣的設計很尷尬。一個類型的用戶應該能夠看到IMO類型的完整層次結構。如果是關於實現共享,可以通過委派給一個私有共享類來完成。 – Gunnar