拡張メソッドと演算子

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

拡張メソッド

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

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

       class-id MyCount static.
       method-id CountWords extension. *> extension method is implicitly static
       procedure division using by value 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 by 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.