メソッド (計算機科学)

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

概要

オブジェクト指向プログラミングにおけるメソッドという用語は元々SmalltalkによってSimulaのメンバープロシージャー (member procedure) をメッセージとメソッドに分けるために導入された。C++ ではメンバー関数と呼ばれるが、これはSimulaのメンバープロシージャーをC言語に流用したことに由来している。Javaのような言語やマイクロソフトなどの企業がメソッドという用語を使っているのは、元々C++よりもSmalltalkの影響を受けていたためである。近年では言語設計やOS開発等で直接Smalltalkの影響を受けていない場合でも、Javaやマイクロソフト等の影響によりメンバープロシージャーやメンバー関数に当たるものをメソッドと呼ぶことが一般化している。
Smalltalkの特色を色濃く受け継いだObjective-Cでは、メッセージとメソッドを明確に使い分けている。
メッセージとメソッドが分かれている言語では1個のメソッドに対しセレクターが異なる複数のメッセージを送ることができる。このためメンバー関数型の言語と違いメッセージとメソッドの分離を強く意識しておく必要がある。
メソッドと通常の関数の違いは主にインスタンス内部へのアクセスの有無である。クラスに所属するフィールドおよびメソッドにはそれぞれアクセシビリティ（アクセスレベル）を設定することができ、アクセス権がないコード領域からは参照したり呼び出したりすることができない。この機能は通常カプセル化と呼ばれており、クラス定義の抽象化に貢献する。また呼び出し時に操作の対象となるインスタンス (レシーバ) を this や self といったキーワード（予約語）、あるいはメソッドに渡された引数によって参照することができる。
C++ではクラスに属さない関数である大域関数（グローバル関数あるいはフリー関数）と対比されることがある。
またメソッドはサブクラス化の際にオーバーライドされることがあり、実際に発生する動作（振る舞い）がレシーバに依存するという特徴を持つ。これを多態性（ポリモーフィズム）と呼ぶ。
統一モデリング言語 (UML) ではメソッドのことを操作 (operation) と呼ぶ。

インスタンスメソッドとクラスメソッド

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

Smalltalkによる例

Smalltalkによるインスタンスメソッドとクラスメソッドの例を示す。
Object
subclass:#MethodSample
instanceVariableNames:'name'"インスタンスオブジェクトに持たせるインスタンス変数"
classVariableNames:''"クラスオブジェクトとクラスオブジェクト直属のインスタンスオブジェクトの間で共有するクラス変数"
poolDictionaries:''"クラスオブジェクトとインスタンスオブジェクトの間で共有するプール変数"
category:'Example'.
 

"インスタンスオブジェクトのインスタンス変数を操作するメソッド(インスタンスメソッド)"MethodSample methodsFor: 'accessing'!givenName

^ name.
!
givenName: aString

name := aString.
!!

"インスタンスオブジェクトとクラスオブジェクトを操作するメソッド(インスタンスメソッド)"MethodSample methodsFor: 'inter-accessing'!name

^ self givenName, ' ', self class familyName. 
!!

MethodSample class instanceVariableNames: 'name'."クラスオブジェクトに持たせるインスタンス変数""クラスオブジェクトのインスタンス変数を操作するメソッド(クラスメソッド)"MethodSample class methodsFor: 'accessing'!familyName
^ name.
!
familyName: aString

name := aString.
!!

MethodSample class methodsFor: 'instance creation'!withGivenName: aString

^ self
new
givenName: aString;
yourself.
!!
インスタンスメソッドを実行するには、まずインスタンスオブジェクトを生成しなければならない:
| objectA objectB |objectA := MethodSample withGivenName: 'John'.objectB := MethodSample withGivenName: 'Joe'.
上の例では #withGivenName: により二つのインスタンスオブジェクトを生成し変数 objectA と objectB に代入している。この時点で、objectA とobjectB のインスタンス変数 name にはそれぞれ "John" と "Joe" が代入されている。
インスタンスメソッドを実行するには次のように記述する:
objectA givenName."'John' を返す"objectB givenName."'Joe' を返す"
上の例では objectA と objectB それぞれのインスタンスオブジェクトに対し #givenName メッセージを送りインスタンスメソッドを実行している。それぞれのメソッドの返り値が異なることから、同じクラスオブジェクトに属するインスタンスオブジェクトでもインスタンス変数が持つ値は、インスタンスオブジェクト毎に異なることがわかる。
一方、クラスメソッドを実行するには、クラスオブジェクトに直接属しているため、インスタンスオブジェクトの代わりにクラスオブジェクトに対してメッセージを送る。
クラスメソッドを実行するには次のように記述する:
type := MethodSampletype familyName: 'Hillton'.type familyName."'Hillton' を返す".
上の例では typeにクラスオブジェクトMethodSampleを代入して、#familyNameと#familyName:メッセージを送りクラスメソッドを実行している。
クラスオブジェクトのインスタンス変数 name は、インスタンスオブジェクトのインスタンス変数と異なり MethodSample に属する全てのインスタンスオブジェクトで共有される。
クラスオブジェクトのインスタンス変数が共有される例を示す:
| type objectA objectB |type := MethodSampleobjectA := type withGivenName: 'John'.objectB := type withGivenName: 'Joe'."#nameはクラスオブジェクトに#familyNameを送っているため、異なるインスタンスオブジェクトでも'Hillton'が共有されている。"type familyName: 'Hillton'.objectA name."'John Hillton'を返す"objectB name."'Joe Hillton'を返す"
クラスメソッドは、変数に代入せず直接クラス名を指定して呼び出すことが多い。特にクラスがオブジェクトではない言語においては、直接クラス名を指定する書き方しかできない。
直接クラス名を指定したクラスメソッドの呼び出しは次のように記述する:
MethodSample familyName: 'Hillton'.MethodSample familyName."'Hillton' を返す".
なお、MethodSample のインスタンスオブジェクト生成するときに使用した new もクラスメソッドである。
一般的にインスタンスオブジェクトを生成する場合には new という特別な演算子を用いる言語が多い。しかし、Smalltalkの影響が強い言語やRuby等いくつかの言語ではクラスメソッドによりインスタンスオブジェクトを生成する。クラスメソッドによりインスタンスオブジェクトを生成する言語の場合、newを独自の実装に変更することができる。例えばnewを実行したとき、別のクラスオブジェクトに属するインスタンスオブジェクトを返すようにすることができる。C++のようにnewが演算子でありながら、クラスメソッド (静的メンバー関数) としてnewを定義できる言語もある。
クラスがオブジェクトになっている言語の場合、インスタンスもクラスも同じオブジェクトとして扱われるため、インスタンスメソッドとクラスメソッドでメッセージの送り方に区別はない。インスタンスメソッドの代わりにクラスメソッドを呼び出すことも、クラスメソッドの代わりにインスタンスメソッドを呼び出すこともできる。どちらのメソッドを呼び出すかは、メッセージを送った変数にインスタンスとクラスのうち、どちらのオブジェクトを代入していたかで決まる。
多くの言語ではインスタンスメソッドとクラスメソッドは同じシグネチャ (signature: 名前と引数) を定義できる。SmalltalkやObjective-Cなどメッセージが存在する言語ではメソッドの多重定義ができないため一見無理なように見えるが、メソッドが所属するオブジェクトがインスタンスとクラスで異なるため、同一のシグネチャでインスタンスメソッドとクラスメソッドを定義することができる。

Javaによる例

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;
  }}
Smalltalk同様インスタンスメソッドを呼び出すには、まずコンストラクタを呼び出してインスタンスを生成しなければならない:
MethodSample objectA = new MethodSample("John");MethodSample objectB = new MethodSample("Joe");
上記の例は、Smalltalkの例におけるインスタンスオブジェクトの生成と同様に動作する。C++の表記を踏襲したJavaでは、new演算子によってインスタンスを生成する。
インスタンスメソッドを呼び出すには次のように記述する:
objectA.getName(); // "John" を返すobjectB.getName(); // "Joe" を返す
上記の例は、Smalltalkの例におけるインスタンスメソッドの呼び出しと同様に動作する。
クラスメソッドを呼び出すには次のように記述する:
MethodSample.setNumber(100);MethodSample.getNumber(); // 100 を返す
上記の例は、Rubyの例における直接クラス名を指定したクラスメソッドの呼び出しと同様に動作する。
Javaではメタクラスとしてjava.lang.Classクラスをサポートする。java.lang.Object.getClass()メソッドによりClass型オブジェクトを取得できる。また、クラス名.classという構文でClass型オブジェクトを取得することもできる。さらにリフレクションを使うことで、Class型オブジェクトからメソッドを呼び出したり、フィールドにアクセスしたりすることもできる。
.NET FrameworkではメタクラスとしてSystem.Typeクラスをサポートする。System.Object.GetType()メソッドによりType型オブジェクトを取得できる。C#ではtypeof演算子により型シンボルからType型オブジェクトを取得することもできる。また、リフレクションもサポートしている。
C++はクラス型オブジェクトやリフレクションをサポートせず、クラス自体を何らかの変数に代入するようなことはできない。

メッセージ送信とメソッド呼び出し

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

仮想メソッド

仮想メソッド (virtual method) とは、サブクラスでオーバーライド (再定義) し、動作を変更することのできるメソッドのことである。C++では仮想関数と呼ばれる。
C++およびC#のメソッドはデフォルトで非仮想であり、メソッドにvirtual修飾子をつけることで仮想メソッドとすることができる。なお、C#のクラス（参照型）は仮想メソッドおよび非仮想メソッドの両方を定義することができるが、構造体（値型）は仮想メソッドを定義することができない。一方Javaのメソッドは常に仮想であり、final修飾子をつけることでオーバーライドを禁止できるが、非仮想メソッドとなるわけではない。finalメソッドを非finalメソッドに変更しても、バイナリ互換性は維持される。

仮想と非仮想

C++では、一般に仮想関数はコンパイル時にどのメンバー関数を呼び出すかを確定できないため、通常の非仮想なメンバー関数呼び出しよりもパフォーマンスが悪いというデメリットがある。そのため、パフォーマンスを気にするC++プログラマには、継承する必要がないクラスのメンバー関数（特にデストラクタを含む）にvirtual修飾子をつけることを非常に嫌う傾向がある。また、C++にはtemplateという機能が存在し、多くの場合仮想関数はtemplateで代用できてしまうため仮想関数にこだわる必要がないという事情もある。ただし、デストラクタが非仮想の場合、派生クラスのインスタンスのポリモーフィックなdeleteが不可能となる、という利便性および安全上のデメリットも発生する。
メソッドがデフォルトで非仮想というC++に準ずる設計選択をしたC#においても、仮想メソッドの呼び出しには非仮想メソッドよりもコストがかかることを念頭に置いて利用する必要がある。
Javaのfinal修飾子は、パフォーマンス上の理由というよりはむしろ、派生クラスでの不用意なオーバーライドを禁止してバグを未然に防止することにある。「Javaではメソッドをfinal修飾することでコンパイラの最適化によりパフォーマンスが向上する」という神話があるが、一方で、Java仮想マシンの性能によってはメソッドをfinalと宣言したからといって優れたパフォーマンスが得られるとは限らないという指摘もある。なお、OracleのHotSpot VMは、finalメソッドを検出して非常に効率よく実行できるように最適化されていると説明されている。

抽象メソッドと具象メソッド

抽象メソッド (abstract method) とは仮想メソッドの一種で、メソッドの実装が無く、宣言だけされているもののことである。C++では純粋仮想関数 (pure virtual function) と呼ばれる。このメソッドを利用するには、このメソッドを含むクラスを継承し、そこでこのメソッドをオーバーライドして実装する必要がある。従って、抽象メソッドを含むクラスは継承しない限りインスタンス化できない。このようなクラスを抽象クラスと呼ぶ。
具象メソッド (concrete method) は抽象メソッドの逆で、実装をもつメソッドのことである。主に抽象メソッドをオーバーライドしたインスタンスメソッドのことを意味するために使われる。
JavaおよびC#ではabstract修飾子を用いて抽象メソッドを宣言できる。抽象メソッドを持つクラス自体もまた、必ずabstract修飾子を使って抽象クラスとして定義しなければならない。
抽象メソッドはデザインパターンの一つTemplate Method パターンで主要な役割を果たす概念であり、ソフトウェアの拡張性、再利用性、汎用性を高めるのに役立つ。
また、抽象メソッドのみを持つ抽象型として、JavaおよびC#ではインターフェイス (interface) を定義できる。JavaおよびC#においてクラスは多重継承できないが、インターフェイスを複数実装することはできる。

アクセサ

アクセサ (accessor) とは、特にオブジェクトが持つフィールドの値の設定 (set) および取得 (get) をするために定義されるメソッドのことを指す。通例、1つのフィールドに対して、単一の入力を持つsetterおよび単一の出力を持つgetterが対称的に用意されるが、片方だけが用意されることもある。アクセッサあるいはアクセッサーとも表記する。

アクセサの表記

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

アクセサとRADツール

アクセサが他のメソッドと区別される大きな理由としてRADツールの連携を想定していることにある。多くのRADツールでは、アクセサとして定義されたメソッドを特別扱いし、グラフィカルユーザインタフェース (GUI) のデザイン時にデザイン画面からアクセサの引数を指定できるようになっている。
Javaには元々、get/setの接頭辞を記述するという規則はなかったが、RAD支援ライブラリのJavaBeans (java.beans) がset/getから始まるメソッドを値設定用のメソッドとして特別に扱うため、Beansが登場して以降、Javaのクラスライブラリ全体に渡ってset/getの接頭辞を使用する命名規則が導入された。現在でも初期の名残としてset/getを接頭辞として持たないアクセサメソッドが存在する（String.length()など）。

多重定義

引数の数、型、順序などが異なる同じ名前のメソッドを定義することをメソッドの多重定義といい、多くのオブジェクト指向言語ではメソッドやコンストラクタを多重定義できる機能を持つ。オーバーロード (overload) とも呼ばれるが、オーバーライドとの混同に注意が必要である。
ただし、PHPやPerlのように、プログラミング言語によっては、型の曖昧さが原因によりメソッドを多重定義できないものもある。この場合はメソッドの引数をメソッド先頭で読み取り、引数の型を判定する条件分岐で対応する。