オーバーロードされた package-private メソッドによりコンパイルが失敗する - これは JLS の異常ですか、それとも javac のバグですか?
-
13-09-2019 - |
質問
JLS の奇妙な点、または JavaC のバグ (どちらかはわかりません) に遭遇しました。以下を読んで、必要に応じて JLS の一節または Sun Bug ID を引用して説明を行ってください。
3 つの「モジュール」のコードを含む人為的なプロジェクトがあるとします。
- API - フレームワーク API を定義します - サーブレット API を考えます
- Impl - API 実装を定義します - Tomcat サーブレット コンテナを考えてください
- アプリ - 私が書いたアプリケーション
各モジュールのクラスは次のとおりです。
API - MessagePrinter.java
package api;
public class MessagePrinter {
public void print(String message) {
System.out.println("MESSAGE: " + message);
}
}
API - MessageHolder.java
(はい、「impl」クラスを参照します - これについては後で詳しく説明します)
package api;
import impl.MessagePrinterInternal;
public class MessageHolder {
private final String message;
public MessageHolder(String message) {
this.message = message;
}
public void print(MessagePrinter printer) {
printer.print(message);
}
/**
* NOTE: Package-Private visibility.
*/
void print(MessagePrinterInternal printer) {
printer.print(message);
}
}
インプル - MessagePrinterInternal.java
- このクラスは API クラスに依存します。名前が示すように、これは私の小さなフレームワークの他の場所で「内部」で使用することを目的としています。
package impl;
import api.MessagePrinter;
/**
* An "internal" class, not meant to be added to your
* application classpath. Think the Tomcat Servlet API implementation classes.
*/
public class MessagePrinterInternal extends MessagePrinter {
public void print(String message) {
System.out.println("INTERNAL: " + message);
}
}
最後に、App モジュールの唯一のクラス...MyApp.java
import api.MessageHolder;
import api.MessagePrinter;
public class MyApp {
public static void main(String[] args) {
MessageHolder holder = new MessageHolder("Hope this compiles");
holder.print(new MessagePrinter());
}
}
そこで、小さなアプリケーション MyApp.java をコンパイルしてみます。私の API jar が jar (たとえば、api.jar) 経由でエクスポートされているとします。善良な市民である私は、クラスパスでその jar のみを参照します。impl.jar に同梱されている Impl クラスは参照しません。
API クラスが「内部」実装クラスに依存すべきではないという点で、私のフレームワーク設計には明らかに欠陥があります。しかし、驚いたのは、MyApp.java がまったくコンパイルされなかったことです。
javac -cp api.jar src\MyApp.java
src\MyApp.java:11: cannot access impl.MessagePrinterInternal class file for impl.MessagePrinterInternal not found
holder.print(new MessagePrinter());
^
1 error
問題は、メソッドのオーバーロードが原因で、コンパイラが使用する print() のバージョンを解決しようとしていることです。ただし、メソッドの 1 つがパッケージプライベートであるため、MyApp には表示されないため、このコンパイル エラーは多少予期せぬものです。
では、これは javac のバグでしょうか、それとも JLS の何らかの異常でしょうか?
コンパイラ:サン Javac 1.6.0_14
解決
がありJLSかのjavacと間違って何もありません。もちろん、これは私があなたの説明の権利を理解していれば、コンパイルのクラスパス上にないクラスのMessageHolder
参照のMessagePrinterInternal
ので、コンパイルされません。あなたのAPIのインターフェイスを持つ例えば、実装にこのリファレンスを破る必要があります。
EDIT 1:明確化のために:これは、あなたが考えているようだとして、パッケージ可視メソッドとは何の関係もありません。問題は、型MessagePrinterInternal
をコンパイルするために必要とされていますが、クラスパスにそれを持っていないということです。あなたはそれが参照クラスへのアクセスを持っていないときに、ソースコードをコンパイルするjavacのを期待することはできません。
EDIT 2:私は再びコードを再読し、これが起こっているように見えるものです:のMyAppがコンパイルされると、クラスMessageHolderをロードしようとします。クラスMessageHolderはMessagePrinterInternalを参照するので、それはまた、それをロードしようと失敗します。私はそれがまた、JVMに依存する場合があります、それはJLSで指定されていることを確認していません。 Sun JVMの私の経験では、クラスがロードされたときに利用できる、少なくともすべての静的に参照されるクラスを持っている必要があります。すなわち、フィールド、メソッドシグネチャに何か、拡張classses、実装インターフェイスのタイプを含みます。あなたは、これは直感的であると主張することもできますが、私は、一般的に、あなたがそのような情報が欠落しているクラスで行う非常に少ないがあることを応答する:あなたがオブジェクトをインスタンス化することはできません、あなたがなどのメタデータ(Classオブジェクト)を使用することはできませんその背景知識が、私はあなたが見る動作が期待されていると言うでしょう。
他のヒント
最初はオフ私はインタフェースではなくクラス(名前に基づいて)であることをAPIパッケージで物事を期待します。一度これを行うあなたはインタフェースパッケージへのアクセスを持つことができないので、問題が消えます。
次のことは、私の知る限り、これがJava風変わり(それはあなたが望む何をしないように)である、ということです。あなたが公共の方法を取り除くとプライベートでパッケージを作成する場合は、同じものを取得します。
のインターフェイスは、あなたの問題を解決し、あなたのコード内でクリーンな分離を与えるようにAPIパッケージ内のすべてを変えるます。
私は、あなたはいつものjavacは少し賢くできることを主張することができますね、それがどこかに停止することがあります。それは人間ではないですが、人間は常にコンパイラより賢くすることができ、あなたは常に人間のための完全な意味を成しますが、コンパイラを度胆を抜く例を見つけることができます。
私はこの問題に関する正確な仕様を知らない、と私はjavacの作成者は、ここで任意のミスを犯した疑い。しかし、誰が気に?なぜそれらのいくつかは表面的であっても、クラスパスにすべての依存関係を入れませんか?それを行うことは一貫して、の私たちのの作る非常に簡単に住んでます。