メソッド (計算機科学)

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索

メソッド (method) あるいは メンバー関数 (-かんすう, member function) とはオブジェクト指向プログラミング言語において、あるクラスないしオブジェクトに所属するサブルーチンを指す。

概要[編集]

オブジェクト指向プログラミングにおけるメソッドという用語は元々SmalltalkによってSimulaメンバープロシージャーメッセージメソッドに別けるために導入された。C++ ではメンバー関数と呼ばれるが、これはSimulaのメンバープロシージャーをCに流用したことに由来している。Javaの様な言語やMicrosoftなどの企業がメソッドという用語を使っているのは元々C++よりもSmalltalkの影響を受けていたためである。近年では言語設計やOS開発等で直接Smalltalkの影響を受けていない場合でも、JavaやMicrosoft等の影響によりメンバープロシージャーやメンバー関数に当たるものをメソッドと呼ぶことが一般化している。

Smalltalkの特色を色濃く受け継いだObjective-Cでは、メッセージとメソッドを明確に使い分けている。

メソッドと通常の関数の違いは主にインスタンス内部へのアクセスの有無である。この機能は通常カプセル化と呼ばれており、クラス定義の抽象化に貢献するとされる。また呼び出し時に操作の対象となるインスタンス(レシーバ) を selfthis といった予約語、あるいはメソッドに渡された引数によって参照することができる。 C++ ではクラスに属さない関数であるグローバル関数と対比されることがある。

またメンバ関数はサブクラス化の際にオーバーライドされる可能性があり (多態性)、実際に発生する動作がレシーバに依存するという特徴を持つ。

UML ではメソッドのことを操作と呼ぶ。

インスタンスメソッドとクラスメソッド[編集]

インスタンスメソッド (instance method) とはインスタンスに属するメソッドのことであり、インスタンスに対しメッセージを送信する事で実行される。インスタンス変数の操作に使われ、インスタンスメソッドはオブジェクト指向プログラミングの中核をなし、もっともよく使われる。 一方、クラスメソッド (class method)とはクラスに属するメソッドのことであり、クラスに対しメッセージを送信する事で実行される。クラス変数の操作やオブジェクトの生成などに使われる。多くのオブジェクト指向言語はメタクラスをサポートしており、クラスオブジェクトの操作手段となる。 C++Javaといったメタクラスの存在しないオブジェクト指向言語では静的メンバー関数あるいは静的メソッド (static method)とよばれ、その振る舞いはクラス変数の操作が許可される点を除き非オブジェクト指向言語における関数と変わらない。

インスタンスメソッドとクラスメソッドの例[編集]

Rubyによる例[編集]

Rubyによるインスタンスメソッドとクラスメソッドの例を示す。

なおRubyでは、下記の用な例は属性を使って記述すべきだが、今回は説明のため通常のメソッドを使用して記述する。

class MethodSample
 # コンストラクター
  def initialize( name )
    # @で始まる変数はインスタンス変数
    @name = name
  end
 
 # インスタンスメソッド
  def getName
    return @name
  end
 
  # クラスメソッド
  def self.setNumber( number )
    # @@で始まる変数はクラス変数
    @@number = number
  end
 
  # クラスメソッド
  def self.getNumber
    return @@number
  end
end

インスタンスメソッドを呼び出すには、まずインスタンスを生成しなければならない:

objectA = MethodSample.new( "John" )
objectB = MethodSample.new( "Joe" )

上の例では new により二つのインスタンスを生成し変数 objectAobjectB にそれぞれ代入している。この時点で、objectAobjectB のインスタンスフィールド @name にはそれぞれ "John" と "Joe" が代入されている。

インスタンスメソッドを呼び出すには次のように記述する:

objectA.getName() #"John" を返す
objectB.getName() #"Joe" を返す

上の例では objectAobjectB それぞれのインスタンスに対し getName メソッドを呼び出した。それぞれの呼び出しの返り値が異なることから、インスタンスフィールドは、そのクラスに属する各オブジェクト毎に異なる値を持つことがわかる。

一方、クラスメソッドはクラスに直接属しているため、代わりにクラスを指定してメソッドを呼び出す。 クラスメソッドを呼び出すには次のように記述する:

type = MethodSample
type.setNumber( 100 )
type.getNumber() #100 を返す

上の例では typeに対しクラスMethodSampleを代入し、クラスメソッドである setNumbergetNumber を呼び出した。

このとき、 MethodSample から複数のインスタンスを生成しても、クラスフィールド @@numberMethodSample クラスに属する全てのインスタンスで共有される。

また、クラスメソッドは、変数に代入せず直接クラス名を指定して呼び出すことが多い。特にメタクラスをサポートしない言語においては、直接クラス名を指定する書き方しかできない。

直接クラス名を指定したクラスメソッドの呼び出しは次のように記述する:

MethodSample.setNumber( 100 )
MethodSample.getNumber() #100 を返す

なお、 MethodSample のインスタンス生成時に使用した new もクラスメソッドである。

一般的にインスタンスを生成する場合には new という特別な演算子を用いる言語が多い。しかし、Smalltalkの影響が強い言語やRuby等いくつかの言語ではクラスメソッドによりインスタンスを生成する。クラスメソッドによりインスタンスを生成する言語の場合、newを独自の実装に変更する事が出来る。例えばnewが呼ばれた際、別のクラスのインスタンスを返すように出来る。C++の様にnewが演算子でありながら、クラスメソッド(静的メンバー関数)としてnewを定義できる言語もある。

メタクラスをサポートした言語の場合、インスタンスもクラスも同じオブジェクトとして扱われるため、メソッド呼び出しの際はインスタンスメソッドとクラスメソッドの区別はない。インスタンスメソッドの代わりにクラスメソッドを呼び出すことも、クラスメソッドの代わりにインスタンスメソッドを呼び出すことも出来る。どちらのメソッドを呼び出すかは、メソッドを呼び出した変数にインスタンスとクラスのどちらのオブジェクトを代入していたかで決まる。

多くの言語ではインスタンスメソッドとクラスメソッドは同じシグニチャー( 名前と引数 )を定義できる。SmalltalkやObjective-Cなどメッセージが存在する言語ではメソッドの多重定義ができないため一見無理なように見えるが、メソッドが所属するオブジェクトがインスタンスとクラスで異なるため、同一のシグニチャーでインスタンスメソッドとクラスメソッドを定義することが出来る。

Javaによる例[編集]

以下にRuby と同様に Java で記述したインスタンスメソッドと静的メソッドの例を示す。Java では static 修飾子がついたメソッドが静的メソッドであり、ついていなければインスタンスメソッドである。

public class MethodSample {
  /** インスタンスフィールド */
  private String name;
 
  /** クラスフィールド */
  private static int number;
 
  /** インスタンスを生成するためのコンストラクタ */
  public MethodSample(final String name) {
    this.name = name;
  }
 
  /** インスタンスメソッド、getter */
  public String getName() {
    return this.name;
  }
 
  /** 静的メソッド、getter */
  public static int getNumber() {
    return MethodSample.number;
  }
 
  /** 静的メソッド、setter */
  public static void setNumber(final int number) {
    MethodSample.number = number;
  }
}

Ruby同様インスタンスメソッドを呼び出すには、まずコンストラクタを呼び出してインスタンスを生成しなければならない:

MethodSample objectA = new MethodSample("John");
MethodSample objectB = new MethodSample("Joe");

上記の例は、Rubyの例におけるインスタンス生成と同様に動作する。C++の派生であるJavaは、C++の表記を踏襲しておりnew演算子によってインスタンスを生成する。

インスタンスメソッドを呼び出すには次のように記述する:

objectA.getName() // "John" を返す
objectB.getName() // "Joe" を返す

上記の例は、Rubyの例におけるインスタンスメソッドの呼び出しと同様に動作する。

クラスメソッドを呼び出すには次のように記述する:

MethodSample.setNumber(100);
MethodSample.getNumber(); // 100 を返す

上記の例は、Rubyの例における直接クラス名を指定したクラスメソッドの呼び出しと同様に動作する。

JavaをはじめとするメタクラスをサポートしないC++系統の言語は、クラスを変数に代入できないため、変数からクラスメソッドを呼び出すような事が出来ない。

メッセージ送信とメソッド呼び出し[編集]

オブジェクト指向を解説した書籍などでメソッド呼び出しについてオブジェクトにメッセージを送信すると表現されることがある。C++系統の言語ではオブジェクトの操作は単なるメンバー関数( メソッド )呼び出しに過ぎず比喩として捉えられる場合が多い。SmalltalkやObjective-Cにおいては、メッセージ送信は単なる比喩ではなく実体のある機構であり、メソッド呼び出しとは別物であるため注意が必要である。

(詳しくはメッセージ転送を参照。)

仮想メソッド、抽象メソッドと具象メソッド[編集]

仮想メソッド[編集]

仮想メソッド (virtual method) (C++では仮想関数)とは、サブクラスオーバーライドし、動作を変更することのできるメソッドのことである。

C++およびC#ではメソッドにvirtual修飾子をつけることで仮想メソッドとすることができる。一方Javaではメソッドはデフォルトで仮想メソッドとなり、final修飾子をつけることで非仮想メソッドとなる。

final修飾子、非virtual[編集]

C++では、一般に仮想関数はコンパイル時にどのメンバー関数を呼び出すかを確定できないため、通常のメンバー関数呼び出しよりもパフォーマンスが悪いというデメリットがある。そのため、パフォーマンスを気にするC++プログラマには、継承する必要がないクラスのメンバー関数にvirtual修飾子をつけることを非常に嫌がる向きがある。また、C++にはtemplateという機能が存在し、多くの場合仮想関数はtemplateで代用できてしまうため仮想関数にこだわる必要がないという事情もある。

Javaにおいても同様に「Javaはデフォルトで仮想的だからfinalをつけないとパフォーマンスが衰える。」と言われるようになった。一方で、Java仮想マシンの性能によってはfinalを宣言したからといって優れたパフォーマンスが得られるとは限らないという向きもある[1]

抽象メソッドと具象メソッド[編集]

抽象メソッド (abstract method) 、あるいはC++では純粋仮想関数 (pure virtual function) とは仮想関数の一種で、メソッドの実装が無く、宣言だけされているもののことである。このメソッドを利用するには、このメソッドを含むクラス継承し、そこでこのメソッドをオーバーライドして実装する必要がある。従って、抽象メソッドを含むクラスは継承しない限りインスタンス化できない。このようなクラスを抽象クラスと呼ぶ。

具象メソッド (concrete method) は抽象メソッドの逆で、実装をもつメソッドのことである。主に抽象メソッドをオーバーライドしたインスタンスメソッドのことを意味するために使われる。

Javaではabstract修飾子を用いて抽象メソッドを宣言できる。抽象メソッドを持つクラスもまた、必ずabstract修飾子を使わなければならない。

抽象メソッドはデザインパターンの一つTemplate Method パターンで主要な役割を果たす概念であり、ソフトウェアの拡張性再利用性汎用性を高めるのに役立つ。

オーバーライド[編集]

C#では、オーバーライドしたメソッドにはoverride修飾子をつけることで、そのメソッドがオーバーライドされていることをコンパイラに知らせることができる。 同様に、Javaでも、Java SE 5から導入されたアノテーション@Overrideを用いることでメソッドがオーバーライドされていることをコンパイラに知らせることができる。

あるスーパークラスとそれを継承したサブクラスが別々の開発者によって実装されている場合、Javaではオーバーライドに関係した問題が起こりうるので注意が必要である。スーパークラスの実装者があとからメソッドを追加したときに、そのメソッドと同じ名前とシグネチャのメソッドが既にサブクラスに存在すると、オーバーライドしたつもりがないのに関係のないメソッドをオーバーライドしてしまうという問題が起こる。これを回避するためにJavaでは@Overrideの指定が推奨されるが、後方互換性を保つために必須とはなっていない。C#ではoverride修飾子が必須なのでこの問題は起こらない。

アクセサ[編集]

アクセサ (accessor) とは、メソッドの中で特にオブジェクトからフィールドの値など単一の値を出し入れするメソッドのこと。

アクセサの表記[編集]

書き方は言語系により異なる。

Java系統では、getXXX, setXXXの様に語幹部にアクセス対象の名詞が入る。Smalltalk系統ではXXX, XXX:というようにアクセス対象の名前だけを記述し、入力と出力を引数の有無で区別する。Objective-Cでは、XXXsetXXX:と片方にだけsetをつける命名規則が用いられる。DelphiやC#等プロパティーをもつ多くの言語では、プロパティー自体がアクセサにあたる。Rubyでは、メソッド呼び出しの際の引数を囲むカッコが省略できるため、引数を持たないメソッド呼び出しが読み出し用アクセサに相当するが、書き込み用アクセサにはXXX=という構文が存在する。C++には決まった規則はなくライブラリーや開発環境に左右される。

アクセサとRADツール[編集]

アクセサが他のメソッドと区別される大きな理由としてRADツールの連携を想定していることにある。多くのRADツールでは、アクセサとして定義されたメソッドを特別扱いし、GUIのデザイン時にデザイン画面からアクセサの引数を指定できるようになっている。

Javaには元々、get/setの接頭辞を記述するという規則はなかったが、RAD支援ライブラリーのjava.beansがset/getから始まるメソッドを値設定用のメソッドとして特別に扱うため、java.beansが登場して以降、Javaのクラスライブラリー全体に渡ってset/getの接頭辞を使用する命名規則が導入された。現在でも初期の名残としてset/getを接頭辞として持たないアクセサメソッドが存在する。

アクセサと公開フィールド[編集]

多くの言語では、フィールドを公開(public)状態にすることで、オブジェクトのフィールドをメソッドを介さず直接読み書き出来る。 しかし、実際にはアクセサを介してフィールドを読み書きすることが殆どであり、フィールドを公開状態にして直接読み書きすることは避けられる。

フィールドとアクセサが1対1の状態であれば、公開状態のフィールドの読み書きとアクセサを介したフィールドの読み書きは殆ど変わらない。それにも関わらず、わざわざアクセサを介してフィールドを読み書きする理由は、アクセサの実装はクラス実装者の自由であり、アクセサが受け取った値をどう処理するかはクラスの実装に委ねられるからである。例えば、アクセサが値を返す際、フィールドを参照せず常に0を返すという実装も有りうる。


また、アクセサを使う最も重要な理由として多態性の恩恵を受けられるという点がある。

例えば、現在の時間を管理するCurrentTimeクラスと、単に時間を保有しているだけのTimeクラスを考える。どちらのクラスも秒を返すgetSecond()というメソッドを持っている。CurrentTimeクラスのgetSecond()現在の秒を返し、TimeクラスのgetSecond()は、同クラスのsetSecond( integer )により代入された秒を返す。また、この他にクラスに所属しないformat( time )メソッド( 関数 )が定義されているとする。format( time )は、引数timeに対しgetSecond()を呼び出し、整形した時刻を文字列として返す。この時、現在の時刻を文字列として得たい場合は、format( time )の引数にCurrentTimeクラスが生成したオブジェクトを与えれば良い。また、指定した時刻を文字列で得たい場合には、format( time )の引数にTimeクラスが生成したオブジェクトを与えれば良い。

この様に公開フィールドではなくアクセサを使用した場合は、オブジェクトの公開フィールドにどんな値を設定して引数に渡したかではなく、どんな種類のオブジェクトを引数に渡したかでメソッド( この例ではformat )の動作を変えることが出来るようになる。 純粋なオブジェクト指向環境として知られるSmalltalkでは、このようなアクセサによる多態性がMVCの依存性辞書の管理を始めあらゆる箇所で活用されている。


その他公開フィールドへの直接アクセスと比較した際のアクセサの利点としては以下のようなものがある。

  • 意図的に setter メソッドを実装しないことで、フィールドの値を勝手に変更させないようにイミュータブル(不変)なクラスを作ることが可能になる。
  • getter/setter でフィールドへアクセスするときにデータを加工してから取得したり挿入することができる。
  • setter でフィールドにデータをセットするときにセットしたいデータを例えば整数のみ、あるいは自然数のみ、偶数のみ、日付のみ、などに限定することができる。

アクセサ論争[編集]

SmalltalkObjective-Cのようなメッセージ送信メタファーの言語では、インスタンス変数は必ずアクセサを用いて利用するというのが常識である。一方 C++ などでは public 変数への直接フィールドアクセスが往々にして利用されることがある。

  • 効率の問題。メソッドをいちいち呼び出すコストを避けたい場合。ただしほとんどはインライン化で解決できる。
  • 記述量の問題。単純に値をセット、取得するだけのことに全てメソッドを記述するのは間違っているという考え方。

問題となるのは後者である。

まず多態性の観点から、アクセサを用いない変数参照は将来にわたっての変更耐久性が著しく劣る。また記法の一貫性からアクセサを指示する向きもある。一方否定派は「強力な IDE を用いればリファクタリングは可能であり、むしろフィールドへの直接アクセスを用いる方が意味が明確となる」という主張を展開し、時にフレームに発展する場合がある。これは現代の「goto文論争」ともいうべき、半ば宗教的な対立関係である。

C# はこの反省から、アクセサ定義を簡素化する要素としてプロパティを導入した。また Ruby など、アクセサを簡単に定義できるメソッドや構文を備えている言語もある。

オーバーロード[編集]

多くのオブジェクト指向言語ではメソッドやコンストラクタオーバーロード (overload) できる機能を持つ。これは、オーバーライドと名前が似ているため間違えられることがある。引数の数、型、順序などが異なる同じ名前のメソッドを定義することをメソッドのオーバーロードという。

ただし、PHPPerlのように、プログラミング言語によっては、型の曖昧さが原因によりメソッドをオーバーロードできないものもある。この場合はメソッドの引数をメソッド先頭で読み取り、引数の型を判定する条件分岐で対応する。

参考文献・脚注[編集]

[ヘルプ]
  1. ^ Javaの理論と実践: ファイナル・アンサー? finalキーワードを有効に使用するためのガイドライン

関連項目[編集]