Java コンパイル - コードの一部を無視するようにコンパイラーに指示する方法はありますか?
-
09-06-2019 - |
質問
私は Java Swing アプリケーションを保守しています。
Java 5 (Apple マシン用) との下位互換性のために、2 つのコードベースを維持しています。1 つは Java 6 の機能を使用し、もう 1 つはそれらの機能を使用しません。
コードは、Java 6 機能を使用する 3 ~ 4 つのクラスを除いて、ほとんど同じです。
1 つのコードベースだけを維持したいと考えています。コンパイル中に、Java 5 コンパイラーにコードの一部を「無視」させる方法はありますか?
Java コンパイラのバージョンに応じて、コードの一部を単にコメント/コメント解除することはしたくありません。
解決
クラスが 1.5 と同様の機能を持っていると仮定します。6.0 の実装の違いを 1 つのクラスにマージできます。そうすれば、ソースを編集してコメント/コメント解除しなくても、コンパイラが常に行う最適化を利用できます。if 式が常に false の場合、if ステートメント内のコードはコンパイルに含まれません。
クラスの 1 つに静的変数を作成して、実行するバージョンを決定できます。
public static final boolean COMPILED_IN_JAVA_6 = false;
次に、影響を受けるクラスでその静的変数をチェックし、コードのさまざまなセクションを単純な if ステートメントに配置します。
if (VersionUtil.COMPILED_IN_JAVA_6) {
// Java 6 stuff goes here
} else {
// Java 1.5 stuff goes here
}
他のバージョンをコンパイルする場合は、その 1 つの変数を変更して再コンパイルするだけです。Java ファイルが大きくなる可能性がありますが、コードが統合され、コードの重複が排除されます。エディターは、到達不能なコードなどについて文句を言うかもしれませんが、コンパイラーはそれを幸いにも無視する必要があります。
他のヒント
カスタム クラス ローダーと動的にコメントされたコードを使用するという提案は、メンテナンスと、新たな牧草地に足を踏み入れた後にプロジェクトを手に入れた哀れな魂の正気を保つことに関しては、少し信じられません。
解決策は簡単です。影響を受けるクラスを 2 つの独立したプロジェクトに抽出します。パッケージ名が同じであることを確認し、メイン プロジェクトで使用できる jar にコンパイルするだけです。パッケージ名とメソッドのシグネチャを同じにしておけば問題はありません。必要なバージョンの jar をデプロイメント スクリプトにドロップするだけです。別々のビルド スクリプトを実行するか、同じスクリプト内に別々のターゲットがあると仮定します。ant と Maven は両方とも、条件付きでファイルを取得してコピーすることを簡単に処理できます。
ここでの最善のアプローチは、おそらくビルド スクリプトを使用することだと思います。すべてのコードを 1 つの場所に置くことができ、含めるファイルと含めないファイルを選択することで、コンパイルするコードのバージョンを選択できます。ファイルごとよりも細かい制御が必要な場合、これは役に立たない可能性があることに注意してください。
おそらく、コードをリファクタリングして、条件付きコンパイルが実際に必要とされず、条件付きクラスロードだけが必要になるようにすることができます。このようなもの:
public interface Opener{
public void open(File f);
public static class Util{
public Opener getOpener(){
if(System.getProperty("java.version").beginsWith("1.5")){
return new Java5Opener();
}
try{
return new Java6Opener();
}catch(Throwable t){
return new Java5Opener();
}
}
}
}
バージョン固有のコードがいくつあるかによっては、これは多大な労力になる可能性があります。
JDK 5 でビルドする「マスター」ソース ルートを 1 つ保持します。JDK 6 以降でビルドする必要がある 2 番目の並列ソース ルートを追加します。(重複があってはなりません。つまり、両方にクラスは存在しません。) インターフェイスを使用して、2 つの間のエントリ ポイントを定義し、少しのリフレクションを行います。
例えば:
---%<--- main/RandomClass.java
// ...
if (...is JDK 6+...) {
try {
JDK6Interface i = (JDK6Interface)
Class.forName("JDK6Impl").newInstance();
i.browseDesktop(...);
} catch (Exception x) {
// fall back...
}
}
---%<--- main/JDK6Interface.java
public interface JDK6Interface {
void browseDesktop(URI uri);
}
---%<--- jdk6/JDK6Impl.java
public class JDK6Impl implements JDK6Interface {
public void browseDesktop(URI uri) {
java.awt.Desktop.getDesktop().browse(uri);
}
}
---%<---
異なる JDK などを使用して、これらを IDE 内の別個のプロジェクトとして構成できます。重要なのは、メインのルートは独立してコンパイルでき、どのルートで何を使用できるかが非常に明確であるのに対し、単一のルートの異なる部分を個別にコンパイルしようとすると、JDK 6 の使用法が誤って「漏洩」する可能性が非常に高いということです。間違ったファイルに。
このように Class.forName を使用する代わりに、ある種のサービス登録システム - java.util.ServiceLoader (メインで JDK 6 を使用でき、JDK 7 のオプションのサポートが必要な場合!)、NetBeans Lookup、Spring などを使用することもできます。等
同じ手法を使用して、新しい JDK ではなくオプションのライブラリのサポートを作成できます。
実際にはそうではありませんが、回避策はあります。見るhttp://forums.sun.com/thread.jspa?threadID=154106&messageID=447625
そうは言っても、少なくとも Java 5 用と Java 6 用に 1 つのファイル バージョンを用意し、必要に応じてビルドまたはメイクを介してそれらを含める必要があります。すべてを 1 つの大きなファイルにまとめて、5 のコンパイラに理解できないものを無視させようとするのは、良い解決策ではありません。
HTH
-- ニッキ --
これには Java 純粋主義者は全員うんざりするでしょう (これは楽しいです、ふふふ) が、私なら C プリプロセッサを使用し、ソースに #ifdefs を入れます。Makefile、rakefile、またはビルドを制御するものはすべて、cpp を実行してコンパイラーに供給する一時ファイルを作成する必要があります。アリにこれをさせることができるかどうかはわかりません。
stackoverflow はそうなるようですが、 の すべての答えがここにあるなら、誰もそれを心配していないことを気にすることはできません http://www.javaranch.com Javaの知恵のために。この質問はかなり前にそこで扱われたと思います。
それは、使用したい Java 6 機能によって異なります。JTable に行ソーターを追加するような単純な場合は、実行時に実際にテストできます。
private static final double javaVersion =
Double.parseDouble(System.getProperty("java.version").substring(0, 3));
private static final boolean supportsRowSorter =
(javaVersion >= 1.6);
//...
if (supportsRowSorter) {
myTable.setAutoCreateRowSorter(true);
} else {
// not supported
}
このコードは Java 6 でコンパイルする必要がありますが、どのバージョンでも実行できます (新しいクラスは参照されません)。
編集:より正確に言うと、1.3 以降のどのバージョンでも動作します ( このページ).
すべてのコンパイルを Java6 のみで実行し、System.getProperty("java.version") を使用して Java5 または Java6 コード パスを条件付きで実行できます。
クラス内に Java6 専用のコードを含めることができ、Java6 専用のコード パスが実行されない限り、クラスは Java5 上で正常に動作します。
これは、古い MSJVM から最新の Java Plug-in JVM まで動作するアプレットを作成するために使用されるトリックです。
Java にはプリコンパイラがありません。したがって、C のような #ifdef を実行する方法はありません。スクリプトをビルドするのが最良の方法です。
条件付きコンパイルは可能ですが、あまりうまくいきません。javac は到達不能なコードを無視します。したがって、コードを適切に構造化していれば、コンパイラにコードの一部を無視させることができます。これを適切に使用するには、javac が到達不能なコードをエラーとして報告したり、コンパイルを拒否したりしないように、正しい引数を javac に渡す必要もあります :-)
上記のパブリック静的最終ソリューションには、作者が言及していない追加の利点が 1 つあります。私の理解では、コンパイラーはコンパイル時にそれを認識し、その最終変数を参照する if ステートメント内のコードをコンパイルします。
したがって、それがあなたが探していた正確なソリューションだと思います。
簡単な解決策は次のとおりです。
- 分岐クラスを通常のクラスパスの外側に配置します。
- 単純なカスタム クラスローダーを作成し、デフォルトとして main にインストールします。
- 5/6 クラスを除くすべてのクラスでは、キャスローダーはその親 (通常のシステム クラスローダー) に従うことができます。
- 5/6 のもの (親によって見つからない唯一のものである必要があります) については、「os.name」プロパティまたは独自のプロパティを介してどちらを使用するかを決定できます。
リフレクションAPIを利用することができます。すべての 1.5 コードを 1 つのクラスに配置し、1.6 API を別のクラスに配置します。Ant スクリプトで 2 つのターゲットを作成します。1 つは 1.6 クラスをコンパイルしない 1.5 用、もう 1 つは 1.5 用のクラスをコンパイルしない 1.6 用です。コード内で Java のバージョンを確認し、リフレクションを使用して適切なクラスをロードすると、javac が機能の欠落について文句を言わなくなります。これは、Windows 上で MRJ (Mac Runtime for Java) アプリケーションをコンパイルする方法です。