第13章 バイナリ互換性

目次

13.1 バイナリの形式
13.2 バイナリ互換性とは
13.3 パッケージとモジュールの進化
13.4 クラスの進化
13.4.1. abstractクラス
13.4.2. sealednon-sealedおよびfinalクラス
13.4.2.1. sealedクラス
13.4.2.2. non-sealedクラス
13.4.2.3. finalクラス
13.4.3. publicクラス
13.4 スーパークラスとスーパーインタフェース
13.4 クラス・タイプのパラメータ
13.4 クラス本体およびメンバーの宣言
13.4 メンバーおよびコンストラクタへのアクセス
13.4 フィールド宣言
13.4.9. finalフィールドおよびstatic定数変数
13.4.10. staticフィールド
13.4.11. transientフィールド
13.4 メソッドおよびコンストラクタの宣言
13.4 メソッドおよびコンストラクタ・タイプのパラメータ
13.4 メソッドおよびコンストラクタの正式パラメータ
13.4 メソッド結果タイプ
13.4.16. abstractメソッド
13.4.17. finalメソッド
13.4.18. nativeメソッド
13.4.19. staticメソッド
13.4.20. synchronizedメソッド
13.4 メソッドおよびコンストラクタのスロー
13.4 メソッドおよびコンストラクタ本体
13.4 メソッドおよびコンストラクタのオーバーロード
13.4 メソッドのオーバーライド
13.4 静的イニシャライザ
13.4 列挙クラスの進化
13.4 レコード・クラスの進化
13.5 インタフェースの進化
13.5.1. publicインタフェース
13.5.2. sealedおよびnon-sealedインタフェース
13.5 スーパーインタフェース
13.5 インタフェース・メンバー
13.5 インタフェース・タイプ・パラメータ
13.5 フィールド宣言
13.5 インタフェース・メソッド宣言
13.5 注釈インタフェース

Javaプログラミング言語用の開発ツールでは、ソース・コードが使用可能な場合は常に、必要に応じて自動再コンパイルをサポートする必要があります。 特定の実装では、クラスおよびインタフェースのソース表現およびバイナリ表現をバージョニング・データベースに格納し、バイナリ互換バージョンのクラスおよびインタフェースをクライアントに提供することによってリンク・エラーを防ぐためにデータベースの整合性メカニズムを使用するClassLoaderを実装することもできます。

広く配布されるパッケージ、クラスおよびインタフェースの開発者は、異なる問題に直面しています。 広く分散されたシステムのお気に入りの例であるインターネットでは、変更されるクラスまたはインタフェースに直接的または間接的に依存する既存のバイナリを自動的に再コンパイルすることは、多くの場合、非現実的または不可能です。 かわりに、この仕様では、既存のバイナリとの互換性を保持(中断しない)しながら、開発者がパッケージまたはクラスまたはインタフェースに対して行うことができる一連の変更を定義します。

Release-to-Release Binary Compatibility in SOM (Forman、 Conner、 Danforth、 and Raper、 Proceedings of OOPSLA '95)のフレームワーク内で、Javaプログラミング言語バイナリは、作成者が識別するすべての関連変換(インスタンス変数の追加に関する注意事項)でバイナリ互換性があります。 そのスキームを使用して、Javaプログラミング言語がサポートする重要なバイナリ互換の変更のリストを次に示します。

この章では、すべての実装で保証されるバイナリ互換性の最小標準を指定します。 Javaプログラミング言語では、互換性のあるソースからはわかっていないが、ここで説明する互換性のある方法でソースが変更されたクラスとインタフェースのバイナリが混在する場合の互換性が保証されます。 ここでは、アプリケーションのリリース間の互換性について説明します。 Java SE Platformのリリース間の互換性については、この章の範囲外です。

開発システムは、再コンパイルできない既存のバイナリに対する変更の影響を開発者に警告する機能を提供することを推奨します。

この章では、最初に、Javaプログラミング言語のバイナリ形式(§13.1)に必要なプロパティを指定します。 次に、バイナリ互換性を定義し、それが何であるか、そして何がそうでないかを説明する(§13.2)。 最後に、パッケージ(§13.3)、クラス(§13.4)、およびインタフェース(§13.5)に対して起こりうる一連の大きな変更を列挙し、これらの変更のうち、バイナリ互換性を保持することが保証されているものとそうでないものを指定します。

13.1.  バイナリの形式

プログラムは、The Java Virtual Machine Specification、 Java SE 26 Editionで指定されたclassファイル形式にコンパイルするか、Javaプログラミング言語で記述されたクラス・ローダーによってその形式にマップできる表現にコンパイルする必要があります。

クラスまたはインタフェース宣言に対応するclassファイルには、特定のプロパティが必要です。 これらのプロパティの多くは、バイナリ互換性を保持するソース・コード変換をサポートするために特に選択されます。 必須プロパティは次のとおりです:

  1. クラスまたはインタフェースには、次の制約を満たすバイナリ名で名前を付ける必要があります。

    • 最上位のクラスまたはインタフェースのバイナリ名(§7.6)は、その正規名(§6.7)です。

      コンパクト・コンパイル・ユニットによって暗黙的に宣言されたトップ・レベル・クラスの正規名は、ホスト・システム(§8.1.8)によって決定されることに注意してください。

    • メンバー・クラスまたはインタフェースのバイナリ名(§8.5§9.5)は、その直近のクラスまたはインタフェースのバイナリ名、$、その後にメンバーの単純な名前で構成されます。

    • ローカル・クラスまたはインタフェースのバイナリ名(§14.3)は、その直近のクラスまたはインタフェースのバイナリ名、$、続いて空でない一連の桁、その後にローカル・クラスの単純名で構成されます。

    • 匿名クラスのバイナリ名(§15.9.5)は、その直近のクラスまたはインタフェースのバイナリ名、$、それに続く空でない一連の桁で構成されます。

    • 汎用クラスまたはインタフェースによって宣言された型変数のバイナリ名(§8.1.2§9.1.2)は、その直近のクラスまたはインタフェースのバイナリ名、$、その後に型変数の単純名が続きます。

    • 汎用メソッド(§8.4.4)によって宣言された型変数のバイナリ名は、メソッドを宣言するクラスまたはインタフェースのバイナリ名、$、メソッドの記述子(JVMS§4.3.3)、$、および型変数の単純名です。

    • 汎用コンストラクタによって宣言される型変数のバイナリ名(§8.8.4)は、コンストラクタを宣言するクラスのバイナリ名、$、コンストラクタの記述子(JVMS§4.3.3)、$、および型変数の単純名です。

  2. 別のクラスまたはインタフェースへの参照は、クラスまたはインタフェースのバイナリ名を使用してシンボリックである必要があります。

  3. 定数変数であるフィールドへの参照(§4.12.4)は、定数変数のイニシャライザで示される値Vにコンパイル時に解決される必要があります。

    このようなフィールドがstaticの場合、フィールドを宣言したクラスまたはインタフェースを含め、バイナリ・ファイルのコードにフィールドへの参照は存在できません。 このようなフィールドは、常に初期化されている(§12.4.2)ように見える必要があります。フィールドのデフォルト初期値(Vと異なる場合)は、監視しないでください。

    このようなフィールドがstatic以外の場合、フィールドを含むクラスを除き、バイナリ・ファイルのコードにフィールドへの参照は存在できません。 (インタフェースにはstaticフィールドのみがあるため、インタフェースではなくクラスになります。) このクラスには、インスタンスの作成時にフィールドの値をVに設定するコードが必要です(§12.5)。

  4. クラスCのフィールド・アクセスを示す正当な式で、定数変数ではなく(異なる可能性がある)クラスまたはインタフェースDで宣言されているfという名前のフィールドを参照する場合、フィールド参照のクラスまたはインタフェース修飾を次のように定義します。

    • 式が単純名で参照される場合、fが現在のクラスまたはインタフェース(C)のメンバーである場合、QCにします。 それ以外の場合は、Qを、fがメンバーである最も内側の字句で囲まれているクラスまたはインタフェース宣言にしてください。 いずれの場合も、Qは参照の修飾クラスまたはインタフェースです。

    • 参照の形式がTypeName.fで、TypeNameはクラスまたはインタフェースを示し、TypeNameで示されるクラスまたはインタフェースは参照の修飾クラスまたはインタフェースです。

    • 式がExpressionName.fまたはPrimary.fの形式の場合は、次のようになります。

      • ExpressionNameまたはPrimaryのコンパイル時型が交差型V1 & ... & Vn (§4.9)の場合、参照の修飾クラスまたはインタフェースは、V1の消去(§4.6)です。

      • それ以外の場合、ExpressionNameまたはPrimaryのコンパイル時型の消去は、参照の修飾クラスまたはインタフェースです。

    • 式がsuper.fの形式の場合、Cのスーパークラスは参照の修飾クラスまたはインタフェースです。

    • 式がTypeName.super.fの形式の場合、TypeNameで示されるクラスのスーパークラスは、参照の修飾クラスまたはインタフェースです。

    fへの参照は、参照の修飾クラスまたはインタフェースへのシンボリック参照、およびフィールドの単純名fにコンパイルする必要があります。

    また、型が想定どおりであるかどうかを検証者がチェックできるように、この参照には、宣言されたフィールドの型のイレイジャへのシンボリック参照も含まれている必要があります。

  5. クラスまたはインタフェースCのメソッド呼出し式またはメソッド参照式が、クラスまたはインタフェースDで宣言された(または暗黙的に宣言された(§9.2)) mという名前のメソッドを参照する場合、メソッド呼出しのクラスまたはインタフェース修飾を次のように定義します。

    • DObjectの場合、メソッド呼出しの修飾クラスまたはインタフェースはObjectです。

    • そうでない場合は、次のようになります。

      • メソッドが単純名で参照される場合、mが現在のクラスまたはインタフェースCのメンバーである場合、QCにしてください。そうでない場合は、Qを、mがメンバーである最も内側の字句で囲まれているクラスまたはインタフェース宣言にしてください。 いずれの場合も、Qはメソッド呼出しの修飾クラスまたはインタフェースです。

      • 式がTypeName.mまたはReferenceType::mの形式の場合、TypeNameで示されるクラスまたはインタフェース、またはReferenceTypeの消去は、メソッド呼出しの修飾クラスまたはインタフェースです。

      • 式がExpressionName.mまたはPrimary.mまたはExpressionName::mまたはPrimary::mの形式の場合は、次のようになります。

        • ExpressionNameまたはPrimaryのコンパイル時型が交差型V1 & ... & Vnの場合、メソッド呼出しの修飾クラスまたはインタフェースはV1の消去です。

        • それ以外の場合、ExpressionNameまたはPrimaryのコンパイル時型の消去は、メソッド呼出しの修飾クラスまたはインタフェースです。

      • 式がsuper.mまたはsuper::mの形式の場合、Cのスーパークラスは、メソッド呼出しの修飾クラスまたはインタフェースです。

      • 式がTypeName.super.mまたはTypeName.super::mの形式の場合、TypeNameがクラスXを示している場合、Xのスーパークラスはメソッド呼出しの修飾クラスまたはインタフェースです。TypeNameがインタフェースXを示している場合、Xはメソッド呼出しの修飾クラスまたはインタフェースです。

    メソッドへの参照は、コンパイル時に、メソッド呼出しの修飾クラスまたはインタフェースへのシンボリック参照と、メソッドの宣言されたシグネチャ(§8.4.2)の消去に解決される必要があります。 メソッドのシグネチャには、§15.12.3で決定された次のすべてが含まれている必要があります。

    • メソッドの単純名

    • メソッドに対するパラメータの数。

    • 各パラメータの型へのシンボリック参照

    メソッドへの参照には、示されたメソッドの戻り型の消去に対するシンボリック参照、または示されたメソッドがvoidとして宣言され、値を返さないことを示す記号参照も含める必要があります。

  6. クラス・インスタンス作成式(§15.9)、明示的なコンストラクタ呼出し(§8.8.7.1)、またはクラスまたはインタフェースCClassType :: new(§15.13)形式のメソッド参照式(§15.13)が、クラスまたはインタフェースDで宣言されたコンストラクタmを参照する場合、コンストラクタ呼出しの修飾クラスを次のように定義します。

    • 式がnew D(...)またはExpressionName.new D(...)またはPrimary.new D(...)またはD :: newの形式の場合、コンストラクタ呼出しの修飾クラスはDです。

    • 式がnew D(...){...}またはExpressionName.new D(...){...}またはPrimary.new D(...){...}の形式の場合、コンストラクタ呼出しの修飾クラスは、式によって宣言された匿名クラスです。

    • 式がsuper(...)またはExpressionName.super(...)またはPrimary.super(...)の形式の場合、コンストラクタ呼出しの修飾クラスはCの直接スーパークラスです。

    • 式の形式がthis(...)の場合、コンストラクタ呼出しの修飾クラスはCです。

    コンストラクタへの参照は、コンパイル時に、コンストラクタ呼出しの修飾クラスへのシンボリック参照と、コンストラクタの宣言された署名(§8.8.2)に解決される必要があります。 コンストラクタのシグネチャには、次の両方を含める必要があります:

    • コンストラクタのパラメータ数

    • 各仮パラメータの型へのシンボリック参照

クラスまたはインタフェースのバイナリ表現には、次のものもすべて含める必要があります:

  1. クラスであり、Objectではない場合、このクラスの直接スーパークラスへのシンボリック参照。

  2. 各直接スーパーインタフェースへのシンボリック参照(ある場合)。

  3. クラスまたはインタフェースで宣言された各フィールドの仕様。フィールドの単純な名前およびフィールドの型の紀元へのシンボリック参照として指定されます。

  4. クラスの場合は、前述のように、各コンストラクタの消去されたシグネチャ。

  5. クラスまたはインタフェースで宣言された各メソッド(インタフェースの場合、暗黙的に宣言されたメソッド(§9.2)を除く)について、前述のとおり、消去された署名および戻り型。

  6. クラスまたはインタフェースの実装に必要なコードは、次のとおりです:

    • インタフェースの場合、フィールド・イニシャライザのコード、およびブロック本体による各メソッドの実装(§9.4.3)。

    • クラス、フィールド・イニシャライザのコード、インスタンス・イニシャライザおよび静的イニシャライザ、ブロック本体による各メソッドの実装(§8.4.7)、および各コンストラクタの実装。

  7. すべてのクラスまたはインタフェースには、その正規名を回復するための十分な情報が含まれている必要があります(§6.7)。

  8. すべてのメンバー・クラスまたはインタフェースには、ソース・レベルのアクセス修飾子(§6.6)をリカバリするための十分な情報が必要です。

  9. ネストされたすべてのクラスまたはインタフェースには、その直近のクラスまたはインタフェース(§8.1.3)へのシンボリック参照が必要です。

  10. すべてのクラスまたはインタフェースには、そのすべてのメンバー・クラスおよびインタフェース(§8.5§9.5)と、その本体内で宣言された他のすべてのネストされたクラスおよびインタフェースへのシンボリック参照が含まれている必要があります。

  11. Javaコンパイラによって生成されるコンストラクトは、出力されたコンストラクトがクラス初期化メソッド(JVMS§2.9)でないかぎり、ソース・コードで明示的にまたは暗黙的に宣言されたコンストラクトに対応していない場合は、合成とマークする必要があります。

  12. ソース・コード(§8.8.1§8.8.9§8.9.3§15.9.5.1)で暗黙的に宣言された仮パラメータに対応する場合、Javaコンパイラによって生成されるコンストラクトは、mandatedとしてマークする必要があります。

次の仮パラメータは、ソース・コードで暗黙的に宣言されます:

  • private以外の内部メンバー・クラスのコンストラクタの最初の仮パラメータ(§8.8.1§8.8.9)。

  • スーパークラスが内部クラス(静的コンテキスト内ではない)である匿名クラスの無名コンストラクタの最初の仮パラメータ(§15.9.5.1)。

  • enumクラス(§8.9.3)で暗黙的に宣言されるvalueOfメソッドの仮パラメータname

  • レコード・クラスのコンパクト・コンストラクタの仮パラメータ(§8.10.4)。

参照のために、次の構文はソース・コードで暗黙的に宣言されますが、classファイル(JVMS§4.7.24、JVMS§4.7.25)でそうマークできるのは仮パラメータおよびモジュールのみであるため、必須としてマークされません。

  • normalクラスとenumクラスのデフォルト・コンストラクタ(§8.8.9§8.9.2)

  • レコード・クラスの正規コンストラクタ(§8.10.4)

  • 匿名コンストラクタ(§15.9.5.1)

  • 列挙クラスのvaluesおよびvalueOfメソッド(§8.9.3)

  • 列挙クラスの特定のpublicフィールド(§8.9.3)

  • レコード・クラスの特定のprivateフィールドおよびpublicメソッド(§8.10.3)

  • インタフェースの特定のpublicメソッド(§9.2)

  • コンテナ注釈(§9.7.5)

  • コンパクト・コンパイル・ユニットによって暗黙的に宣言されたクラス(§8.1.8)。

モジュール宣言に対応するclassファイルには、バイナリ名がmodule-infoで、スーパークラス、スーパーインタフェースなし、フィールドなしおよびメソッドなしのクラスのclassファイルのプロパティが必要です。 さらに、モジュールのバイナリ表現には、次のすべてが含まれている必要があります:

  • moduleの後に指定される名前のシンボリック参照として指定されたモジュール名の指定。 また、この仕様には、モジュールが正常かオープンか(§7.7)が含まれている必要があります。

  • requiresディレクティブによって示される各依存性の指定。ディレクティブによって示されるモジュールの名前へのシンボリック参照として与えられます(§7.7.1)。 また、この仕様には、依存性がtransitiveかどうか、および依存性がstaticかどうかが含まれている必要があります。

  • exportsまたはopensディレクティブで示される各パッケージの仕様で、ディレクティブによって示されるパッケージの名前のシンボリック参照として指定されます(§7.7.2)。 また、ディレクティブが修飾されている場合は、ディレクティブのto句によって示されるモジュールの名前へのシンボリック参照を指定する必要があります。

  • usesディレクティブで示される各サービスの仕様で、ディレクティブによって示されるクラスまたはインタフェースの名前のシンボリック参照として指定されます(§7.7.3)。

  • providesディレクティブで示されるサービス・プロバイダの仕様で、ディレクティブのwith句(§7.7.4)で示されるクラスおよびインタフェースの名前へのシンボリック参照として指定されます。 また、この仕様では、ディレクティブによってサービスとして指定されたクラスまたはインタフェースの名前へのシンボリック参照を指定する必要があります。

次の各項では、既存のバイナリとの互換性を損なうことなく、クラスおよびインタフェース宣言に加えられる可能性のある変更について説明します。 前述の変換要件では、Java Virtual Machineとそのclassファイル形式がこれらの変更をサポートしています。 前述の要件の下でクラス・ローダーによってclassファイルにマップされる圧縮または暗号化された表現など、その他の有効なバイナリ形式は、これらの変更も必ずサポートします。

13.2. バイナリ互換性とは

以前にエラーなしでリンクされていた既存のバイナリがエラーなしでリンクし続ける場合、型への変更はバイナリ互換性(バイナリ互換性のブレークと同等ではない)です。

バイナリは、他のクラスおよびインタフェースのアクセス可能なメンバーおよびコンストラクタに依存するようにコンパイルされます。 バイナリ互換性を維持するために、クラスまたはインタフェースは、アクセス可能なメンバーおよびコンストラクタ、その存在および動作を、そのユーザーとの契約として扱う必要があります。

Javaプログラミング言語は、契約への追加や偶発的な名前の衝突によってバイナリ互換性が損なわれないように設計されています。 具体的には、特定のメソッド名をオーバーロードするメソッドを追加しても、既存のバイナリとの互換性が損なわれることはありません。 既存のバイナリがメソッド検索に使用するメソッド・シグネチャは、コンパイル時にオーバーロード解決アルゴリズムによって選択されます(§15.12.2)。

実行する特定のメソッドが実行時に選択されるようにJavaプログラミング言語が設計されている場合は、実行時にそのような曖昧さが検出される可能性があります。 このような規則は、呼び出しサイトであいまいさを可能にするために、追加のオーバーロードされたメソッドを追加すると、未知の数の既存のバイナリとの互換性が損なわれる可能性があることを意味します。 詳細は、§13.4.23を参照してください。

バイナリ互換性は、ソースの互換性と同じではありません。 特に、§13.4.6の例では、互換性のあるバイナリのセットが、すべてをまとめてコンパイルしないソースから生成できることを示しています。 この例は一般的です。新しい宣言が追加され、ソースコードの変更されていない部分で名前の意味が変更されますが、ソースコードのその変更されていない部分の既存のバイナリは、名前の完全修飾された以前の意味を保持します。 一貫したソース・コード・セットを生成するには、前の意味に対応する修飾名またはフィールド・アクセス式を指定する必要があります。

13.3.  パッケージおよびモジュールの展開

既存のバイナリとの互換性を損なうことなく、新しいトップレベルクラスまたはインタフェースをパッケージに追加できます。ただし、新しいクラスまたはインタフェースが、以前に無関係なクラスまたはインタフェースに指定された名前を再利用しない場合です。 新しいクラスまたはインタフェースが、関連のないクラスまたはインタフェースに以前に指定された名前を再利用する場合、両方のクラスまたはインタフェースのバイナリを同じクラス・ローダーでロードできなかったため、競合が発生する可能性があります。

publicではなく、それぞれpublicクラスまたはインタフェースのスーパークラスまたはスーパーインタフェースではないトップ・レベルのクラスおよびインタフェースの変更は、それらが宣言されているパッケージ内のクラスおよびインタフェースにのみ影響します。 このようなクラスおよびインタフェースは、このパッケージの影響を受けるバイナリが一緒に更新される場合にかぎり、ここで非互換性が説明されていても削除または変更されることがあります。

パッケージをエクスポートまたはオープンするように宣言されたモジュールが、パッケージのエクスポートまたはオープンを行わないように変更された場合、またはパッケージを別のセットにエクスポートまたはオープンする場合友人の場合、パッケージのpublicおよびprotectedクラスおよびインタフェースにアクセスできなくなった既存のバイナリがリンクされていると、IllegalAccessErrorがスローされます。 広範に配布されたモジュールの場合、このような変更を行わないことをお薦めします。

特定のパッケージをエクスポートまたは開くようにモジュールが宣言されていない場合、パッケージをエクスポートまたは開くようにモジュールを変更しても、既存のバイナリとの互換性が損なわれることはありません。 ただし、パッケージをエクスポートするようにモジュールを変更すると、そのモジュールを読み取るモジュールによって、同じ名前のパッケージをエクスポートする他のなんらかのモジュールも読み取られることがあるため、プログラムが開始されなくなる場合があります。

requiresディレクティブをモジュール宣言に追加したり、transitive修飾子をrequiresディレクティブに追加したりしても、既存のバイナリとの互換性は損なわれません。 ただしこの場合は、モジュールが、同じ名前のパッケージをエクスポートする複数のモジュールを読み取るようになることがあるため、プログラムが開始されなくなる場合があります。

モジュール宣言でrequiresディレクティブを削除するか、requiresディレクティブからtransitive修飾子を削除すると、そのモジュールによってエクスポートされたクラスおよびインタフェースを参照する過程で、特定のモジュールを読みやすくするためにディレクティブまたは修飾子に依存する既存のバイナリとの互換性が損なわれる可能性があります。 IllegalAccessErrorは、既存のバイナリからのそのような参照がリンクされている場合にスローされます。

モジュール宣言でusesまたはprovidesディレクティブを追加または削除しても、既存のバイナリとの互換性は損なわれません。

13.4.  クラスの展開

この項では、クラスとそのメンバーおよびコンストラクタの宣言に対する変更が既存のバイナリに与える影響について説明します。

13.4.1. abstractクラス

abstractが宣言されていないクラスがabstractとして宣言されるように変更された場合、そのクラスの新しいインスタンスを作成しようとする既存のバイナリは、リンク時にInstantiationErrorをスローするか、または(リフレクティブ・メソッドが使用されている場合)実行時にInstantiationExceptionをスローします。したがって、このような変更は、広く分散されているクラスにはお薦めしません。

abstractとして宣言されているクラスをabstractとして宣言しないように変更しても、既存のバイナリとの互換性は損なわれません。

13.4.2. sealednon-sealedおよびfinalクラス

13.4.2.1. sealedクラス

自由に拡張可能なクラス(§8.1.1.2)がsealedとして宣言されるように変更された場合、このクラスの既存のサブクラスのバイナリがロードされ、このクラスの直接サブクラス(§8.1.6)が許可されていない場合、IncompatibleClassChangeErrorがスローされます。このような変更は、広く分散されているクラスにはお薦めしません。

sealedとして宣言されたfinalクラスを変更しても、既存のバイナリとの互換性は損なわれません。

sealedクラスの許可された直接サブクラスのセットにクラスを追加しても、既存のバイナリとの互換性は損なわれません。

許可された直接サブクラスを追加してsealedクラスを進化させることは、バイナリ互換の変更とみなされます。これは、エラーなしで以前にリンクされていた既存のバイナリ(たとえば、完全なswitch (§14.11.1)を含むクラス・ファイル)がエラーなしでリンクし続けるためです。 完全なswitchを含むクラス・ファイルは、スイッチオーバーするsealedクラスが階層の所有者によって拡張され、新しい許可された直接サブクラスを持つ場合、リンクに失敗しません。 完全なswitchを含むクラス・ファイルをリンクする場合、JVMは完全性チェックを実行する必要はありません。

コンパイル時に認識されなかった、許可された直接サブクラスのインスタンス(§14.11.3§15.28.2)を検出した場合、switchの実行はエラー(MatchExceptionがスローされる)で失敗する可能性があります。 厳密に言えば、エラーはsealedクラスのバイナリ互換性のない変更にフラグを付けるのではなく、より正確にsealedクラスの移行互換性のない変更にフラグを付けます。

sealedクラスの許可された直接サブクラスのセットからクラスを削除すると、削除されたクラスの既存のバイナリがロードされると、IncompatibleClassChangeErrorがスローされます。

sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持たないクラスからsealed修飾子を削除しても、既存のバイナリとの互換性は損なわれません。

シール済クラスCsealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースが存在する場合、sealed修飾子を削除すると、sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持つすべてのクラスがfinalsealedまたはnon-sealedのいずれかである必要があるため、Cの再コンパイルが妨げられます。

13.4.2.2. non-sealedクラス

non-sealedとして宣言されたsealedクラスを変更しても、既存のバイナリとの互換性は損なわれません。

non-sealedとして宣言されたfinalクラスを変更しても、既存のバイナリとの互換性は損なわれません。

non-sealedクラスCには、sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェース(§8.1.1.2)が必要です。 non-sealed修飾子を削除すると、sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持つすべてのクラスがfinalsealedまたはnon-sealedのいずれかである必要があるため、Cの再コンパイルが妨げられます。

13.4.2.3. finalクラス

finalが宣言されていないクラスがfinalとして宣言されるように変更された場合、このクラスの既存のサブクラスのバイナリがロードされると、IncompatibleClassChangeErrorがスローされます。これは、finalクラスにサブクラスがない可能性があるためです。このような変更は、広く分散されているクラスにはお薦めしません。

sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持たないクラスからfinal修飾子を削除しても、既存のバイナリとの互換性は損なわれません。

finalクラスCsealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースが含まれていた場合、final修飾子を削除すると、sealedダイレクト・スーパークラスまたはsealedダイレクト・スーパーインタフェースを持つすべてのクラスがfinalsealedまたはnon-sealed (§8.1.1.2)のいずれかである必要があるため、Cの再コンパイルが妨げられます。

13.4.3. publicクラス

publicを宣言されていないクラスをpublicとして変更しても、既存のバイナリとの互換性は損なわれません。

publicとして宣言されたクラスがpublicとして宣言されないように変更された場合、既存のバイナリがリンクされていて、そのクラス・タイプにアクセスできなくなった場合は、IllegalAccessErrorがスローされます。このような変更は、広く分散されているクラスにはお薦めしません。

13.4.4.  スーパークラスおよびスーパーインタフェース

クラスが自身のスーパークラスである場合、ロード時にClassCircularityErrorがスローされます。 新しくコンパイルされたバイナリが既存のバイナリとともにロードされた場合に、そのような循環を引き起こす可能性があるクラス階層を変更することは、広く分散されたクラスにはお薦めしません。

クラスの直接スーパークラス・タイプまたは直接スーパーインタフェース・タイプのセットを変更しても、クラスのスーパークラスまたはスーパーインタフェースの合計セットがメンバーを失わないかぎり、既存のバイナリとの互換性は損なわれません。

たとえば、クラスのRAWスーパータイプを、RAW型で指定されたクラスまたはインタフェースのパラメータ化に置き換えることは、バイナリ互換性があります。

直接スーパークラスまたは直接スーパー・インタフェースのセットを変更した結果、いずれかのクラスまたはインタフェースがスーパークラスまたはスーパー・インタフェースでなくなった場合、変更されたクラスのバイナリとともに既存のバイナリがロードされると、リンケージ・エラーが発生する可能性があります。 このような変更は、広範囲に分散されたクラスにはお薦めしません。

例13.4.4-1  スーパークラスの変更

次のテスト・プログラムがあるとします:

class Hyper { char h = 'h'; }
class Super extends Hyper { char s = 's'; }
class Test extends Super {
    public static void printH(Hyper h) {
        System.out.println(h.h);
    }
    public static void main(String[] args) {
        printH(new Super());
    }
}

コンパイルおよび実行され、出力が生成されます:

h

次に、新しいバージョンのクラスSuperがコンパイルされるとします。

class Super { char s = 's'; }

このバージョンのクラスSuperは、Hyperのサブクラスではありません。 その後、HyperおよびTestの既存のバイナリを新しいバージョンのSuperで実行すると、リンク時にVerifyErrorがスローされます。 SuperHyperのサブクラスではないため、new Super()の結果はHyper型の仮パラメータのかわりに引数として渡すことができないため、ベリファイアから苦情が生じます。

検証ステップなしで発生する可能性があることを考慮するよう指示されます。 : プログラムが実行され、次のように出力されることがあります:

s

これは、ベリファイアを使用しないと、Javaタイプ・システムは、それぞれが正しいJavaコンパイラによって生成された場合でも、一貫性のないバイナリ・ファイルをリンクすることによって遅延される可能性があることを示しています。

この章では、ベリファイアがないか、ベリファイアの使用に失敗した実装は型の安全性を維持しないため、有効な実装ではありません。


例13.4.4-2. スーパークラスの概要

大まかに言えば、クライアントに対してバイナリ互換性のあるクラス変換が、そのクライアントに対してソース互換性がない可能性がある様々な状況があります。

たとえば、multi-catch句(§14.20)の代替が互いにサブクラスでもスーパークラスでもないという要件は、ソース制限にすぎません。 次のコードを実行したとします。

try {
    failByThrowingAorB();
} catch (A|B e) {
    ...
}

コードのコンパイル時に、ABにサブクラスとスーパークラスの関係がない場合に有効です。 その後、ABがそのような関係を持つように変更されるために、このクライアントに関してバイナリ互換性があります。 以前にコンパイルされたコードは引き続き実行されますが、変更はこのクライアントに関してソースとの互換性がないため、コードを再コンパイルできません。


13.4.5. クラス・タイプのパラメータ

クラスの型パラメータを追加または削除しても、それ自体はバイナリ互換性には影響しません。

このような型パラメータがフィールドまたはメソッドの型で使用されている場合、前述の型の変更には通常の意味がある可能性があります。

クラスの型パラメータの名前を変更しても、既存のバイナリには影響しません。

クラスの型パラメータの最初の境界を変更すると、その型パラメータを独自の型で使用しているメンバーの消去(§4.6)が変更され、バイナリ互換性に影響する可能性があります。 このようなバインドの変更は、メソッドまたはコンストラクタの型パラメータの最初のバインドの変更に類似しています(§13.4.13)。

他のバインドを変更しても、バイナリ互換性には影響しません。

13.4.6.  クラス本体およびメンバー宣言

既存のバイナリとの非互換性は、スーパークラスまたはサブクラスのインスタンス(それぞれstatic)メンバーとして、名前とアクセシビリティ(フィールドの場合)が同じインスタンス(それぞれstatic)メンバー、または名前とアクセシビリティおよびシグネチャと戻り型(メソッドの場合)が同じインスタンス(それぞれstatic)メンバーを追加することで発生しません。 リンクされるクラスのセットでコンパイル時エラーが発生しても、エラーは発生しません。

privateが宣言されていないクラス・メンバーまたはコンストラクタを削除すると、そのメンバーまたはコンストラクタが既存のバイナリによって使用されている場合、リンケージ・エラーが発生する可能性があります。

例13.4.6-1. クラス本体の変更

class Hyper {
    void hello() { System.out.println("hello from Hyper"); }
}
class Super extends Hyper {
    void hello() { System.out.println("hello from Super"); }
}
class Test {
    public static void main(String[] args) {
        new Super().hello();
    }
}

このプログラムは出力を生成します:

hello from Super

新しいバージョンのクラスSuperが作成されるとします。

class Super extends Hyper {}

次に、Superを再コンパイルし、TestおよびHyperの元のバイナリを使用してこの新しいバイナリを実行すると、次の出力が生成されます。

hello from Hyper

予想通り。


superキーワードを使用して、スーパークラスで宣言されたメソッドにアクセスし、現在のクラスで宣言されたメソッドをバイパスできます。 super.Identifierは、コンパイル時にスーパークラスSのメソッドmに解決されます。 メソッドmがインスタンス・メソッドの場合、実行時に呼び出されるメソッドは、superを含む式を含むクラスの直接スーパークラスのメンバーであるmと同じシグネチャを持つメソッドです。

例13.4.6-2.  スーパークラスの変更

class Hyper {
    void hello() { System.out.println("hello from Hyper"); }
}
class Super extends Hyper { }
class Test extends Super {
    public static void main(String[] args) {
        new Test().hello();
    }
    void hello() {
        super.hello();
    }
}

このプログラムは出力を生成します:

hello from Hyper

新しいバージョンのクラスSuperが作成されるとします。

class Super extends Hyper {
    void hello() { System.out.println("hello from Super"); }
}

次に、SuperおよびHyperTestではなく再コンパイルされると、既存のバイナリTestを使用して新しいバイナリを実行すると、次の出力が生成されます。

hello from Super

期待通り。


13.4.7. メンバーおよびコンストラクタへのアクセス

メンバーまたはコンストラクタの宣言されたアクセスを、より少ないアクセスを許可するように変更すると、既存のバイナリとの互換性が損なわれる可能性があるため、これらのバイナリが解決されたときにリンケージ・エラーがスローされます。 アクセス修飾子をパッケージ・アクセスからprivateアクセスに変更した場合、protectedアクセスからパッケージ・アクセスまたはprivateアクセスに変更した場合、またはpublicアクセスからprotectedアクセス、パッケージ・アクセスまたはprivateアクセスに変更した場合、アクセスは少なくなります。 したがって、広く分散されているクラスでは、アクセスを制限しないようにメンバーまたはコンストラクタを変更することはお薦めしません。

驚くことに、サブクラス(すでに)がアクセスの少ないメソッドを定義しているときに、メンバーまたはコンストラクタをよりアクセスしやすいように変更してもリンク・エラーが発生しないようにバイナリ形式が定義されています。

例13.4.7-1. アクセシビリティの変更

パッケージpointsでクラスPointが定義されている場合:

package points;
public class Point {
    public int x, y;
    protected void print() {
        System.out.println("(" + x + "," + y + ")");
    }
}

プログラムによって使用される:

class Test extends points.Point {
    public static void main(String[] args) {
        Test t = new Test();
        t.print();
    }
    protected void print() {
        System.out.println("Test");
    }
}

その後、これらのクラスがコンパイルされ、Testが実行されて出力が生成されます。

Test

クラスPointのメソッドprintpublicに変更され、Pointクラスのみが再コンパイルされてから、Testの既存のバイナリで実行された場合、リンケージ・エラーは発生しません。 これは、コンパイル時にpublicメソッドがprotectedメソッドによってオーバーライドされることが不適切であっても発生します(Testprintpublicに変更されていないかぎり、この新しいPointクラスを使用してクラスTestを再コンパイルできなかったことが示されています)。


既存のサブクラスのバイナリを分割せずに、スーパークラスがprotectedメソッドをpublicに変更できるようにすると、バイナリの脆弱性が低下します。 そのような変更によってリンケージエラーが発生する可能性がある代替方法では、追加のバイナリ非互換性が作成されます。

13.4.8.  フィールド宣言

広範に配布されるプログラムがフィールドをそのクライアントに公開しないようにしてください。 これは、前述のバイナリ互換性の問題とは別の、一般的に推奨されるソフトウェア・エンジニアリングの慣例です。 クラスにフィールドを追加すると、再コンパイルされていない既存のバイナリとの互換性が損なわれる可能性があります。

修飾クラスCを持つフィールドfへの参照を想定します。 さらに、fが実際にCSのスーパークラスで宣言されたインスタンス(それぞれstatic)フィールドであり、fのタイプがXであるとします。

Cまたは C自体のスーパークラスである Sのサブクラスに、fと同じ名前を持つ X型の新しいフィールドが追加されると、リンクエラーが発生する可能性があります。 そのようなリンク・エラーは、前述以外では、次のいずれかが当てはまる場合のみ発生します。

  • 新しいフィールドが、古いフィールドよりもアクセス可能性が低い。

  • 新しいフィールドは、static (それぞれインスタンス)フィールドです。

特に、フィールド・アクセスが、互換性のない型を持つスーパークラスのフィールドを以前に参照していたため、クラスを再コンパイルできなくなった場合は、リンク・エラーは発生しません。 そのような参照がある、以前にコンパイルされたクラスは、引き続きスーパークラスで宣言されたフィールドを参照します。

例13.4.8-1. フィールド宣言の追加

class Hyper { String h = "hyper"; }
class Super extends Hyper { String s = "super"; }
class Test {
    public static void main(String[] args) {
        System.out.println(new Super().h);
    }
}

このプログラムは出力を生成します:

hyper

新しいバージョンのクラスSuperが作成されるとします。

class Super extends Hyper {
    String s = "super";
    int h = 0;
}

次に、HyperおよびSuperを再コンパイルし、Testの古いバイナリを使用して結果の新しいバイナリを実行すると、次の出力が生成されます。

hyper

Hyperのフィールドhは、Testの元のバイナリによって出力されます。 これは最初は驚くべきことのように思えるかもしれませんが、実行時に発生する非互換性の数を減らすのに役立ちます。 (理想的な世界では、再コンパイルが必要なすべてのソースファイルは、それらのいずれかが変更されるたびに再コンパイルされ、そのような予期しないことがなくなります。 しかし、このような大規模な再コンパイルは、特にインターネットにおいて、非現実的または不可能であることがよくあります。 また、前述したように、このような再コンパイルでは、ソース・コードをさらに変更する必要がある場合があります。)

別の例として、プログラムは次のようになります。

class Hyper { String h = "Hyper"; }
class Super extends Hyper { }
class Test extends Super {
    public static void main(String[] args) {
        String s = new Test().h;
        System.out.println(s);
    }
}

コンパイルおよび実行され、次の出力が生成されます。

Hyper

次に、新しいバージョンのクラスSuperがコンパイルされるとします。

class Super extends Hyper { char h = 'h'; }

結果のバイナリがHyperおよびTestの既存のバイナリとともに使用された場合でも、出力は次のようになります。

Hyper

これらのバイナリのソースをコンパイルする場合でも:

class Hyper { String h = "Hyper"; }
class Super extends Hyper { char h = 'h'; }
class Test extends Super {
    public static void main(String[] args) {
        String s = new Test().h;
        System.out.println(s);
    }
}

mainのソース・コードのhは、Superで宣言されたcharフィールドを参照していると解釈され、char値をStringに割り当てることができないため、コンパイル時にエラーが発生します。


クラスからフィールドを削除すると、このフィールドを参照する既存のバイナリとの互換性が損なわれ、既存のバイナリからのそのような参照がリンクされると、NoSuchFieldErrorがスローされます。 広く分散されているクラスから安全に削除できるのは、privateフィールドのみです。

バイナリ互換性のため、型変数(§4.4)またはパラメータ化された型(§4.5)を含むフィールドfを追加または削除することは、型がf型の消去(§4.6)である同じ名前のフィールドの追加(それぞれ削除)と同等です。

13.4.9. finalフィールドおよびstatic定数変数

finalが宣言されていないフィールドをfinalとして宣言するように変更すると、既存のバイナリとの互換性が損なわれ、フィールドに新しい値を割り当てようとする可能性があります。

例13.4.9-1 変数を finalに変更する

class Super { char s; }
class Test extends Super {
    public static void main(String[] args) {
        Super x = new Super();
        x.s = 'a';
        System.out.println(x.s);
    }
}

このプログラムは出力を生成します:

a

新しいバージョンのクラスSuperが作成されるとします。

class Super { final char s = 'b'; }

Superが再コンパイルされ、Testが再コンパイルされない場合、Testの既存のバイナリで新しいバイナリを実行すると、IllegalAccessErrorになります。


キーワードfinalを削除するか、フィールドの初期化先の値を変更しても、既存のバイナリとの互換性は損なわれません。

フィールドが定数変数(§4.12.4)で、さらにstaticの場合、キーワードfinalを削除するか、その値を変更しても、既存のバイナリとの互換性は損なわれず、実行されませんが、再コンパイルされないかぎり、フィールドの使用に関する新しい値は表示されません。 この結果は、条件付きコンパイルをサポートするという決定の副作用です(§14.22)。 (新しい値は、定数の式(§15.29)で使用が行われた場合には見られず、それ以外の場合は見られたと仮定できます。 これはそうではありません。既存のバイナリには新しい値はまったく表示されません。)

広く配布されるコードで「不定数」の問題を回避する最善の方法は、static定数変数を、実際に変更される可能性が低い値に対してのみ使用することです。 真の数学的定数以外に、ソース・コードではstatic定数変数を非常にスペアリングに使用することをお薦めします。

finalの読取り専用の性質が必要な場合は、private static変数とその値を取得するための適切なアクセッサ・メソッドを宣言することをお薦めします。 したがって、次のことを推奨します。


private static int N;
public static int getN() { return N; }

次のようにはなりません。


public static final int N = ...;

問題ありません:


public static int N = ...;

Nが読取り専用である必要がない場合。

13.4.10. staticフィールド

privateが宣言されていないフィールドがstaticとして宣言されておらず、staticとして宣言されるように変更された場合(またはその逆の場合)、リンケージ・エラー(特にIncompatibleClassChangeError)は、そのフィールドが、他の種類のフィールドを想定した既存のバイナリによって使用された場合に発生します。 このような変更は、広く配布されているコードではお薦めしません。

13.4.11. transientフィールド

フィールドのtransient修飾子を追加または削除しても、既存のバイナリとの互換性は損なわれません。

13.4.12.  メソッドおよびコンストラクタ宣言

メソッドまたはコンストラクタをクラスに追加すると、既存のバイナリとの互換性が損なわれることはありません。ただし、呼出しで、互換性のない型を持つスーパークラスのメソッドまたはコンストラクタが以前に参照されていたため、クラスを再コンパイルできなくなった場合です。 そのような参照がある、以前にコンパイルされたクラスは、引き続きスーパークラスで宣言されたメソッドまたはコンストラクタを参照します。

修飾クラスCを持つメソッドmへの参照を想定します。 さらに、mは、実際にはC (S)のスーパークラスで宣言されたインスタンス(それぞれstatic)メソッドであるとします。

mと同じシグネチャおよび戻り型を持つX型の新しいメソッドが、CまたはC自体のスーパークラスであるSのサブクラスに追加されると、リンク・エラーが発生する可能性があります。 そのようなリンク・エラーは、前述以外では、次のいずれかが当てはまる場合のみ発生します。

  • 新しいメソッドが、古いフィールドよりもアクセス可能性が低い。

  • 新しいメソッドは、static (それぞれインスタンス)メソッドです。

クラスからメソッドまたはコンストラクタを削除すると、このメソッドまたはコンストラクタを参照する既存のバイナリとの互換性が損なわれる可能性があります。既存のバイナリからの参照がリンクされている場合は、NoSuchMethodErrorがスローされます。 このようなエラーは、一致するシグネチャおよび戻り型を持つメソッドがスーパークラスで宣言されていない場合にのみ発生します。

非内部クラスのソース・コードに宣言されたコンストラクタが含まれていない場合、パラメータのないデフォルト・コンストラクタが暗黙的に宣言されます(§8.8.9)。 このようなクラスのソース・コードに1つ以上のコンストラクタ宣言を追加すると、このデフォルト・コンストラクタが暗黙的に宣言されるのを防ぎ、新しいコンストラクタの1つにもパラメータがないため、デフォルト・コンストラクタを置き換えないかぎり、コンストラクタを事実上削除します。 パラメータのないデフォルト・コンストラクタには、その宣言のクラスと同じアクセス修飾子が与えられるため、既存のバイナリとの互換性を保持する場合は、すべての置換に同じかそれ以上のアクセス権が必要です。

13.4.13. メソッドおよびコンストラクタ・タイプのパラメータ

メソッドまたはコンストラクタの型パラメータを追加または削除しても、バイナリ互換性には影響しません。

このような型パラメータがメソッドまたはコンストラクタの型で使用されている場合、前述の型の変更には通常の意味がある可能性があります。

メソッドまたはコンストラクタの型パラメータの名前を変更しても、既存のバイナリには影響しません。

メソッドまたはコンストラクタの型パラメータの最初の境界を変更すると、その型パラメータを独自の型で使用しているメンバーの消去(§4.6)が変更され、バイナリ互換性に影響する可能性があります。 具体的には、次のようになります。

  • typeパラメータをフィールドの型として使用すると、そのフィールドが削除され、同じ名前のフィールドがtype変数の新しい消去として追加されたかのように効果が現れます。

  • typeパラメータがメソッドの仮パラメータの型として使用されるが、戻り型として使用されない場合は、そのメソッドが削除されたかのように効果があります。前述の仮パラメータの型を除き、同じ新しいメソッドに置き換えられます。この仮パラメータでは、型パラメータの新しい消去がその型になります。

  • typeパラメータがメソッドの戻り型として使用されているが、メソッドの仮パラメータの型として使用されていない場合、効果は、そのメソッドが削除されたかのようになり、戻り型(現在はtypeパラメータの新しい消去)を除き、同じ新しいメソッドに置き換えられます。

  • typeパラメータがメソッドの戻り型として使用され、メソッドの1つ以上の仮パラメータの型として使用される場合、そのメソッドが削除され、新しいメソッドに置き換えられたかのように効果が現れます。これは、戻り型(現在は型パラメータの新しい消去)を除き、同じです。ただし、前述の仮パラメータの型(型として型パラメータの新しい消去)は除きます。

他のバインドを変更しても、バイナリ互換性には影響しません。

13.4.14. メソッドおよびコンストラクタの正式パラメータ

メソッドまたはコンストラクタの仮パラメータの名前を変更しても、既存のバイナリには影響しません。

メソッドの名前、または仮パラメータの型をメソッドまたはコンストラクタに変更したり、パラメータを追加したり、メソッドまたはコンストラクタ宣言からパラメータを削除すると、新しいシグネチャを持つメソッドまたはコンストラクタで、古いシグネチャを持つメソッドまたはコンストラクタを削除し、新しいシグネチャを持つメソッドまたはコンストラクタを追加した場合(§13.4.12)。

メソッドの最後の仮パラメータの型をT[]からT型の可変引数パラメータ、つまりT... (§8.4.1)に変更しても、既存のバイナリには影響しません。

バイナリ互換性のために、署名に型変数(§4.4)またはパラメータ化された型(§4.5)が含まれるメソッドまたはコンストラクタmを追加または削除することは、署名がmの署名の消去(§4.6)である他の同等のメソッドの追加(それぞれ削除)と同等です。

13.4.15. メソッド結果タイプ

メソッドの結果型を変更したり、結果型をvoidに置き換えたり、voidを結果型に置き換えたりすると、古いメソッドを削除し、新しい結果型または新しくvoid結果で新しいメソッドを追加するという効果が結合されます(§13.4.12を参照)。

バイナリ互換性のために、戻り型が型変数(§4.4)またはパラメータ化された型(§4.5)を含むメソッドまたはコンストラクタmを追加または削除することは、戻り型がmの戻り型(§4.6)の消去(§4.6)である他の同等のメソッドの追加(それぞれ削除)と同等です。

13.4.16. abstractメソッド

abstractと宣言されているメソッドをabstractと宣言しないように変更しても、既存のバイナリとの互換性は損なわれません。

abstractを宣言されていないメソッドをabstractとして変更すると、そのメソッドを以前に起動した既存のバイナリとの互換性が損なわれ、AbstractMethodErrorが発生します。

例13.4.16-1. メソッドを abstractに変更する

class Super { void out() { System.out.println("Out"); } }
class Test extends Super {
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println("Way ");
        t.out();
    }
}

このプログラムは出力を生成します:

Way
Out

新しいバージョンのクラスSuperが作成されるとします。

abstract class Super {
    abstract void out();
}

Superが再コンパイルされたがTestではない場合、クラスTestにはメソッドoutの実装がなく、abstractであるため、Testの既存のバイナリで新しいバイナリを実行すると、AbstractMethodErrorになります。


13.4.17. finalメソッド

finalと宣言されているメソッドをfinalと宣言しないように変更しても、既存のバイナリとの互換性は損なわれません。

finalを宣言されていないインスタンス・メソッドをfinalとして変更すると、メソッドをオーバーライドする機能に依存する既存のバイナリとの互換性が損なわれる可能性があります。

例13.4.17-1. メソッドを finalに変更する

class Super { void out() { System.out.println("out"); } }
class Test extends Super {
    public static void main(String[] args) {
        Test t = new Test();
        t.out();
    }
    void out() { super.out(); }
}

このプログラムは出力を生成します:

out

新しいバージョンのクラスSuperが作成されるとします。

class Super { final void out() { System.out.println("!"); } }

Superが再コンパイルされ、Testが実行されない場合、Testの既存のバイナリで新しいバイナリを実行すると、IncompatibleClassChangeErrorが発生します。これは、クラスTestがインスタンス・メソッドoutを不適切にオーバーライドしようとするためです。


finalを宣言されていないクラス(static)メソッドをfinalとして変更しても、既存のバイナリとの互換性は損なわれません。これは、メソッドをオーバーライドできなかったためです。

13.4.18. nativeメソッド

メソッドのnative修飾子を追加または削除しても、既存のバイナリとの互換性は損なわれません。

型の変更が、再コンパイルされていない既存のnativeメソッドに与える影響は、この仕様の範囲外であるため、実装の説明とともに指定する必要があります。 このような影響を制限する方法でnativeメソッドを実装することをお薦めしますが、必須ではありません。

13.4.19. staticメソッド

privateが宣言されていないメソッドもstatic (つまり、クラス・メソッド)として宣言され、static (つまり、インスタンス・メソッド)が宣言されないように変更された場合、またはその逆の場合、既存のバイナリとの互換性が損なわれ、既存のバイナリでこれらのメソッドが使用されている場合、リンク時間エラー(つまり、IncompatibleClassChangeError)が発生する可能性があります。 このような変更は、広く配布されているコードではお薦めしません。

13.4.20. synchronizedメソッド

メソッドのsynchronized修飾子を追加または削除しても、既存のバイナリとの互換性は損なわれません。

13.4.21. メソッドおよびコンストラクタのスロー

メソッドまたはコンストラクタのthrows句を変更しても、既存のバイナリとの互換性が損なわれることはありません。これらの句はコンパイル時にのみチェックされます。

13.4.22. メソッドおよびコンストラクタ本体

メソッドまたはコンストラクタの本体を変更しても、既存のバイナリとの互換性が損なわれることはありません。

メソッドのキーワードfinalは、メソッドを安全にインライン化できることを意味するものではなく、メソッドをオーバーライドできないことを意味します。 そのメソッドの新しいバージョンがリンク時に提供される可能性は依然としてあります。 また、元のプログラムの構造は、反射のために保存する必要があります。

したがって、Javaコンパイラはコンパイル時にインラインでメソッドを拡張できないことに注意してください。 一般に、実装では遅延(実行時)コードの生成と最適化を使用することをお薦めします。

13.4.23. メソッドおよびコンストラクタのオーバーロード

既存のメソッドまたはコンストラクタをオーバーロードする新しいメソッドまたはコンストラクタを追加しても、既存のバイナリとの互換性が損なわれることはありません。 各呼出しに使用されるシグネチャは、これらの既存のバイナリがコンパイルされたときに決定されたため、新しく追加されたメソッドまたはコンストラクタは、そのシグネチャが適用可能であり、最初に選択されたシグネチャよりも限定的である場合でも使用されません。

新しいオーバーロードされたメソッドまたはコンストラクタを追加すると、クラスまたはインタフェースが次回コンパイルされるときにコンパイル時にエラーが発生することがあります。これは、最も具体的なメソッドまたはコンストラクタがないためです(§15.12.2.5)。このエラーは、実行時にオーバーロード解決が行われないため、プログラムの実行時に発生しません。

例13.4.23-1 オーバーロードされたメソッドの追加

class Super {
    static void out(float f) {
        System.out.println("float");
    }
}
class Test {
    public static void main(String[] args) {
        Super.out(2);
    }
}

このプログラムは出力を生成します:

float

新しいバージョンのクラスSuperが作成されるとします。

class Super {
    static void out(float f) { System.out.println("float"); }
    static void out(int i)   { System.out.println("int");   }
}

Superが再コンパイルされ、Testが再コンパイルされない場合は、テストの既存のバイナリを使用して新しいバイナリを実行すると、出力が生成されます。

float

ただし、Testが再コンパイルされ、この新しいSuperを使用すると、出力は次のようになります。

int

前回のケースでは期待通りだったかもしれません。


13.4.24.  メソッドのオーバーライド

インスタンス・メソッドがサブクラスに追加され、スーパークラスのメソッドをオーバーライドした場合、サブクラス・メソッドは既存のバイナリのメソッド呼出しによって検出され、これらのバイナリは影響を受けません。

クラス・メソッドがクラスに追加された場合、メソッド呼出しの修飾クラスがサブクラスでないかぎり、このメソッドは見つかりません。

13.4.25.  静的イニシャライザ

クラスの静的イニシャライザ(§8.7)を追加、削除または変更しても、既存のバイナリには影響しません。

13.4.26.  列挙クラスの展開

enumクラス内でenum定数を追加したり、並べ替えても、既存のバイナリとの互換性が失われることはありません。

sealedクラス(§13.4.2.1)と同様に、enumクラスにenum定数を追加することはバイナリ互換の変更とみなされますが、コンパイル時に不明な新しいenum定数(§14.11.3§15.28.2)がswitchで検出された場合、完全なswitch (§14.11.1)の実行が失敗する可能性があります。

enumクラスからenum定数を削除すると、enum定数に対応するpublicフィールドが削除されます(§8.9.3)。 結果は、§13.4.8に記載されています。 広く配布されるenumクラスについては、このような変更はお薦めしません。

他のあらゆる点において、enumクラスに関するバイナリ互換性のルールは標準クラスに関するものと同じです。

13.4.27.  レコード・クラスの展開

レコードクラス内のレコードコンポーネントを追加、削除、変更、または並べ替えると、再コンパイルされていない既存のバイナリとの互換性が損なわれる可能性があります。このような変更は、広く配布されているレコードクラスでは推奨されません。

より正確には、レコード・コンポーネントの追加、削除、変更または順序変更によって、コンポーネント・フィールドおよびアクセッサ・メソッドの対応する暗黙的な宣言が変更されるだけでなく、正規コンストラクタおよびその他のサポート・メソッドの署名および実装が変更される可能性があり、その結果は§13.4.8および§13.4.12で指定されます。

他のすべての点では、レコード・クラスのバイナリ互換性ルールは通常のクラスのルールと同じです。

13.5.  インタフェースの展開

この項では、インタフェースとそのメンバーの宣言に対する変更が既存のバイナリに与える影響について説明します。

13.5.1. publicインタフェース

publicを宣言されていないインタフェースをpublicとして変更しても、既存のバイナリとの互換性は損なわれません。

publicと宣言されているインタフェースがpublicと宣言されないように変更されると、既存のバイナリがリンクされていて、そのインタフェース・タイプにアクセスできなくなった場合、IllegalAccessErrorがスローされます。そのため、広く分散しているインタフェースでは、このような変更はお薦めしません。

13.5.2. sealedおよびnon-sealedインタフェース

自由に拡張可能なインタフェース(§9.1.1.4)がsealedとして宣言されるように変更された場合、このインタフェースの既存のサブクラスまたはサブインタフェースのバイナリがロードされ、このインタフェースの直接サブクラスまたはサブインタフェース(§9.1.4)が許可されていない場合、IncompatibleClassChangeErrorがスローされます。このような変更は、広く分散されているクラスにはお薦めしません。

sealedインタフェースの許可された直接サブクラスまたはサブインタフェースのセットにクラスまたはインタフェースを追加しても、既存のバイナリとの互換性は損なわれません。

sealedクラス(§13.4.2.1)と同様に、sealedインタフェースの許可された直接サブクラスまたはサブインタフェースを追加することは、バイナリ互換の変更とみなされる一方で、switchがコンパイル時に不明であった新しい許可された直接サブクラスまたはサブインタフェースのインスタンス(§14.11.3§15.28.2)に遭遇した場合に、完全なswitch (§14.11.1)の実行が失敗することがあります。

sealedインタフェースの許可された直接サブクラスまたはサブインタフェースのセットからクラスまたはインタフェースが削除された場合、削除されたクラスまたはインタフェースの既存のバイナリがロードされると、IncompatibleClassChangeErrorがスローされます。

non-sealedとして宣言されたインタフェースをsealedに変更しても、既存のバイナリとの互換性は損なわれません。

non-sealedインタフェースIには、sealed直接スーパーインタフェースが必要です。 non-sealed修飾子を削除すると、sealedダイレクト・スーパーインタフェースを持つすべてのインタフェースがsealedまたはnon-sealedである必要があるため、Iの再コンパイルが妨げられます。

sealed直接スーパーインタフェースを持たないインタフェースからsealed修飾子を削除しても、既存のバイナリとの互換性は損なわれません。

シール済インタフェースIsealedダイレクト・スーパーインタフェースが存在する場合、sealed修飾子を削除すると、sealedダイレクト・スーパーインタフェースを持つすべてのインタフェースがsealedまたはnon-sealedである必要があるため、Iの再コンパイルが妨げられます。

13.5.3.  スーパーインタフェース

インタフェース階層を変更すると、§13.4.4で説明されているように、クラス階層の変更と同じ方法でエラーが発生します。 特に、クラスの以前のスーパーインタフェースがスーパーインタフェースでなくなった変更によって、既存のバイナリとの互換性が損なわれ、VerifyErrorが発生する可能性があります。

13.5.4.  インタフェース・メンバー

abstractprivateまたはstaticメソッドをインタフェースに追加しても、既存のバイナリとの互換性は損なわれません。

Cのスーパーインタフェースにフィールドを追加すると、Cのスーパークラスから継承されたフィールドが非表示になることがあります。 元の参照がインスタンス・フィールドに対するものであった場合、IncompatibleClassChangeErrorが生成されます。 元の参照が代入だった場合は、IllegalAccessErrorが結果になります。

インタフェースからメンバーを削除すると、既存のバイナリでリンク・エラーが発生する可能性があります。

例13.5.4-1. インタフェースメンバーの削除

interface I { void hello(); }
class Test implements I {
    public static void main(String[] args) {
        I anI = new Test();
        anI.hello();
    }
    public void hello() { System.out.println("hello"); }
}

このプログラムは出力を生成します:

hello

新しいバージョンのインタフェースIがコンパイルされるとします。

interface I {}

Iが再コンパイルされ、Testが再コンパイルされない場合、Testの既存のバイナリで新しいバイナリを実行すると、NoSuchMethodErrorになります。


13.5.5. インタフェース・タイプ・パラメータ

インタフェースの型パラメータに対する変更の影響は、クラスの型パラメータに類似した変更の影響と同じです。

13.5.6.  フィールド宣言

インタフェースのフィールド宣言を変更する場合の考慮事項は、§13.4.8および§13.4.9で説明されているように、クラスのstatic finalフィールドの考慮事項と同じです。

13.5.7. インタフェース・メソッド宣言

インタフェースのメソッド宣言を変更する際の考慮事項には、§13.4.7§13.4.14§13.4.15§13.4.19§13.4.21§13.4.22および§13.4.23で説明されているように、クラス内のメソッドを変更するための考慮事項が含まれます。

defaultメソッドを追加したり、メソッドをabstractからdefaultに変更しても、既存のバイナリとの互換性は損なわれませんが、既存のバイナリがそのメソッドを呼び出そうとすると、IncompatibleClassChangeErrorが発生する可能性があります。 このエラーは、メソッド呼出しの修飾インタフェースKが2つのインタフェース(IJ)のサブインタフェースであり、IJの両方が同じシグネチャと結果を持つdefaultメソッドを宣言し、IJのいずれも他方のサブインタフェースではない場合に発生します。

つまり、デフォルト・メソッドを追加することは、コンパイル時または起動時にエラーが発生した場合でも、リンク時にエラーが発生しないため、バイナリ互換の変更です。 実際には、デフォルト・メソッドを導入して偶発的なクラッシュが発生するリスクは、final以外のクラスに新しいメソッドを追加することに関連するものと似ています。 衝突が発生した場合、クラスにメソッドを追加すると、LinkageErrorがトリガーされる可能性はほとんどありませんが、子のメソッドを誤ってオーバーライドすると、予期しないメソッド動作が発生する可能性があります。 どちらの変更もコンパイル時にエラーが発生する可能性があります。

例13.5.7-1. デフォルト・メソッドの追加

interface Painter {
    default void draw() {
        System.out.println("Here's a picture...");
    }
}

interface Cowboy {}

public class CowboyArtist implements Cowboy, Painter {
    public static void main(String... args) {
        new CowboyArtist().draw();
   }
}

このプログラムは出力を生成します:

Here's a picture...

デフォルト・メソッドがCowboyに追加されるとします。

interface Cowboy {
    default void draw() {
        System.out.println("Bang!");
    }
}

Cowboyが再コンパイルされ、CowboyArtistが実行されない場合、CowboyArtistの既存のバイナリで新しいバイナリを実行すると、エラーなしでリンクされますが、maindraw()を起動しようとすると、IncompatibleClassChangeErrorが発生します。


13.5.8.  注釈インタフェース

注釈インタフェースは、他のインタフェースとまったく同様に動作します。 注釈インタフェースからの要素の追加または削除は、メソッドの追加または削除に類似しています。 注釈インタフェースを繰り返し可能にする(§9.6.3)など、注釈インタフェースに対するその他の変更を制御する重要な考慮事項がありますが、これらはJava Virtual Machineによるバイナリのリンクには影響しません。 かわりに、このような変更は、プログラム内の注釈の存在を明らかにするJava SEプラットフォームでのリフレクティブAPIの動作に影響します。 API仕様は、基礎となる注釈インタフェース(§1.4)に対して様々な変更が行われた場合の動作を記述します。

注釈を追加または削除しても、Javaプログラミング言語のプログラムのバイナリ表現の正しいリンクには影響しません。