Javaでのコントラクトプログラミング

こんにちは

Java Core遠隔教育コースの詳細な研究の一環として、一連の出版物を執筆し、最も人気のある記事のいくつかを翻訳しています。



オンライン教育プラットフォームudemy.comでScala for Java Developersコースも教えています(Coursera / EdXに似ています)。



ここで、 「アサーションを使用したプログラミング」の翻訳をいくつかのコメントとともに検討してください。



オリジナルの出版物は、Javaでassertキーワードを使用するオプションと、クラスローディングレベルでこのメカニズムのサポートがどのように実装されるかを詳細に説明しているだけでなく、 Design-by-Contractのやや非公式の紹介でもあります。



その他のJava Coreの記事と翻訳
コースプログラム「Java Core」

ロシア語で1000時間以上のJavaビデオ

Javaアノテーション、パートI



Multicore Javaプログラミングコースの他の記事と翻訳
コース「Javaでのマルチコアプログラミング」のプログラム

Java / GParsベースのアクターの紹介、パートI

JSR 133(Java Memory Model)FAQ(翻訳)





PS著者は、個人的にはロシア語がJava言語よりもはるかに複雑であることを認め、気付いたすべてのエラーについてPMに感謝して耳を傾け、できるだけ早く修正しようとします。






アサーションプログラミング(oracle.com:アサーションを使用したプログラミング)



アサートとは、プログラムに関する仮定をテストできるJavaプログラミング言語のステートメントです。 たとえば、パーティクルの速度を計算するメソッドを記述する場合、計算された速度が光の速度よりも遅いと「主張」できます。



各ステートメントには、あなたの意見では、実行時に真になる論理式が含まれています。 そうでない場合、システムは例外をスローします。 論理式が実際に真であることを検証することにより、アサートはプログラムの動作に関する仮定を確認し、プログラムにエラーがないという確信を高めます。



プログラミングでステートメントを書くことは、エラーを検出して修正するための最速かつ最も効果的な方法の1つであることが経験により示されています。 追加の利点として、ステートメントは、プログラムの内部動作を文書化し、保守性を高めるのに役立ちます。



この記事では、ステートメントを使用してプログラムする方法を示します。 次のトピックについて説明します。

はじめに

コードへのステートメントの埋め込み

クレームを使用したファイルのコンパイル

クレームの有効化と無効化

既存のプログラムとの互換性

設計に関するよくある質問





はじめに



assert文には2つの形式があります。 最初の、より単純な形式は次のとおりです。

assert Expression1;
      
      





1はブール式です。 システムがステートメントをチェックするとき、 1を評価し、それがfalse( false )の場合、システムは詳細なエラーメッセージなしでjava.lang.AssertionErrorをスローします

承認の2番目の形式は次のとおりです。

 assert Expression1 : Expression2;
      
      





ここで:



このバージョンのassertステートメントを使用して、詳細なエラーメッセージを提供します。 システムは、式2の値を適切なAssertionErrorコンストラクターに渡して、値の文字列表現を詳細なエラーメッセージとして使用します。

翻訳者コメント
AssertErrorには、各データ型の個別のコンストラクターが含まれています(shortとbyteは自動的にintにキャストされ、コンストラクターは提供されませんでした)

 package java.lang; public class AssertionError extends Error { ... public AssertionError(Object detailMessage) {...} public AssertionError(boolean detailMessage) {...} public AssertionError(char detailMessage) {...} public AssertionError(int detailMessage) {...} public AssertionError(long detailMessage) {...} public AssertionError(float detailMessage) {...} public AssertionError(double detailMessage) {...} ... }
      
      





これにより、任意のプリミティブ型または参照型の式2として使用できます

 public class App { public static void main(String[] args) { assert args != null : 1; assert args != null : 1.0; assert args != null : false; assert args != null : "Hello!"; assert args != null : new int[] {10, 20, 30}; }
      
      





または何かを返す任意のメソッドの呼び出し

 public class App { public static void main(String[] args) { assert args != null : f0(); assert args != null : f1(); assert args != null : f2(); assert args != null : f3(); assert args != null : f4(); } private static byte f0() {return 0;} private static double f1() {return 0.0;} private static boolean f2() {return true;} private static String f3() {return "Hello!";} private static int[] f4() {return new int[] {10, 20, 30};} }
      
      





ただし、voidを返すメソッドを呼び出していない

 public class App { public static void main(String[] args) { assert args != null : f(); //    } private static void f() {} } >> COMPILATION ERROR: 'void' type is not allowed here
      
      









このようなエラーメッセージの目的は、ステートメント違反の理由に関する情報を記録および報告することです。 このメッセージにより、診断が可能になり、最終的に、ステートメントが失敗する原因となったエラーを排除できるはずです。 このメッセージは、ユーザーに対するエラーメッセージではないことに注意してください。 一般に、これらのメッセージを独自に理解可能にする必要も、国際化する(ユーザーの言語に翻訳する)必要もありません。 詳細メッセージは、失敗したステートメントを含むソースコードと組み合わせて、完全なスタックトレースのコンテキストで解釈する必要があります。



すべての未処理の例外と同様に、アサーションエラーには通常、ファイル番号とそれらがスローされた行を含むスタックトレースが含まれます。 プログラムに障害の診断に役立つ追加情報がある場合にのみ、2番目の形式の承認を最初の形式よりも優先して使用する必要があります。 たとえば、 1に2つのx変数とy変数の関係が含まれる場合、2番目の形式を使用する必要があります。 これらの条件下では、 2の合理的な変数は"x:" + x + "、y:" + yになります。

翻訳者コメント
これは、たとえば次の例を参照します。

 class App { public static void f(){ int x, y; //   assert x > y; } }
      
      





ステートメント違反(x> y)が発生すると、単にAsertionErrorをスローします。

しかし、そのような例

 class App { public static void f(){ int x, y; //   assert x > y : "x: " +  + ", : " + ; } }
      
      





「x:0、y:123」などのメッセージを含むAsertionErrorをスローしますこれにより、プログラマは計算時に特定の無効なxおよびyの値を分析できます。





場合によっては、 1の計算にコストがかかることがあります。 たとえば、ソートされていないリストの最小要素のシーカーメソッドを記述するときに、選択した要素が実際に最小であることを確認するステートメントが追加されたとします。 この場合、ステートメントの検証は、メソッド自体の実行と同じくらい「高価」になります。 ステートメントがアプリケーションのパフォーマンスを損なわないようにするために、プログラムの起動時にステートメントを有効または無効にすることができます。 デフォルトでは無効になっています。 承認を無効にすると、パフォーマンスの低下が完全になくなります。 無効にすると、セマンティクスとパフォーマンスの点で空のステートメントと同等になります。 詳細については、「 クレームの有効化と無効化」を参照してください。

翻訳者コメント
ArrayUtils.min(int [])メソッドでは、ポストインバリアントの2つのチェックと複雑さの両方が、最小要素を見つける複雑さ(O(N))と釣り合っています。

 public class ArrayUtils { public static int min(int[] array) { int min; //     //  min -    array assert checkMin(array, min); assert checkContains(array, min); return min; } // ,        private static boolean checkMin(int[] array, int min) { for (int elem : array) { if (elem < min) { return false; } } return true; } // ,     "" private static boolean checkContains(int[] array, int min) { for (int elem : array) { if (elem == min) { return true; } } return false; } }
      
      







翻訳者コメント
空の演算子は、明らかに「セミコロン」(;)演算子を意味します

 public class App { public static void main(String[] args) { ;;;; ;;;;;;;; ;;; ;; ;;; ;;;;;;;;;; ;; ;; ;;;;;;;; ;;;; for (int k = 0; k < 10; k++) { ; } } }
      
      









assertキーワードをJavaに追加する 、既存のコードに影響があります。 詳細については、既存のプログラムとの互換性を参照してください。





コードへのステートメントの埋め込み



次のようなステートメントを使用すると便利な状況が多くあります。





それらを使用すべきではない状況もあります。



翻訳者コメント
副作用(副作用)-「見える世界」を変えるアクション。

単純に

tmp x = 1 + 2 + 3;

「目に見えない世界」を変更しません(以下の誰もプログラムでtmp識別子を使用しない場合)



フルフィルメント

System.out.println( "Hello!");

「目に見える世界」を変更します(副作用があります)-このような行を持つプログラムは明らかにコンソールに「もっと」出力しますが、それでも下のプログラムには影響しません-読み取りデータは変更しません。



しかし、行

tmp = 2 * tmp;

tmp変数の値を読み取って使用すると、以下のプログラムに影響します(副作用があります)。







内部不変量


言語でステートメントが使用可能になる前に、多くのプログラマーはコメントを使用して、プログラムの動作に関する仮定を「述べ」ました。 たとえば、if-elseif-elseif- ...の長いチェーン内のelseセクションに関する仮定を開示するには。 以前にこのようなものを書いた:

 if (i % 3 == 0) { ... } else if (i % 3 == 1) { ... } else{ //    (i % 3 == 2) ... }
      
      





ここで、各コメントの代わりに「明らかに真のステートメント」を使用したステートメントを使用する価値があります。 たとえば、前のコードを次のように書き換えます。

 if (i % 3 == 0) { ... } else if (i % 3 == 1) { ... } else { assert i % 3 == 2 : i; ... }
      
      





'i'が負の場合、%演算子は剰余を計算するため、上記の例のステートメントは失敗する可能性があることに注意してください。

翻訳者コメント
一例


ステートメントを使用する別の候補は、デフォルトのセクション切り替えステートメントです。 通常、デフォルトセクションがないことは、ケースセクションの1つが常に選択されるというプログラマの自信を示しています。 変数が少数の値の1つを持つという仮定は不変であり、ステートメントを使用して検証する必要があります。 カードゲームを扱うプログラムに次のswitchステートメントが表示されるとします。

 switch(suit) { caseSuit.CLUBS: //  ... break; caseSuit.DIAMONDS: //  ... break; caseSuit.HEARTS: //  ... break; caseSuit.SPADES: //  ... }
      
      





ほとんどの場合、これはスーツ型変数が4つの値のいずれかを持つという仮定を示しています。 この仮定をテストするには、次のデフォルトセクションを追加します。

 default: assert false : suit;
      
      





変数のセットが異なる値を想定し、アサーションが含まれている場合、アサーションは失敗し、 AssertionError例外をスローします。

許容されるオプションは次のとおりです。

 default: throw new AssertionError(suit);
      
      





このイディオムは、クレームが無効になっている場合でも保護を提供します。 そして、この追加の保護は何の価値もありません: throwステートメントは、プログラムがクラッシュした場合にのみ実行されます。 さらに、このオプションは、 アサートが無効な特定の状況で有効です。 メソッドがメソッドを返す場合、switchステートメントの各ケースにreturnが含まれ、切り替え後に戻り結果がない場合、最初のオプションは構文エラーになります。

翻訳者コメント
このプログラムはコンパイル中です

 public class App { public static int twice(int arg) { switch (arg) { case 0: return 0; case 1: return 2; case 2: return 4; case 3: return 6; default: throw new AssertionError(arg); } } }
      
      





しかし、これはそうではありません

 class App { public static int twice(int arg) { switch (arg) { case 0: return 0; case 1: return 2; case 2: return 4; case 3: return 6; default: assert false : arg; } } } COMPILATION ERROR: Missing return statement
      
      











実行時不変式




前の例では、不変式がテストされるだけでなく、実行のスレッドに関する仮定もチェックします。 元のswitchステートメントの作成者は、 suit変数が常に4つの値のいずれかを常に持つだけでなく、caseセクションの1つに「進む」ことも想定しています。 これは、ステートメントを使用する必要がある別の領域を指します。 決して到達しないと思われる場所にステートメントを配置します。 次のステートメントを使用する必要があります。

 assert false;
      
      





次のメソッドがあるとします:

 void foo() { for (...) { if (...) {return;} ... } //      !!! }
      
      





コードが次のようになるように、最終コメントを置き換える必要があります。

 void foo() { for (...) { if (...) {return;} } assert false; //      !!! }
      
      







注:この方法は注意して使用してください。 Java仕様に従って状態が使用不可であると判断された場合、ステートメントをそこに配置しようとすると、コンパイル時エラーが表示されます。 繰り返しになりますが、受け入れ可能な代替手段はAssertionErrorをスローすることです。

翻訳者コメント
Java仕様によると、コンパイラは「使用不可」の演算子をチェックする必要があります。 「アクセスできない」演算子が検出されると、コンパイルエラーが発生します。 前提条件:実行されないステートメントを含むプログラムは、プログラマーエラーの結果である可能性が高い

 public class App { public static void main(String[] args) { return; return; //    } } >> COMPILATION ERROR: Unreachable statement
      
      





検証ルールは厳密に記述されており、考えられるすべての状況が含まれているわけではありません。 コンパイラを「だます」のはとても簡単だと言ってみましょう。

 public class App { public static void main(String[] args) { if (true) {return;} return; //   ,    } }
      
      











前提条件、事後条件(事後条件)クラス不変条件




assertコンストラクトは、契約ごとの設計のための本格的な機会を提供するものではありませんが、非公式の契約ごとのプログラミングスタイルを維持するのに役立ちます。 このセクションでは、次のような状況でステートメントを使用する方法を示します。







前提条件


慣例により、パブリックメソッドの前提条件は、特定の特定の例外をスローするチェックを生成します。 例:

 /** *    (refresh rate). * * @param rate  ,     . * @throws IllegalArgumentException  (rate <= 0)  (rate > MAX_REFRESH_RATE). */ public void setRefreshRate(int rate) { //       if (rate <= 0 || rate > MAX_REFRESH_RATE) throw new IllegalArgumentException("Illegal rate: " + rate); setRefreshInterval(1000 / rate); }
      
      





この規則は、assertステートメントが追加されても変更されませんでした。 ステートメントを使用して、パブリックメソッドのパラメーターを確認しないでください 。 メソッドは引数を常にチェックすることを保証するため、この場合のステートメントの使用は不適切です。 彼は、ステートメントが独立性に含まれているかどうかの議論をチェックしなければなりません。 さらに、指定されたタイプの例外をスローするために、assertコンストラクトを指定することはできません。 AssertionErrorのみを生成できます。



ただし、文を使用して非パブリックメソッドの前提条件をテストすることができます。これは、計算時に、クライアントがクラスで何をしても関係なく実行されます。 たとえば、前のパブリックメソッドによって呼び出される次のヘルパーメソッドでは、ステートメントが適切です。

 /** *    (    ) * * @param interval    . */ private void setRefreshInterval(int interval) { //     (nonpublic)  assert (interval > 0) && (interval <= 1000 / MAX_REFRESH_RATE) : interval; ... //    }
      
      





注: MAX_REFRESH_RATEが 1000を超える場合、またはクライアントが1000を超える更新レートを選択した場合、このステートメントは失敗します。実際、これはライブラリーのエラーを示しています。





ロック状態の前提条件




マルチスレッドでの使用を目的としたクラスには、いくつかのロックが取得されるか、またはその逆の自由があるという前提条件を持つ非パブリックメソッドがしばしばあります。 このようなものを見ることは珍しくありません:

 privateObject[] a; public synchronized int find(Object key) { return find(key, a, 0, a.length); } / /    -   c      private int find(Object key, Object[] arr, int start, int len) { ... }
      
      





holdLock(Object)という静的メソッドがThreadクラスに追加され、現在のスレッドが指定されたオブジェクトのロックを保持しているかどうかをテストします。 このメソッドは、次の例に示すように、コメントを補足するステートメントと組み合わせて使用​​できます。

 / /         : private int find(Object key, Object[] arr, int start, intlen) { assert Thread.holdsLock(this); // lock-status assertion ... }
      
      





また、ロックがキャプチャされないというステートメントを記述することもできます。

翻訳者コメント
java.util.concurrentの多くのクラスで同様のチェックが可能です。

ReentrantLock.isHeldByCurrentThread() -正確な対応物

ReentrantReadWriteLock.isWriteLockedByCurrentThread() -正確なアナログ

Semaphore.availablePermits() -同様の分析に使用できます

...







事後条件




パブリックメソッドと非パブリックメソッドの両方のステートメントを使用して、事後条件を検証できます。 たとえば、次のパブリックメソッドはステートメントを使用して事後条件をテストします。

値(this -1 mod m)を持つBigIntegerを返します。

  /** * @param m  * @return this^(-1) mod m. * @throws ArithmeticException  m <= 0   BigInteger *       * mod m ( BigInteger    m). */ public BigInteger modInverse(BigInteger m) { if (m.signum <= 0) { throw new ArithmeticException("Modulus not positive: " + m); } ... //   assert this.multiply(result).mod(m).equals(ONE) : this; return result; }
      
      





翻訳者コメント
代数のビット:基数N(Z N )の剰余の環では、xがNと互いに素である場合、すべての非ゼロ要素xは(および一意の要素を持つ)逆要素を持ちます。



例#1(リングZ 5の逆):

1の場合-逆= 1、1 * 1 mod 5 = 1

2の場合-逆= 3、2 * 3 mod 5 = 1

3の場合-逆= 2、3 * 2 mod 5 = 1

4の場合-逆= 4、4 * 4 mod 5 = 1



例#2(リングZ 6の逆):

1の場合-逆= 1、1 * 1 mod 6 = 1

2-返品不可

3-返品不可

4-返品不可

5の場合-逆= 5、5 * 5 mod 6 = 1



232.x

事後条件チェックに関連する計算を実行する前に、いくつかのデータを保存する必要がある場合があります。 これを行うには、2つのステートメントと、1つ以上の変数の状態を保存する単純な内部クラスを使用して、計算後にチェック(または再チェック)できるようにします。 たとえば、次のようなコードがあるとします。

  void foo(int[] array) { //     ... //          int- //    ,     }
      
      







上記のメソッドを変更して、事後条件のテキストステートメントを「機能的」に変える方法は次のとおりです。

 void foo(final int[] array) { //  (inner)         class DataCopy { private int[] arrayCopy; DataCopy() { arrayCopy = (int[]) array.clone();} boolean isConsistent() {return Arrays.equals(array, arrayCopy);} } DataCopy copy = null; //  ;         assert ((copy = new DataCopy()) != null); ... //    array // ,   array   assert copy.isConsistent(); }
      
      





このアプローチを簡単に一般化して、複数のデータ項目を保存し、任意の複雑さのステートメントをテストできます。



最初のステートメント(副作用のみを目的として実行される)を、より表現力豊かな次のステートメントに置き換えることができます。

 copy = new DataCopy();
      
      





これをしないでください。2番目の例は、ステートメントが含まれているかどうかに関係なく配列をコピーします。これは、クレームが無効にされた場合でも、コストが発生しないという原則に違反しています。





クラス不変量




クラス不変式は、クラスの各インスタンスに常に格納される内部不変式の一種です。ただし、一貫性のある状態から別の状態への遷移の瞬間は例外です。クラス不変式は、異なる属性間の関係を示すことができ、メソッドの完了前後に真でなければなりません。ある種のデータのバランスの取れたツリー構造を実装するとします。クラス不変式は次のようになります。ツリーはバランスが取れており、適切に順序付けられています。



アサーションメカニズムは、不変条件をチェックするための特定のスタイルを強制しません。ただし、必要な制約をチェックする式を組み合わせて、ステートメントをトリガーできる単一の内部メソッドにすると便利な場合があります。バランスの取れたツリーの例を続けるには、データ構造の要件に従ってツリーが本当にバランスが取れていることを確認するプライベートメソッドを実装することをお勧めします。

 //  true     private boolean balanced() { ... }
      
      





このメソッドは、メソッドの実行前後にtrueでなければならない制約をチェックするため、各パブリックメソッドとコンストラクターは、戻りの直前に次の行を含む必要があります。

 assert balanced();
      
      





原則として、データ構造がネイティブメソッドによって実装されていない場合にのみ、各パブリックメソッドのヘッダーに同様のチェックを配置する必要はありません。この場合、メモリ破損のバグにより、メソッド呼び出し間でJava「ヒープ」(「ネイティブピア」)のフレームワーク外のデータ構造が破損する可能性があります。メソッドの開始時のエラーは、この種のメモリ破損が発生したことを示します。

翻訳者コメント
つまり、クラスはJavaネイティブインターフェイス(JNI)を介して実装されます。




また、クラスの状態が他のクラスによって変更される可能性がある場合、各クラスメソッドの先頭にクラス不変条件のチェックを含めることも適切です。状態が他のクラスから直接見えないように、クラスデザインを使用することをお勧めします。

翻訳者コメント
« » ( ) « » . . «» , «» - , .







高度なテクニック



次のセクションでは、リソースとシステムが制限されているデバイスにのみ適用されるトピックについて説明します。これらのデバイスでは、クレームを無効にしないでください。このトピックに興味がない場合は、次のセクション「クレームを使用したファイルのコンパイル」に進んでください。



クラスファイルからクレームを削除する


リソースが限られているデバイス用のアプリケーションを開発するプログラマーは、クラスファイルからクレームを完全に削除したい場合があります。これにより、原則的にステートメントをアクティブにすることはできませんが、クラスファイルのサイズが小さくなり、クラスの読み込みが速くなります。高品質のJITコンパイラがない場合、これによりメモリ要件が低くなり、実行時のパフォーマンスが向上します。



クレームメカニズムは、クラスファイルからそれらを削除するための直接的なサポートを提供しません。ただし、ステートメントは「条件付きコンパイル」のイディオムと組み合わせて使用​​できます。このアプローチはJava仕様で説明されており、生成されたクラスファイル内のステートメントへのすべての「参照」をコンパイラーが排除できるようにします。

  static final boolean asserts = ... ; // false    if (asserts) assert <expr> ;
      
      





翻訳者コメント
« » . :

«An optimizing compiler may realize that the statement… will never be executed and may choose to omit the code for that statement from the generated class file ...»


( , ) javac «optimizing compiler»,

 public class App { static final boolean asserts = false; public static void main(String[] args) { //   if (asserts) { //  B } //  C } }
      
      







 public class App { static final boolean asserts = false; public static void main(String[] args) { //   //  C } }
      
      









クレームを請求する方法


一部の重要なシステムのプログラマーは、含まれるステートメントのみでコードを動作させたい場合があります。次の静的ベースのアプローチは、ステートメントが無効になっている場合、クラスの初期化を防ぎます。

 static { boolean assertsEnabled = false; assert assertsEnabled = true; //   !!! if (!assertsEnabled) throw new RuntimeException("   !!!"); }
      
      





この静的初期化子をクラス宣言の先頭に置きます。

翻訳者コメント
« » , — . つまり

 class App { static int x = f(); static { System.err.println("static{}"); } static int y = g(); static { boolean assertsEnabled = false; assert assertsEnabled = true; if (!assertsEnabled) throw new RuntimeException(); } public static void main(String[] args) {} static int f() { System.err.println("f()"); return 0; } static int g() { System.err.println("g()"); return 0; } } >> f() >> static{} >> g() >> Exception in thread "main" java.lang.ExceptionInInitializerError >> ...
      
      





.







クレームを使用したファイルのコンパイル



javacコンパイラがステートメントを含むコードを受け入れるには、次の例のように-source 1.4コマンドラインオプションを使用する必要があります

 javac -source 1.4 MyClass.java
      
      





このフラグは、ソースコードレベルで互換性の問題を引き起こさないために必要です。

翻訳者コメント
javac (-source -target)

javac -source 1.3 -target 1.4

class-. , . つまり , Java 5 Java 1.3, - Java 1.4.

1.3 , , assert , generics, varargs,…

class- 1.4 , , class- Java 1.4. class- (minor_version, major_version)

 ClassFile { ... u2 minor_version; u2 major_version; ... }
      
      





Oracle's Java Virtual Machine implementation in JDK release 1.0.2 supports class file format versions 45.0 through 45.3 inclusive. JDK releases 1.1.* support class file format versions in the range 45.0 through 45.65535 inclusive. For k ≥ 2, JDK release 1.k supports class file format versions in the range 45.0 through 44+k.0 inclusive.








クレームの有効化と無効化



既定では、要求は実行時に無効になります。2つのコマンドラインスイッチがあります。

さまざまな詳細レベルでステートメントを有効にするには、-enableassertionsまたは-eaを使用しますさまざまな詳細レベルでステートメントを無効にするには、-disableassertionsまたは-daを使用しますフラグ引数で粒度を指定します:







たとえば、次のコマンドはcom.wombat.fruitbatパッケージとそのサブパッケージに対してのみ有効になっているクレームを使用してBatTutorプログラム起動します。

 java -ea:com.wombat.fruitbat... BatTutor
      
      







コマンドラインにこのような「スイッチ」がいくつか含まれている場合、クラスをロードする前に順番に処理されます。たとえば、次のコマンドは、com.wombat.fruitbatパッケージクレームを有効にしてcom.wombat.fruitbat.Brickbatクラスを無効にしてBatTutorプログラムを起動します

 java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat BatTutor
      
      







上記のコマンドラインフラグは、1つの例外を除いてすべてのクラスローダーに適用されます。これはシステムClassLoaderです。例外は、引数なしのスイッチに関するもので、(上記のように)システムクラスには適用されません。この動作により、通常望ましいシステムクラスを除くすべてのクラスに承認を簡単に含めることができます。



すべてのシステムクラスでステートメントを有効にするには、別のフラグ-enablesystemassertionsまたは-esaを使用します。同様に、システムクラスのステートメントを無効にするには、-disablesystemassertionsまたは-dsaを使用します



たとえば、次のコマンドは、システムクラスとcom.wombat.fruitbatパッケージとそのサブパッケージの両方にクレームが含まれるBatTutorプログラムを起動します。

 java -esa -ea:com.wombat.fruitbat...
      
      







クラスの承認ステータス(オンまたはオフ)は、クラスが初期化されるときに設定され、変更されなくなります。ただし、特別な処理が必要な別のケースがあります。通常は望ましくありませんが、初期化の前にメソッドまたはコンストラクターを実行することは可能です。これは、静的初期化手順でクラス階層が循環している場合に発生する可能性があります。



クラスが初期化される前に文が実行される場合、実行は文がクラスに含まれているかのように動作する必要があります。このトピックは、Java仕様で詳しく説明されています。

翻訳者コメント
:

An assert statement that is executed before its class has completed initialization is enabled.



This rule is motivated by a case that demands special treatment. Recall that the assertion status of a class is set no later than the time it is initialized. It is possible, though generally not desirable, to execute methods or constructors prior to initialization. This can happen when a class hierarchy contains a circularity in its static initialization, as in the following example:

 public class Foo { public static void main(String[] args) { Baz.testAsserts(); // Will execute after Baz is initialized. } } class Bar { static { Baz.testAsserts(); // Will execute before Baz is initialized! } } class Baz extends Bar { static void testAsserts() { boolean enabled = false; assert enabled = true; System.out.println("Asserts " + (enabled ? "enabled" : "disabled")); } } >> Asserts enabled >> Asserts disabled
      
      





Invoking Baz.testAsserts() causes Baz to be initialized. Before this can happen, Bar must be initialized. Bar's static initializer again invokes Baz.testAsserts(). Because initialization of Baz is already in progress by the current thread, the second invocation executes immediately, though Baz is not initialized (§12.4.2).



Because of the rule above, if the program above is executed without enabling assertions.







既存のプログラムとの互換性



Javaでassertキーワード追加しても、既存のバイナリ(.classファイル)で問題は発生しませんassertを識別子として使用するアプリケーションをコンパイルしようとすると、コンパイラの警告またはエラーメッセージが表示されます。ステートメントが有効な識別子である世界からそうでない世界への移行を容易にするために、コンパイラはこのバージョンで2つの動作モードをサポートしています。



-source 1.4フラグを指定してソースモード1.4を特に要求しない限り、コンパイラはソースモード1.3で動作します。このフラグの使用を忘れると、ステートメントを使用するプログラムはコンパイルされません。コンパイラーにデフォルトの動作として古いセマンティクスを使用することを強制する決定(つまり、assertを識別子として使用できるようにする)は、最も好ましい互換モードのために行われました。ソースモード1.3は長い間機能する可能性があります。

翻訳者コメント
( Java 1.4), . つまり javac JDK 7 JDK 8 , -source 1.3






設計とFAQ



このセクションには、アサートの内部構造に関するよくある質問が含まれています。

一般的な問題

互換性の

構文とセマンティクス

クラスAssertionError

クレームの有効化と無効化





一般的な質問


特別なサポートなしでJavaプログラミング言語の上にアサーションをプログラムできるのに、なぜアサーティブ性を提供するのですか?

特別な実装は可能ですが、多くの場合、theyい(各ステートメントにifが必要)または非効率的です(ステートメントが無効になっていても条件を計算します)。さらに、それぞれの特別な実装は、独自の方法でステートメントの包含と非アクティブ化を実装します。これにより、特にデバッグモードでこれらの実装の有用性が低下します。これらの欠陥の結果として、クレームはJavaプログラマーの文化の一部ではありませんでした。プラットフォームにクレームのサポートを追加すると、この状況を修正できる可能性が高くなります。



なぜこのツールの導入は、ライブラリの形ではなく、言語の拡張によって行われるのですか?

言語の変更は重大なステップであり、軽視すべきではないことを認識しています。ライブラリを介したアプローチが検討されました。ただし、ステートメントがオフになっている場合は、ステートメントの実行コストを無視できることが重要であると考えられています。ライブラリの場合、これを実現するために、プログラマーは各ステートメントをifステートメントとしてハードコーディングする必要があります。多くのプログラマーはこれをしません。ifステートメントを省略し、パフォーマンスが低下するか、ステートメントを完全に無視します。また、ステートメントはJames Goslingの元のJava仕様に含まれていることにも注意してください。申し立ては、満足のいく開発と実装の時間がないため、Oak仕様から削除されました。

翻訳者コメント
Java Oak . :).




-- (design-by-contract) , , Eiffel?

この可能性を検討しましたが、プラットフォームライブラリの大幅な変更や、古いライブラリと新しいライブラリ間の後方互換性の大きな違反なしに、Javaに「移植」することはできません。さらに、そのような機会が、Javaの特徴である言語のシンプルさを維持できるかどうかもわかりませんでした。その結果、単純な条件付きステートメント(ブール型のアサーション機能)がかなり理解可能なソリューションであり、リスクがはるかに少ないという結論に達しました。条件文を言語に追加しても、将来の設計ごとの完全なサポートの追加が妨げられることはありません。



シンプルなステートメントにより、契約による設計の限られた形式を実際に使用できますこのアサートは、内部(非パブリック)事前条件、事後条件、およびクラス不変条件のチェックに適しています。外部(パブリック)事前条件チェックは、メソッド内のチェックによって実行することもできます。これにより、特に、IllegalArgumentExceptionやIllegalStateExceptionなどの文書化された例外が発生します。



ブールアサーションに加えて、アサーションが無効になっている場合にコードブロックの実行を無効にするためのアサーションのような構造を提供してみませんか?

このような設計を提供すると、プログラマーは複雑なインラインステートメントを作成するようになりますが、それらを別々のメソッドに入れる方が適切です。





互換性


新しいキーワードは、識別子として「アサート」を使用する既存のプログラムとの互換性の問題になりますか?

はい、ソースファイルレベルで(識別子として 'assert'を使用するクラスのバイナリは引き続き正常に動作します。)移行を容易にするために、開発者が移行期間中に識別子として 'assert'を使用し続けることができる戦略を実装しました。



このツールは、古いJREでは実行できないクラスファイルを生成しませんか?

はい、そうです。クラスファイルは、メソッドの新しいクラスの課題に含まれていますクラスローダおよびクラスなど)(desiredAssertionStatusをこれらのメソッドの呼び出しを含むクラスファイルが古いJRE(クラスClassLoaderにそのようなメソッドがない)によって実行される場合、プログラムは実行されず、NoSuchMethodErrorをスローします。これは、言語の新しい機能を使用するプログラムが古いバージョンと互換性がない場合に当てはまります。





構文とセマンティクス


Expression 2でプリミティブ型が許可されるのはなぜですか?

この式のタイプを制限する正当な理由はありません。任意の型の解決は、たとえば、一意の整数コードを各ステートメントに関連付けたい開発者に便利です。さらに、System.out.println(...)の引数として式を「感じる」ようにします。これは望ましいと思われます。





クラスAssertionError


2が欠落しているステートメントによってAssertionErrorが生成される場合、プログラムがステートメントの状態を詳細なテキストメッセージ(たとえば、「高さ<最大高さ」)で報告しないのはなぜですか?

すぐに使える有用性が向上する場合もありますが、これはこれらすべての文字列定数をクラスファイルに追加してランタイムイメージを表すコストを正当化するものではありません。AssertionErrorがこの例外をスローしたオブジェクトへのアクセスを提供しないのは



なぜですか?同様に、ステートメントからAssertionErrorコンストラクターに任意のオブジェクトを渡さないのはなぜですかエラーメッセージ(文字列)の代わりに?

これらのオブジェクトへのアクセスにより、プログラマーはステートメントの失敗後にシステムを復元しようとしますが、これはこの言語機能の目的と矛盾します。AssertionErrorに



コンテキストアクセスメソッド(getFile()getLine()getMethod()など)を提供しないのはなぜですか?

この機能はThrowableにすでに提供されています。したがって、アサーションエラーだけでなく、すべてのスロー可能オブジェクトに使用できます。Throwable.getStackTrace()にはこの機能を提供するメソッドがあります。

翻訳者コメント
( assert AssertionError ) , Throwable.getStackTrace() — 1.4.

++- Throwable AssertionError.



翻訳者コメント


 public class App { public static void main(String[] args) { try { f(); } catch (Throwable t) { StackTraceElement[] stack = t.getStackTrace(); StackTraceElement frame = stack[0]; System.out.println("FileName: " + frame.getFileName()); System.out.println("ClassName: " + frame.getClassName()); System.out.println("MethodName: " + frame.getMethodName()); System.out.println("LineNumber: " + frame.getLineNumber()); } } public static void f() { throw new RuntimeException(); } } >> FileName: App.java >> ClassName: App >> MethodName: f >> LineNumber: 16
      
      









AssertionError Error , RuntimeException ?

これは論点です。専門家グループがそれについて議論し、AssertionErrorの後にプログラマーがプログラムを復元しようとするのを防ぐために、Errorがより適切な祖先であるという結論に達しました。一般的に、アサートエラーの原因を特定することはかなり困難であり、不可能です。このような拒否は、プログラムが「未知の方向に移動している」こと(「既知の領域の外」)を意味し、実行を継続しようとすると致命的となる可能性があります。さらに、規則の規則では、ほとんどの実行時例外(例外ではなく、エラーではなく継承を示すメソッドが必要です@throwsjavadoc)。メソッドの仕様にアサートエラーを生成できる状況を含めることはほとんど意味がありません。このような情報は実装の詳細と見なすことができ、実装ごとに、またリリースごとに異なる場合があります。

翻訳者コメント
«How to Write Doc Comments for the Javadoc Tool» «Documenting Exceptions with @throws Tag» .







クレームの有効化と無効化


ステートメントをオンまたはオフにするチームが、従来のパッケージセマンティクスの代わりにパッケージツリーセマンティクスを使用するのはなぜですか?

プログラマーは実際にパッケージを階層構造として使用してコードを編成するため、階層制御は便利です。たとえば、package-as-treesのセマンティクスにより、1つのアクションのすべてのSwingでステートメントを有効または無効にできます。

翻訳者コメント
Java, ( ).

: .

つまり aaa.bbb.ccc «» aaa.bbb xxx.yyy.zzz . (public, protected, (package private), private) : . « ».



Swing

 javax.swing.* javax.swing.border.* javax.swing.colorchooser.* javax.swing.event.* javax.swing.filechooser.* javax.swing.plaf.* javax.swing.plaf.basic.* javax.swing.plaf.metal.* javax.swing.plaf.multi.* javax.swing.plaf.nimbus.* javax.swing.plaf.synth.* javax.swing.table.* javax.swing.text.* javax.swing.text.html.* javax.swing.text.html.parser.* javax.swing.text.rtf.* javax.swing.tree.* javax.swing.undo.* javax.swing.* javax.swing.*
      
      





-- javax.swing.* .





setClassAssertionStatus()が承認ステータスを設定するのに遅すぎる場合(つまり、クラスが既に初期化されている場合)、例外をスローする代わりにブール値返すのなぜですか?

フラグの設定が遅すぎる場合、警告メッセージ以外のアクションは必要ありません。例外は不必要に重いようです。setDefaultAssertionStatus()setAssertionStatus()の



2つを使用する代わりに、1つのメソッド名をオーバーロードしないのはなぜですか?

命名方法の明確さは、一般的な利益のためです。メソッドのオーバーロードは混乱を引き起こす傾向があります。desiredAssertionStatus



のセマンティクスをカスタマイズしないのはなぜですかクラスがすでに初期化されているときにステートメントの現在の状態を返すことで、より「プログラマーフレンドリー」にするために?

結果として生じる方法に何らかの利点があるかどうかはまだ明確ではありません。このメソッドは、アプリケーションプログラマが使用するためのものではなく、必要以上に遅くて難しくするのは実用的ではないようです。アプレットがステートメントをオン/オフにできないようにするRuntimePermission



がないのはなぜですか?

アプレットがClassLoaderメソッドを呼び出す理由はありませんがステートメントの包含/除外のステータスを変更し、そのような呼び出しを行うことは無害に思えます。最悪のシナリオでは、アプレットは、初期化が必要なクラスにステートメントを含めることにより、簡単なサービス拒否攻撃を引き起こす可能性があります。さらに、アプレットは、アプレットがアクセスできるクラスローダーによってロードされる必要があるクラスステートメントのステータスにのみ影響を与えることができます。また、ここでは、信頼できないコードのクラスローダー(getClassLoaderにアクセスできないようにするRuntimePermissionが既に存在ます。

翻訳者コメント
«» Java.

java.lang.SecurityManager ( — , — ) RuntimePermission , — .

 //   "" public class App { public static void main(String[] args) { while (true); } }
      
      





 //   -  public class App { public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.exit(123); } }).start(); while (true); } } >> Process finished with exit code 123
      
      





 //      System.exit(123); //         ... ""! public class App { public static void main(String[] args) { System.setSecurityManager(new SecurityManager() { public void checkExit(int status) { throw new SecurityException("You haven't permission to exit."); } }); new Thread(new Runnable() { public void run() {System.exit(123);} }).start(); while (true); } }
      
      







/ , , .

 public class App { public static void main(String[] args) { ClassLoader loader = App.class.getClassLoader(); loader.setDefaultAssertionStatus(true); loader.setPackageAssertionStatus("", true); } }
      
      





SecurityException

 grant { permission java.lang.RuntimePermission "getClassLoader"; };
      
      





java.policy





「周囲の」クラスの承認状態を照会する構成を提供しないのはなぜですか?

このような設計により、人々は複雑なステートメントコードを作成することができます。そして、これは悪い決断だと考えています。また、必要に応じて、現在のAPIの上に次の構成を使用します。

 boolean assertsEnabled = false; assert assertsEnabled = true; //   !!! / /  assertsEnabled     
      
      







クラスの初期化の前に実行されるアサートが、ステートメントが含まれているかのように動作するのなぜですか?

クラスのコンストラクタとメソッドが初期化される前に機能できることを知っているプログラマはほとんどいません。これが発生した場合、クラス不変式がまだ作成されていない可能性が高く、深刻で微妙なエラーにつながる可能性があります。この状態で実行されるステートメントはおそらく動作せず、問題についてプログラマーに警告します。したがって、プログラマがこのような非標準的な状況でコードが遭遇するすべてのステートメントを実行することは一般に便利です。



連絡先





Javaトレーニングをオンラインで行い( プログラミングコースはこちら) 、Java Coreコースの再設計一環としてトレーニング資料の一部を公開していますこの記事では、視聴者の講義のビデオ録画をyoutubeチャンネルで見ることができます。おそらく、 チャンネルのビデオがより体系化されています



スカイプ:GolovachCourses

メール:GolovachCourses@gmail.com



All Articles