拡張メソッドと演算子

拡張メソッドを使用すると、メソッドを既存のタイプに追加できるため、コードの編集や再コンパイルを行わずに追加機能が提供されます。

拡張メソッド

C# などの他のマネージ・コード言語と同様に、マネージ COBOL は拡張メソッドをサポートします。これにより、既存のタイプにメソッドを追加できるため、コードの編集や再コンパイルを行わずに追加機能を提供できます。

例えば、次の拡張メソッドは、文字列の単語数を数えるメソッドを追加することで、文字列クラスを拡張します。

       class-id MyCount static.
       method-id CountWords extension. *> 拡張メソッドは暗黙的に静的
       procedure division using str as string 
                          returning wordCount as binary-long.
            set wordCount to str::Split(' ')::Length
       end method.
       end class.

メソッドをオーバーライドできない場合は拡張します。拡張するメソッドが、アクセスできない最終クラスまたはコードにある場合があります。このような場合、拡張メソッドを個別に定義してコンパイルし、そのメソッドを自由に使用できます。

拡張メソッドの定義

次の構成を使用して、拡張メソッドを定義します。

       class-id MyExtension static.
       method-id MyExtensionMethod extension.
       procedure division using myName as ExtendedType 
                      returning myReturnName as returnType.
           set myReturnName to my code...
       end method.
       end class.

拡張メソッドを定義するには、次のようにします。

  • メソッド・ヘッダで EXTENSION キーワードを使用します。
  • メソッドは、ネストでも汎用でもない静的クラス内になければなりません。メソッドは暗黙的に静的になります。
  • メソッドの本文では、最初のパラメータは拡張するタイプになります。これは、常に値パラメータになります。
拡張メソッドの使用

拡張メソッドを呼び出すための特別な構文はありません。拡張メソッドは他のメソッドとほぼ同じように使用します。ただし、最初のパラメータを、拡張されるタイプとして指定する点を除きます。次の構文を使用します。

parameter-1::method-name(more parameters)

次に示す例では、次のようなコードで使用します。 aString::CountWords:

       class-id MyWords.
       method-id CheckWords.
       01 myWordCount binary-long.
       procedure division using value aString as string 
                          returning b as condition-value.
           set myWordCount to aString::CountWords
           if myWordCount < 10
               set b to true
           else
               set b to false
           end-if 
       end method.
       end class.

拡張メソッドは、別のクラスやインターフェースで定義されるインスタンス・メソッドであるかのように使用します。拡張メソッドは、オブジェクト・インスタンスで利用可能な追加メソッドとして表示されますが、どの場所にも実装されます。

拡張メソッドを呼び出す場合、そのメソッドは、呼び出し文で指定されるクラスには含まれません。メソッドは別のクラスのどこかに含まれます。呼び出しクラスと実行クラスは異なります。メソッドの定義される包含タイプと実行する包含タイプは異なります。

拡張メソッドは次のように機能します。

  • コンパイラは、パラメータを含む静的メソッドを生成します。最初のパラメータのタイプは、実行される (呼び出される) タイプです。2 番目と以降のパラメータは、宣言されたパラメータです。例えば、Compare メソッドには事実上 2 つのパラメータがあります。最初のパラメータのタイプは string、2 番目のパラメータは s2 です。
  • 最初のパラメータには、System.run-time.CompilerServices.ExtensionAttribute 属性 (あるいは、JVM プログラムの場合、JVM ランタイム・システムで定義される相当の属性) の注釈が付けられます。この属性は、包含クラスとアセンブリにも適用されます。

拡張演算子

拡張演算子は、拡張メソッドとかなり似ています。拡張演算子を使用すると、コードの編集や再コンパイルを行わずに、演算子の代替動作を提供できます。拡張演算子は、拡張メソッドと同様に定義して使用します。

例えば、拡張と演算子を次のように定義して使用できます。

       set timer3 to timer1 + myMins 
       operator-id + extension. 
       01 sumMins binary-long.
       01 m binary-long value 0.
       01 h binary-long value 0.
       procedure division using value a as type Timer 
                                      b as binary-long 
                            returning c as type Timer.
           set sumMins to a::pMins + b
           divide sumMins by 60 giving h remainder m
           set c to new Timer(a::pHour + h , m)
       end operator.

演算子をオーバーロードできない場合は拡張します。拡張する演算子が、アクセスできない最終クラスまたはコードにある場合があります。このような場合、拡張演算子を個別に定義してコンパイルし、その演算子を自由に使用できます。

拡張演算子を宣言して使用するには、次のようにします。

  • EXTENSION キーワードを使用して演算子を宣言します。演算子は静的クラスになければなりません。
  • 拡張演算子を呼び出すための特別な構文はありません。
  • JVM マネージ COBOL では、拡張演算子はコンパイラで使用できなければなりません。ILREF 指令と JVMGEN 指令を組み合わせて使用する必要があります。詳細は、「拡張メソッド」を参照してください。

拡張演算子は、op_Equality や op_Addition などのメソッドとして実装されます。このメソッドは、TypeInfo の固有辞書に追加されます。このため、演算子のオーバーロードと拡張を検索するコードは、2 つのオペランドのクラスの他にこの辞書も調べることができます。

JVM COBOL での拡張メソッド (および演算子)

メソッドを呼び出す場合、そのメソッドが属するクラスを指定します。次の例では、myWord クラスのメソッド CheckWords() は次のように呼び出されます。

       01 myWord type MyWords.
       01 myDoc string.
       01 sizeOK condition-value.
           set sizeOK to myWord::CheckWords(myDoc)
           ...
       class-id MyWords final.
       method-id CheckWords.
       ...

上記の例では、クラスが見つかるため、メソッドも見つかります。クラス・ローダのアーキテクチャは、メソッドが定義されるクラスを使用してクラスのロード時期を把握します。この場合、myWord クラスは、メソッド CheckWords() を呼び出す前にロードされます。これは、コンパイル時にほぼ同様に行われます。myWord::CheckWords メソッドが、コンパイルされるクラスで参照される場合、コンパイラは CheckWords メソッドをロードするクラス・ローダを使用して、メソッドの署名を解決し、適切なコードの生成方法を把握します。

問題:拡張が見つからない

ただし、拡張メソッド (または演算子) を呼び出す場合、拡張メソッドは、呼び出し文で指定されるクラスには含まれません。次の例では、拡張メソッド CountWords() は、aString 変数 (文字列) のタイプで呼び出されますが、次のように拡張クラスで実行されます。

       01 aString string.
       01 myWordCount binary-long.
           set myWordCount to aString::CountWords
           ...
       class-id MyCount static.
       method-id CountWords extension.
       ...

メソッドは String クラスで呼び出されますが、実行されるメソッドは、別のクラス (MyCount クラス) に含まれます。クラス・ローダはメソッドを見つけることができません。その定義される包含タイプと実行する包含タイプが異なるためです。コンパイラが拡張メソッド (および拡張演算子) を使用するには、その拡張を参照するクラスをコンパイルする前に定義クラスをロードする必要があります。

解決方法:拡張をあらかじめロードしておきます。

コンパイラが拡張を見つけられるようにするには、コマンド・ラインで ILREF 指令を JVMGEN 指令と組み合わせて使用します。次の例では、ops.class ファイル内に定義されるクラスをロードします。ops.class で定義されるクラスの名前とパッケージは、クラス・ファイルの名前や辞書と同じである必要はありません。

Windows:

cobol x.cbl jvmgen ilref(ops.class);

UNIX:

cob x.cbl –C jvmgen  -C ilref(ops.class)

次のコマンドは、ファイル ops.jar を解凍して moreOps.class をロードします。.jar ファイルが機能するには、CLASSPATH 環境変数上にあるか、JVMCLASSPATH 指令で指定される必要があります。

Windows:

cobol -j x.cbl jvmclasspath(ops.jar) ilref(moreOps.class);

UNIX:

cob -j x.cbl -C jvmclasspath(ops.jar) –C ilref(moreOps.class)

ILREF 指令には、クラス・フォーマットのファイルが必要です。.class 拡張のあるファイルはクラス・フォーマットで扱われます。

JVM にあらかじめロードされる拡張

JVM の場合、次の拡張がサポートされます。

  • 文字列同一の拡張演算子
  • 従属文字列の拡張メソッド

拡張を含むクラスは、ILREF 指令を使用することにより、コンパイラであらかじめロードされます。

文字列同一の拡張演算子

文字列同一の拡張演算子は、コンパイルの前にコンパイラであらかじめロードされます。これにより、同一であるかどうかのチェックが行われ、.NET 環境の結果と JVM 環境の結果が同じになります。例えば、次のコードは 2 つの文字列を比較します。

       01 string1 string value “12345”.
       01 string2 string value “12345”.
       if string1 = string2
       	   display “the values are the same” 
       else
           display “error - the values look the same, but the equality returned false”
       end-if

あらかじめロードされた拡張を使用しない場合、コードは文字列を異なるものと判断します。これは、JVM java.lang.String::equality(string) メソッドが参照を比較し、.NET System.String::equality(string) メソッドが値を比較するためです。あらかじめロードされた拡張を使用する場合、コードは文字列を同じものと判断します。

文字列同一の拡張は、文字列タイプの場合、= と等号演算子の両方を拡張します。演算子は、2 つの文字列インスタンスに同じ文字シーケンスが正確に含まれる場合は true を戻し、それ以外は false を戻します。演算子の拡張は次のように定義されます。

       operator-id = extension.
       procedure division using value a as string 
                                      b as string
                          returning r as condition-value.

文字列インスタンス間でオブジェクトが同一であるかどうかを確認するには、次の構文を使用します。

       if a as object = b as object  
JVM COBOL の従属文字列の拡張メソッド

従属文字列の拡張メソッドは、コンパイルの前にコンパイラであらかじめロードされます。これにより、従属文字列メソッドでは、.NET 環境の結果と JVM 環境の結果が同じになります。次に例を示します。

       01 x string value “1234567890”.
       display x::Substring(2,3) “ The .NET way of doing things”
       display x::substring(2,3) “ The JDK way of doing things”

System.String::Substring(int, int) の .NET 動作は java.lang.String::Substring(int int) の JVM 動作と異なるため、拡張がインストールされない場合、.NET と JVM の結果は異なります。ただし、拡張がインストールされる場合、JVM 動作は .NET の動作と一致し、同じ結果になります。

従属文字列の拡張メソッドは、文字列タイプ (java.lang.String でもある) を拡張します。このメソッドは、.NET の従属文字列のメソッドの署名と一致する署名の文字列の新しいインスタンスを戻します。従属文字列のメソッドの拡張は次のように定義されます。

       method-id Substring extension.
       procedure division using value a as string 
                                startIdx as binary-long
                                sslength as binary-long
                      returning r as string.

JVM の文字列同一の拡張演算子

文字列同一の拡張演算子は、コンパイルの前にコンパイラであらかじめロードされます。これにより、同一であるかどうかのチェックが行われ、.NET 環境の結果と JVM 環境の結果が同じになります。例えば、次のコードは 2 つの文字列を比較します。

       01 string1 string value “12345”.
       01 string2 string value “12345”.
       if string1 = string2
       	   display “the values are the same” 
       else
           display “error - the values look the same, but the equality returned false”
       end-if

あらかじめロードされた拡張を使用しない場合、コードは文字列を異なるものと判断します。これは、JVM java.lang.String::equality(string) メソッドが参照を比較し、.NET System.String::equality(string) メソッドが値を比較するためです。あらかじめロードされた拡張を使用する場合、コードは文字列を同じものと判断します。

文字列同一の拡張は、文字列タイプの場合、= と等号演算子の両方を拡張します。演算子は、2 つの文字列インスタンスに同じ文字シーケンスが正確に含まれる場合は true を戻し、それ以外は false を戻します。演算子の拡張は次のように定義されます。

       operator-id = extension.
       procedure division using value a as string 
                                      b as string
                          returning r as condition-value.

文字列インスタンス間でオブジェクトが同一であるかどうかを確認するには、次の構文を使用します。

       if a as object = b as object  

JVM COBOL の従属文字列の拡張メソッド

従属文字列の拡張メソッドは、コンパイルの前にコンパイラであらかじめロードされます。これにより、従属文字列メソッドでは、.NET 環境の結果と JVM 環境の結果が同じになります。次に例を示します。

       01 x string value “1234567890”.
       display x::Substring(2,3) “ The .NET way of doing things”
       display x::substring(2,3) “ The JDK way of doing things”

System.String::Substring(int, int) の .NET 動作は java.lang.String::Substring(int int) の JVM 動作と異なるため、拡張がインストールされない場合、.NET と JVM の結果は異なります。ただし、拡張がインストールされる場合、JVM 動作は .NET の動作と一致し、同じ結果になります。

従属文字列の拡張メソッドは、文字列タイプ (java.lang.String でもある) を拡張します。このメソッドは、.NET の従属文字列のメソッドの署名と一致する署名の文字列の新しいインスタンスを戻します。従属文字列のメソッドの拡張は次のように定義されます。

       method-id Substring extension.
       procedure division using value a as string 
                                startIdx as binary-long
                                sslength as binary-long
                      returning r as string.