Javaコードはどのバージョンですか?

2003年5月23日

Q:Q:

A:

public class Hello {public static void main(String [] args){StringBuffer Greeting = new StringBuffer( "hello、"); StringBuffer who = new StringBuffer(args [0])。append( "!"); Greeting.append(who); System.out.println(挨拶); }} //クラスの終わり

最初、質問はかなり些細なようです。そのため、Helloクラスに含まれるコードはほとんどなく、Java1.0にまでさかのぼる機能のみが使用されます。したがって、クラスはほぼすべてのJVMで問題なく実行されるはずですよね?

よくわからない。Java 2 Platform、Standard Edition(J2SE)1.4.1のjavacを使用してコンパイルし、以前のバージョンのJavaランタイム環境(JRE)で実行します。

> ... \ jdk1.4.1 \ bin \ javac Hello.java> ... \ jdk1.3.1 \ bin \ java Helloworldスレッド "main"の例外java.lang.NoSuchMethodErrorat Hello.main(Hello.java:20 ) 

ソースが100%Java 1.0と互換性がある場合でも、予期される「hello、world!」の代わりに、このコードはランタイムエラーをスローします。また、エラーは予想どおりではありません。クラスバージョンの不一致の代わりに、メソッドの欠落について何らかの理由で文句を言います。困惑?もしそうなら、あなたはこの記事の後半で完全な説明を見つけるでしょう。まず、議論を広げましょう。

なぜ異なるJavaバージョンを気にするのですか?

Javaはプラットフォームにまったく依存せず、ほとんどが上位互換性があるため、特定のJ2SEバージョンを使用してコードをコンパイルし、それ以降のJVMバージョンで機能することを期待するのが一般的です。(Java構文の変更は通常、バイトコード命令セットに実質的な変更を加えることなく発生します。)この状況での問題は、コンパイル済みアプリケーションでサポートされるある種のベースJavaバージョンを確立できますか、それともデフォルトのコンパイラ動作は受け入れられますか?私の推薦については後で説明します。

もう1つのかなり一般的な状況は、目的のデプロイメントプラットフォームよりもバージョンの高いコンパイラを使用することです。この場合、最近追加されたAPIは使用せず、ツールの改善によるメリットを享受したいだけです。このコードスニペットを見て、実行時に何をすべきかを推測してみてください。

public class ThreadSurprise {public static void main(String [] args)throws Exception {Thread [] thread = new Thread [0]; スレッド[-1] .sleep(1); //これをスローする必要がありますか?}} //クラスの終わり

このコードはスローするArrayIndexOutOfBoundsException必要がありますか?ThreadSurprise異なるSunMicrosystems JDK / J2SDK(Java 2 Platform、Standard Development Kit)バージョンを使用してコンパイルすると、動作に一貫性がなくなります。

  • バージョン1.1以前のコンパイラは、スローしないコードを生成します
  • バージョン1.2はスローします
  • バージョン1.3はスローしません
  • バージョン1.4はスローします

ここでの微妙な点Thread.sleep()は、静的メソッドであり、Threadインスタンスをまったく必要としないということです。それでも、Java言語仕様では、コンパイラーはの左側の式からターゲットクラスを推測するだけでなくthreads [-1].sleep (1);、式自体を評価する(そしてそのような評価の結果を破棄する)必要があります。のインデックス-1を参照していますthreadsそのような評価の配列部分?Java言語仕様の表現はやや曖昧です。J2SE 1.4の変更の要約は、左側の式を完全に評価することを支持して、あいまいさが最終的に解決されたことを意味します。すごい!J2SE 1.4コンパイラは最良の選択のように思われるので、ターゲットランタイムプラットフォームが以前のバージョンであっても、そのような修正と改善の恩恵を受けるために、すべてのJavaプログラミングに使用したいと思います。(これを書いている時点では、すべてのアプリケーションサーバーがJ2SE 1.4プラットフォームで認定されているわけではないことに注意してください。)

最後のコード例はやや人工的なものでしたが、要点を説明するのに役立ちました。最近のJ2SDKバージョンを使用するその他の理由には、javadocやその他のツールの改善の恩恵を受けたいことが含まれます。

最後に、クロスコンパイルは、組み込みJava開発とJavaゲーム開発におけるかなりの生き方です。

こんにちはクラスのパズルの説明

Helloこの記事を開始した例では、間違ったクロスコンパイルの例です。J2SE 1.4は、StringBufferAPIに新しいメソッドを追加しましたappend(StringBuffer)。javacがgreeting.append (who)バイトコードへの変換方法を決定するとStringBuffer、ブートストラップクラスパスでクラス定義を検索し、の代わりにこの新しいメソッドを選択しappend(Object)ます。ソースコードは完全にJava1.0と互換性がありますが、結果のバイトコードにはJ2SE1.4ランタイムが必要です。

この間違いを犯すのがいかに簡単であるかに注意してください。コンパイルの警告はなく、エラーは実行時にのみ検出可能です。J2SE1.4のjavacを使用してJava1.1互換Helloクラスを生成する正しい方法は次のとおりです。

> ... \ jdk1.4.1 \ bin \ javac -target 1.1 -bootclasspath ... \ jdk1.1.8 \ lib \ classes.zip Hello.java 

正しいjavacの呪文には、2つの新しいオプションが含まれています。それらが何をするのか、そしてなぜそれらが必要なのかを調べてみましょう。

すべてのJavaクラスにはバージョンスタンプがあります

気付いていないかもしれませんが、.class生成するすべてのファイルにはバージョンスタンプが含まれています0xCAFEBABE。マジックナンバーの直後のバイトオフセット4から始まる2つの符号なし短整数です。これらはクラス形式のメジャー/マイナーバージョン番号であり(クラスファイル形式の仕様を参照)、この形式定義の拡張ポイントであるだけでなく、ユーティリティもあります。Javaプラットフォームのすべてのバージョンは、サポートされるバージョンの範囲を指定します。これは、この記事の執筆時点でサポートされている範囲の表です(この表の私のバージョンは、Sunのドキュメントのデータとわずかに異なります。Sunのコンパイラの非常に古い(1.0.2より前の)バージョンにのみ関連するいくつかの範囲値を削除することを選択しました) :

Java 1.1プラットフォーム:45.3-45.65535 Java 1.2プラットフォーム:45.3-46.0 Java 1.3プラットフォーム:45.3-47.0 Java 1.4プラットフォーム:45.3-48.0 

クラスのバージョンスタンプがJVMのサポート範囲外にある場合、準拠するJVMはクラスのロードを拒否します。前の表から、後のJVMは常に前のバージョンレベルからのバージョン範囲全体をサポートし、それを拡張することに注意してください。

これは、Java開発者としてのあなたにとってどのような意味がありますか?コンパイル中にこのバージョンスタンプを制御する機能があれば、アプリケーションに必要な最小のJavaランタイムバージョンを適用できます。これはまさに-targetコンパイラオプションが行うことです。これは、さまざまなJDK / J2SDKからjavacコンパイラによってデフォルトで発行されるバージョンスタンプのリストです(J2SDK 1.4は、javacがデフォルトのターゲットを1.1から1.2に変更する最初のJ2SDKであることに注意してください)。

JDK 1.1:45.3 J2SDK 1.2:45.3 J2SDK 1.3:45.3 J2SDK 1.4:46.0 

そして、さまざまなを指定した場合の効果は次-targetのとおりです。

-ターゲット1.1:45.3-ターゲット1.2:46.0​​-ターゲット1.3:47.0-ターゲット1.4:48.0 

例として、以下はURL.getPath()J2SE1.3で追加されたメソッドを使用します。

URL url =新しいURL( "// www.javaworld.com/columns/jw-qna-index.shtml"); System.out.println( "URLパス:" + url.getPath());

このコードには少なくともJ2SE1.3が必要なので-target 1.3、ビルドするときに使用する必要があります。java.lang.NoSuchMethodError1.2 JVMにクラスを誤ってロードした場合にのみ発生するサプライズにユーザーが対処するように強制するのはなぜですか?確かに、私のアプリケーションにはJ2SE 1.3が必要であることを文書化できますが、バイナリレベルで同じものを適用する方がよりクリーンで堅牢です。

I don't think the practice of setting the target JVM is widely used in enterprise software development. I wrote a simple utility class DumpClassVersions (available with this article's download) that can scan files, archives, and directories with Java classes and report all encountered class version stamps. Some quick browsing of popular open source projects or even core libraries from various JDKs/J2SDKs will show no particular system for class versions.

Bootstrap and extension class lookup paths

When translating Java source code, the compiler needs to know the definition of types it has not yet seen. This includes your application classes and core classes like java.lang.StringBuffer. As I am sure you are aware, the latter class is frequently used to translate expressions containing String concatenation and the like.

A process superficially similar to normal application classloading looks up a class definition: first in the bootstrap classpath, then the extension classpath, and finally in the user classpath (-classpath). If you leave everything to the defaults, the definitions from the "home" javac's J2SDK will take effect—which may not be correct, as shown by the Hello example.

To override the bootstrap and extension class lookup paths, you use -bootclasspath and -extdirs javac options, respectively. This ability complements the -target option in the sense that while the latter sets the minimum required JVM version, the former selects the core class APIs available to the generated code.

Remember that javac itself was written in Java. The two options I just mentioned affect the class lookup for byte-code generation. They do not affect the bootstrap and extension classpaths used by the JVM to execute javac as a Java program (the latter could be done via the -J option, but doing that is quite dangerous and results in unsupported behavior). To put it another way, javac does not actually load any classes from -bootclasspath and -extdirs; it merely references their definitions.

With the newly acquired understanding for javac's cross-compilation support, let's see how this can be used in practical situations.

Scenario 1: Target a single base J2SE platform

This is a very common case: several J2SE versions support your application, and it so happens you can implement everything via core APIs of a certain (I will call it base) J2SE platform version. Upward compatibility takes care of the rest. Although J2SE 1.4 is the latest and greatest version, you see no reason to exclude users who can't run J2SE 1.4 yet.

The ideal way to compile your application is:

\bin\javac -target -bootclasspath \jre\lib\rt.jar -classpath 

Yes, this implies you might have to use two different J2SDK versions on your build machine: the one you pick for its javac and the one that is your base supported J2SE platform. This seems like extra setup effort, but it is actually a small price to pay for a robust build. The key here is explicitly controlling both the class version stamps and the bootstrap classpath and not relying on defaults. Use the -verbose option to verify where core class definitions are coming from.

As a side comment, I'll mention that it is common to see developers include rt.jar from their J2SDKs on the -classpath line (this could be a habit from the JDK 1.1 days when you had to add classes.zip to the compilation classpath). If you followed the discussion above, you now understand that this is completely redundant, and in the worst case, might interfere with the proper order of things.

Scenario 2: Switch code based on the Java version detected at runtime

Here you want to be more sophisticated than in Scenario 1: You have a base-supported Java platform version, but should your code run in a higher Java version, you prefer to leverage newer APIs. For example, you can get by with java.io.* APIs but wouldn't mind benefiting from java.nio.* enhancements in a more recent JVM if the opportunity presents itself.

In this scenario, the basic compilation approach resembles Scenario 1's approach, except your bootstrap J2SDK should be the highest version you need to use:

\bin\javac -target -bootclasspath \jre\lib\rt.jar -classpath 

This is not enough, however; you also need to do something clever in your Java code so it does the right thing in different J2SE versions.

One alternative is to use a Java preprocessor (with at least #ifdef/#else/#endif support) and actually generate different builds for different J2SE platform versions. Although J2SDK lacks proper preprocessing support, there is no shortage of such tools on the Web.

ただし、異なるJ2SEプラットフォームの複数のディストリビューションを管理することは、常に追加の負担になります。いくつかの特別な先見性があれば、アプリケーションの単一ビルドを配布することで逃げることができます。これを行う方法の例を次にURLTest1示します(URLからさまざまな興味深いビットを抽出する単純なクラスです)。