先日、
「検証環境に乗っけるときに、Javaのバージョンが1.6だと動かなかったので、1.5でコンパイルしてライブラリちょうだい。」
なんて言われてしまいました。(検証環境に乗せるまで、誰も使用するJavaのバージョンを聞いても教えてくれないプロジェクトがこの世に存在するのです。)
1.5準拠に変更してコンパイルエラーとなるのは、interfaceのメソッドに対して「@Override」アノテーションをつけているものだけだったので、コンパイル時に「-targetオプション」だけ1.5にして、とりあえずお茶を濁しました。
そもそもこの「-targetオプション」って何なんでしょう?
ということで調べてみました。
まず、javacのドキュメントにはこうあります。
-target versionつまりは、指定したバージョン以降で動作します、ってことですね。
指定されたバージョンの VM をターゲットにしたクラスファイルを生成します。このクラスファイルは、指定されたターゲット以降のバージョンでは動作しますが、それより前のバージョンの VM では動作しません。有効なターゲットは、1.1、1.2、1.3、1.4、1.5 (5 も可)、および 1.6 (6 も可) です
以下の何の面白みもないソースで試してみます。
public class Test { public static void main(String[] args) { System.out.println("もじれつ"); } }コンパイルして、javap.exeを使ってみます。
まずは1.6。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javac.exe" Test.java C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javap.exe" -v Test Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" minor version: 0 major version: 50 (略)1.6と1.5で実行してみます。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\java.exe" Test もじれつ C:\>"C:\Program Files\Java\jdk1.5.0_21\bin\java.exe" Test Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124) at java.net.URLClassLoader.defineClass(URLClassLoader.java:260) at java.net.URLClassLoader.access$100(URLClassLoader.java:56) at java.net.URLClassLoader$1.run(URLClassLoader.java:195) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:188) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268) at java.lang.ClassLoader.loadClass(ClassLoader.java:251) at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)1.5ではUnsupportedClassVersionErrorが投げられてしまいました。
次にソースを1.5に指定してみます。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javac.exe" -target 1.5 Test.java C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javap.exe" -v Test Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" minor version: 0 major version: 49 (略)major versionが49になりました。
同じように実行。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\java.exe" Test もじれつ C:\>"C:\Program Files\Java\jdk1.5.0_21\bin\java.exe" Test もじれつどちらも実行できました。
スバラシイ!
今回問題になった、@Overrideアノテーションをつけていた場合はどうなるのでしょう?
実装メソッドに@Overrideがついているのがポイントです。
public interface TestInterface { void test(); }
public class TestInterfaceImpl implements TestInterface { @Override public void test() { System.out.println("Test!"); } }
public class Test { public static void main(String[] args) { TestInterface test = new TestInterfaceImpl(); test.test(); } }これをtarget指定でコンパイルして、1.5のJVMで実行してみます。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javac.exe" -target 1.5 *.java C:\>"C:\Program Files\Java\jdk1.5.0_21\bin\java.exe" Test Test!エラーとならず実行できました。
と、ここまで書いて気付いたのですが、@Overrideはコンパイル時だけ有効なアノテーションなので、コンパイル後は消えてしまうんでしたっけ。あまり意味の無い検証でしたね。。。
じゃあ、1.5に無いメソッドを使ったらどうなる?ということで、試してみます。
またもや面白みの無いソース。1.6から導入されたString#isEmpty()で試してみます。
public class Test { public static void main(String[] args) { System.out.println("string".isEmpty()); } }当然、1.5でコンパイルすると、コンパイルエラーになります。
C:\>"C:\Program Files\Java\jdk1.5.0_21\bin\javac.exe" Test.java Test.java:5: シンボルを見つけられません。 シンボル: メソッド isEmpty() 場所 : java.lang.String の クラス System.out.println("string".isEmpty()); ^ エラー 1 個じゃあこれを同じ様にコンパイル。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javac.exe"-target 1.5 Test.java C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javap.exe" -v Test Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" minor version: 0 major version: 49 (略)major versionが49なので、1.5用にコンパイルされています。
そして1.5で実行。
C:>"C:\Program Files\Java\jdk1.5.0_21\bin\java.exe"Test Exception in thread "main" java.lang.NoSuchMethodError: java.lang.String.isEmpty()Z atTest.main(Test.java:5)あらら。。。NoSuchMethodErrorが発生してしまいました。String#isEmpty()は1.6から導入されたため、当然といえば当然の結果ですが。。。
と、思ったら、Javacのドキュメントにちゃんと書いてありました。
Java プラットフォーム JDK の javac は、デフォルトでは、Java 2 SDK のブートストラップクラスに対してコンパイルを行うので、Java 2 SDK ではなく JDK 1.5 のブートストラップクラスに対してコンパイルを行うように指定する必要があります。これは、-bootclasspath および -extdirs を使って指定します。この指定を行わないと、1.5 VM には存在しない Java 2 プラットフォーム API に対応したコンパイルが行われるため、プログラムの実行時に障害が発生することがあります。では、サンプルにあるとおり、-bootclasspath、-extdirs を指定してみます。
C:\>"C:\Program Files\Java\jdk1.6.0_30\bin\javac.exe" -target 1.5 -bootclasspath "C:\Program Files\Java\jdk1.5.0_21\jre\lib\rt.jar" -extdirs "" Test.java Test.java:5: シンボルを見つけられません。 シンボル: メソッド isEmpty() 場所 : java.lang.String の クラス System.out.println("string".isEmpty()); ^ エラー 1 個ちゃんと(?)コンパイル時に怒られるようになりました。
要するに、ちゃんと目的のバージョンのrt.jar等を使ってコンパイルしろ、ってことですね。
0 コメント:
コメントを投稿