Java 2 SDK 開発ガイド (Solaris 編)

言語の非互換性の問題

JDK 1.0 および 1.1 のコンパイラは、数種類の不正なコードを警告メッセージやエラーメッセージを表示せずにコンパイルしてしまいます。コードを JLS の仕様に準拠させるという点において、Java 2 SDK Solaris 版のコンパイラは厳密さが増しています。次は、JDK 1.0 または 1.1 のコンパイラではコンパイルされるが、Java 2 SDK Solaris 版のコンパイラではコンパイルされない不正なコードの種類を示します。

  1. 以前のコンパイラでは、long 型に対する int 型初期設定が許可されることがありました。Java 2 SDK Solaris 版ではつねにエラーになります。たとえば、次のコードの i および j の初期設定は無効です。


    public class foo {
    	int i = 3000000000;
    	int j = 6000000000;
    }

    この場合、以前のコンパイラは i の不正な初期化を報告するだけであり、j の初期化はメッセージを表示せずに単にオーバーフローしていました (バグ ID 4035346)。

  2. 以前のコンパイラでは、8 ビットに収まる文字リテラルに対する char から byte および short への暗黙の代入変換が許可されていました。Java 2 SDK Solaris 版のコンパイラは、そうした暗黙の代入変換を許可しません。たとえば、次のコードはコンパイラを通りません。


    byte b = 'b';

    こうした変換には、次のような明示的な型変換を使用してください (バグ ID 4030496)。


    byte b = (byte)'b';
  3. Java 2 SDK Solaris 版のコンパイラは 0xL を許可しません (正当な 16 進リテラルではない)。以前のコンパイラは、これをゼロとして解析します (バグ ID 4049982)。

  4. Java 2 SDK Solaris 版のコンパイラは ''' (つまり '¥u0027') を許可しません (正当な char リテラルではない)。代わりに '¥" を使用してください (バグ ID 1265387)。

  5. Java 2 SDK Solaris 版のコンパイラは "¥u000D" を許可しません (string および char リテラルでは不正)。CR および LF 文字 (¥u000A¥u000D) は、コメントで使用されている場合でも行を終了します (バグ ID 4086919)。このため、次のコードは不正です。


    //This comment about ¥u000D is not legal; it is really two lines.

    代わりに ¥r を使用してください (バグ ID 4063417)。

  6. Java 2 SDK Solaris 版のコンパイラは、void[] 型を許可しません。不正です (バグ ID 4034979)。

  7. abstract メソッド修飾子を privatefinalnative、または synchronized 修飾子と組み合わせないでください。不正です (バグ ID 1266571)。

  8. 以前のコンパイラでは、状況によっては、final 変数に対する二重代入が許可されることがあります。たとえば、次の 2 つの例には final 変数に対する二重代入が含まれていますが、1.1 のコンパイラでは許可されます。Java 2 SDK Solaris 版のコンパイラは、こうした代入を許可しません (バグ ID 4066275 および 4056774)。


    public class Example1 {
    		public static void main(String[] argv) {
    			int k=0;
    			for (final int a;;) {
    				k++;
    				a=k;
    				System.out.println("a="+a);
    				if (k>3)
    					return;
    			}
    		}
    }
    
    public class Example2 {
    		final int k;
    		Example2() {
    			k = 1;
    		}
    		Example2(Object whatever) {
    			this();
    			k = 2;
    		}
    }
    static public void main(String[] args) {
    		Example2 t = new Example2(null);
    		System.out.println("k is "+ t.k);
    }
  9. static フィールド式であることが明らかな Classname.fieldname の形式の式で、static ではないフィールドを参照することはできません。Java 2 SDK Solaris 版より前のバージョンでは、javacthis.fieldname と書かれているかのようにそのまま許可していました (バグ ID 4087127)。

  10. JLS のセクション 5.5 は、2 つのインタフェースが存在していて、それらのインタフェースにシグニチャーは同じでも、戻り値の型が異なるメソッドが含まれている場合、そのインタフェース間のキャストはコンパイル時エラーになると規定しています。Java 2 SDK Solaris 版より前のコンパイラでは、このコンパイル時エラーが生成されませんでした (バグ ID 4028359)。たとえば、次のコードは現在ではコンパイル時エラーになります。


    interface Noisy {
    		int method();
    }
    interface Quiet {
    		void method();
    }
    public class InterfaceCast {
    		public static void main(String[] args) {
    			Noisy one = null;
    			Quiet two = (Quiet) one;
    		}
    }
  11. Java 2 SDK Solaris 版は、条件文の 3 つ目の部分式に代入式を受け付けません。たとえば、Java 2 SDK Solaris 版のコンパイラは次の文でエラーをスローします。


    myVal = condition ? x = 7 : x = 3;

    既存のコードでこのエラーが発生する場合は、以前の JDK のときのように、原因になっている代入式を括弧で囲って、コンパイルしてください。


    myVal = condition ? x = 7 : (x = 3);
  12. 前のバージョンの javac では、フィールドにデフォルト値が初期設定されていた場合に、コンパイラが誤って初期設定を省略していました (バグ ID 1227855)。このため、次のようなプログラムは異なる意味をもつ場合があります。


    abstract class Parent {
    		Parent() {
    			setI(100);
    		}
    }
    	abstract public void setI(int value);
    }
    public class InitTest extends Parent {
    		public int i = 0;
    		public void setI(int value) {
    			i = value;
    		}
    }
    	public static void main(String[] args) {
    		InitTest test = new InitTest();
    		System.out.println(test.i);
    }

    前のバージョンの javac でコンパイルした場合、このプログラムは不正な出力 (100) を生成します。Java 2 SDK Solaris 版の javac は、正しい出力 (0) を生成します。

    1 つのクラスの例を次のように作成することもできます。


    public class InitTest2 {
    		public int j = method();
    		public int i = 0;
    		public int method() {
    			i = 100;
    			return 200;
    		}
    }
    public static void main(String[] args) {
    		InitTest2 test = new InitTest2();
    		System.out.println(test.i);
    }

    以前、この出力は 100 でした。現在は 0 になります。同じ現象が、参照型と null を使用したコードで発生することがあります。

  13. JLS 6.6.1 には、修飾名におけるアクセスに関して次の規定があります。

    参照型 (classinterface、または array) のメンバ (フィールドまたはメソッド) または class 型のコンストラクタは、その型がアクセス可能で、そのメンバまたはコンストラクタのアクセス許可が宣言されている場合にのみアクセスすることができます。"

    Java 2 SDK Solaris 版より前のコンパイラは、この規則を正しく適用していませんでした。メンバまたはコンストラクタが属する型がアクセスできるかどうかに関係なく、アクセス許可が宣言されている場合はアクセスを許可していました。次に不正なプログラムの例を示します。


    import pack1.P1;
    public class CMain {
    		public static void main(String[] args) {
    			P1 p1 = new P1();
    // 以下のフィールド i へのアクセスは
    // 不正。p2 の型、すなわち class 型 pack1.P2 に
    // はアクセスできない
    			p1.p2.i = 3;
    			System.out.println(p1.p2.i);
    		}
    }
    package pack1;
    public class P1 {
    		public P2 p2 = new P2();
    }
    // パッケージ pack1 からのみしか P2 をアクセス可能
    // にするアクセス修飾子がないことに注目
    public class P2 {
    		public int i = 0;
    }

    Java 2 SDK Solaris 版における内部クラスの導入によって、クラスまたはインタフェースのメンバはフィールドやメソッドだけでなく、別のクラスまたはインタフェースにすることができます。Java 2 SDK Solaris 版では、内部クラスに対しても上記のアクセス規則が適用されます。

  14. Java 2 SDK Solaris 版より前のコンパイラは、あるクラスが abstract クラスであることを検出できないことがありました。これは、サブクラスが別のパッケージのスーパークラスに定義されている package-privateabstract メソッドと同じ名前のメソッドを宣言した場合に発生します。メソッドが同じ名前であっても、名前が無効にされることはありません。たとえば、次のファイルをコンパイルすると、


    package one;
    public abstract class Parent {
    		abstract void method();
    }
    
    package two;
    public class Child extends one.Parent {
    	void method() {}
    }

    次のエラーメッセージが返されます。


    two/Child.java:3: class two.Child is not able to provide an
    implementation for the method void method() declared in class
    one. Parent because it is private to another package. class
    two. Child must be declared abstract.
    	public class Child extends one.Parent {
    			^
  15. Java 2 SDK Solaris 版のコンパイラは、入れ子内のラベルの重複を正しく検出します。JLS の仕様では、入れ子内のラベルの重複を禁止しています。このため、次の文は不正です。


    sameName:
    while (condition) {
    	sameName:
    	while (condition2) {
    		break sameName;
    	}
    }
  16. Java 2 SDK Solaris 版のコンパイラは、ラベル付きの宣言を正しく検出します。JLS の仕様では、ラベル付きの宣言が禁止されています。これはバグ ID 4039843 の修正です。

  17. Java 2 SDK Solaris 版のコンパイラは新しいキーワードの strictfp を認識します。このため、プログラムで strictfp を識別子として使用することはできません。Java 2 SDK Solaris 版のコンパイラは、この新しいキーワードを使用して、メソッドデータ構造体の修飾子ビットを設定します。Java 2 SDK Solaris 版より前の Java プラットフォームの指定では、このビットをゼロにしている必要がありました。この strictfp キーワードを使用しているコードは厳しい浮動小数点モード (Java プラットフォームに定義されているデフォルトのモード) で動作します。この新しいキーワードを使用していないコードは、デフォルト浮動小数点モードで動作し、いくつかのプロセッサを巧みに利用して、性能の向上を図れるようにします。

    strictfp キーワードのマークが付いていない一部の数値コードの動作が、Java 2 SDK Solaris 版と前のバージョンとでは異なることがあります。そうしたコードの動作は、Java プラットフォームの実装状態によっても異なることがあります。状況によっては、オーバーフローまたはアンダーフローが発生し、多少異なる結果が生成されることがあります。大部分の数値コードは、こうした違いの影響を受けるとは考えられません。しかし、浮動小数点演算が重要な意味を持つコードは影響を受けることがあります。

  18. JDK 1.1 では式構文が拡張され、次の例に示すように this キーワードを使用して、現在のインスタンスに対する参照をクラス名で修飾できるようになりました。


    PrimaryNoNewArray: ...
                    ClassName . this

    こうした式の値は、必ず存在する必要がある囲むクラス (ClassName) の現在のインスタンスに対する参照になります。

    Java 2 SDK Solaris 版より前の javac コンパイラでは、こうした式の取り扱いに誤りがありました。ClassName で指定された型のサブタイプであるもっとも内側で囲むクラスの現在のインスタンスに対する参照を生成していました。現在は、そうした式も正しく実装されます。

  19. JDK 1.1 では、式構文が拡張され、次の例に示すように、this キーワードを使用して、現在のインスタンスに対する参照をクラス名で修飾できるようになりました。


    PrimaryNoNewArray: ...
                    ClassName . this

    この構文では、次のような形式でメンバにアクセスすることができます。


    ClassName.this.fieldname
    ClassName.this.methodname( ... ) 

    この拡張が必要なのは、内部クラスが存在していて、このコードの任意の地点で現在のインスタンスが複数存在する可能性があるためです。次の例に示すように、内部クラス仕様では、super キーワードに対して同様の拡張が含まれていませんでした。


    FieldAccess: ...
                    ClassName.super.Identifier
    MethodInvocation:...
                    ClassName.super.Identifier(ArgumentList_opt) 

    第 2 版の JLS に含まれることを予想して、Java 2 SDK Solaris 版には、これらの構文が実装されています。

    これらのどの場合も、現在のインスタンスは、存在する必要がある囲むクラス (ClassName) の現在のインスタンスです。

    次は、1 つの例として、修飾された super の指定が実際に必要になる場合を示しています。


    class C {
    		void f() { ... }
    }
    class D extends C {
    		void f() {
                // f() をオーバーライドして新しいスレッドで実行
    			new Thread(new Runnable() {
    				public void run() {
    					D.super.f();
    				}
    			}).start();
    		}
    }

    実装に関する注: アクセスメソッドを使用する必要がある場合は、スーパークラスではなく ClassName で指定しているクラスに置く必要があります。スーパークラスを同じコンパイルユニットで定義する必要はありません。事前にコンパイルしておいてもかまいません。

  20. JDK 1.1 では、式構文が拡張され、次の例に示すように、super キーワードを使用したコンストラクタの呼び出しを、外部インスタンスに対する参照で修飾できるようになりました。


    ExplicitConstructorInvocation: ...
    	Primary.super(ArgumentList_opt); 

    this を使用したコンストラクタの呼び出しが不注意にも省略されていました。


    ExplicitConstructorInvocation: ...
    	Primary.this(ArgumentList_opt); 

    第 2 版の JLS に含まれることを予想して、Java 2 SDK Solaris 版には、これらの構文が実装されています。

  21. 内部クラスの仕様では、内部クラスでメンバインタフェースを宣言することはできません。Java 2 SDK Solaris 版では、この規則が適用されますが、JDK 1.1 リリースでは、そうした宣言は黙認されます。 たとえば、次のコードは、JDK 1.1 リリースの javac では誤って容認され、Java 2 SDK Solaris 版の javac では拒否されます。


    class InnerInterface {
    		class Inner {
    			interface A { }
    		}
    }

    静的なメンバクラスは内部クラスになりません。静的なメンバクラスは最上位のクラスです (内部クラスの仕様で定義に従う)。JDK 1.1 リリースおよび Java 2 SDK Solaris 版の javac はともに、正しいコードとして次のコードを受け入れます。


    class NestedInterface {
    		static class Inner {
    			interface A { }
    		}
    }

    ローカルのクラスが最上位のクラスになることはありません。このため、次の例は不正であり、1.1 リリースおよび Java 2 SDK Solaris 版のどちらの javac でも、構文エラーが返されます。


    class LocalNestedInterface {
    	void foo()
    			 static class Inner {
    			 	interface A { }
    			}
    	}
    }
  22. Java 2 SDK Solaris 版のコンパイラは、パッケージが同じ名前の型とサブパッケージを含むことはできないという制限事項を強制的に適用します。この変更のため、classpath にパッケージの完全修飾名と同じ完全修飾名を持つクラスをコンパイルすることは誤りです。また、既存のクラスと同じ名前を持つことになるパッケージ (または正しい接頭辞) を持つクラスをコンパイルすることも誤りです。これは、バグ ID 4101529 に対する修正です。次に、現在はコンパイルできないクラスの例をいくつか示します。


    ¥¥例 1
    package java.lang.String;
    class Illegal {
    }
    
    ¥¥例 2
    package java;
    class util {
    }