メソッド (計算機科学)
メソッド (method) あるいは メンバー関数 (-かんすう, member function) とはオブジェクト指向プログラミング言語において、あるクラスまたはオブジェクトに所属するサブルーチンを指す。
概要
編集this
や self
といったキーワード︵予約語︶、あるいはメソッドに渡された引数によって参照することができる。
C++ではクラスに属さない関数である大域関数︵グローバル関数あるいはフリー関数︶と対比されることがある。
またメソッドはサブクラス化の際にオーバーライドされることがあり、実際に発生する動作︵振る舞い︶がレシーバに依存するという特徴を持つ。これを多態性︵ポリモーフィズム︶と呼ぶ。
統一モデリング言語 (UML) ではメソッドのことを操作 (operation) と呼ぶ。
インスタンスメソッドとクラスメソッド
編集インスタンスメソッドとクラスメソッドの例
編集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 := MethodSample
type familyName: 'Hillton'.
type familyName. "'Hillton' を返す".
上の例では type
にクラスオブジェクトMethodSample
を代入して、#familyName
と#familyName:
メッセージを送りクラスメソッドを実行している。
クラスオブジェクトのインスタンス変数 name
は、インスタンスオブジェクトのインスタンス変数と異なり MethodSample
に属する全てのインスタンスオブジェクトで共有される。
クラスオブジェクトのインスタンス変数が共有される例を示す:
| type objectA objectB |
type := MethodSample
objectA := 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++はクラス型オブジェクトやリフレクションをサポートせず、クラス自体を何らかの変数に代入するようなことはできない[4]。
メッセージ送信とメソッド呼び出し
編集オブジェクト指向を解説した書籍などでメソッド呼び出しについてオブジェクトにメッセージを送信すると表現されることがある。C++系統の言語ではオブジェクトの操作は単なるメンバー関数(メソッド)呼び出しに過ぎず比喩として捉えられる場合が多い。SmalltalkやObjective-Cにおいては、メッセージ送信は単なる比喩ではなく実体のある機構であり、メソッド呼び出しとは別物であるため注意が必要である。
仮想メソッド、抽象メソッドと具象メソッド
編集仮想メソッド
編集virtual
修飾子をつけることで仮想メソッドとすることができる。なお、C#のクラス︵参照型︶は仮想メソッドおよび非仮想メソッドの両方を定義することができるが、構造体︵値型︶は仮想メソッドを定義することができない。一方Javaのメソッド︵インスタンスメソッド︶は常に仮想であり、final
修飾子をつけることでオーバーライドを禁止できるが、非仮想メソッドとなるわけではない。final
メソッドを非final
メソッドに変更しても、バイナリ互換性は維持される[5]。
仮想と非仮想
編集virtual
修飾子をつけることを非常に嫌う傾向がある。また、C++にはtemplateという機能が存在し、多くの場合仮想関数はtemplateで代用できてしまうため仮想関数にこだわる必要がないという事情もある[疑問点]。ただし、デストラクタが非仮想の場合、派生クラスのインスタンスのポリモーフィックなdeleteが不可能となる、という利便性および安全上のデメリットも発生する。
メソッドがデフォルトで非仮想というC++に準ずる設計選択をしたC#においても、仮想メソッドの呼び出しには非仮想メソッドよりもコストがかかることを念頭に置いて利用する必要がある[6][7]。
Javaのインスタンスメソッドは常に仮想であるが、クラスメソッド︵静的メソッド︶はオーバーライドすることのできない非仮想であるため、静的メソッドのほうが呼び出しコストが小さく、パフォーマンス上のメリットがある[8]。
Javaのfinal
修飾子は、パフォーマンス上の理由というよりはむしろ、派生クラスでの不用意なオーバーライドを禁止してバグを未然に防止することにある。﹁Javaではメソッドをfinal
修飾することでコンパイラの最適化によりパフォーマンスが向上する﹂という神話があるが、一方で、Java仮想マシンの性能によってはメソッドをfinal
と宣言したからといって優れたパフォーマンスが得られるとは限らないという指摘もある[9][10]。なお、OracleのHotSpotVMは、final
メソッドを検出して非常に効率よく実行できるように最適化されていると説明されている[5]。
抽象メソッドと具象メソッド
編集abstract
修飾子を用いて抽象メソッドを宣言できる。抽象メソッドを持つクラス自体もまた、必ずabstr
act
修飾子を使って抽象クラスとして定義しなければならない。
抽象メソッドはデザインパターンの一つTemplate Method パターンで主要な役割を果たす概念であり、ソフトウェアの拡張性、再利用性、汎用性を高めるのに役立つ。
また、抽象メソッドのみを持つ抽象型として、JavaおよびC#ではインターフェイス (interface) を定義できる。JavaおよびC#においてクラスは多重継承できないが、インターフェイスを複数実装することはできる。
オーバーライド
編集アクセサ
編集アクセサの表記
編集アクセサとRADツール
編集String.length
()
など︶。
アクセサと公開フィールド
編集"アクセサ目的以外の#value"
[ 1 ] value. "-> 1"
( Continuation currentDo: [ :sink | sink value: 0. ] ) value. "-> 1"
"純粋なアクセサとしての#value"
( 1 ) asValueHolder value. "-> 1"
( 0 -> 1 ) value. "-> 1"
(2)
"共通の手順"
block :=
[ :valueHolder |
self
traceCr: valueHolder value printString.
].
"手順の適用"
block value: ( 1 ) asValueHolder.
block value: ( 0 -> 1 ).
block value: [ 1 ].
block value: ( Continuation currentDo: [ :sink | sink value: 0. ] ).
#value以外の実例
"アクセサ以外:1と10を元に計算している。"
( 1 to: 10 ) size.
"アクセサ"
#( 1 2 3 ) size.
"アクセサ以外:'text.txt'を全部読み込んだ結果を返す"
'text.txt' asFileRefarence readStream contents.
"アクセサ以外:self valueの結果を返す"
1 asValueHolder contents.
"アクセサ"
#( 1 2 3 ) contents.
"アクセサ以外: Class名を生成して返す。"
Class name.
"アクセサ"
CmCommand new name.
純粋なオブジェクト指向環境として知られるSmalltalkでは、アクセサによる多態性がMVCの依存性辞書[要説明]の管理を始めあらゆる箇所で活用されている。
アクセサ論争
編集public
変数への直接操作が往々にして利用されることがある。
●効率の問題。メソッドをいちいち呼び出すコストを避けたい場合。ただしほとんどはコンパイラ最適化によるインライン化で解決できる[13]。
●記述量の問題。単純に値を設定/取得するだけのことに全てメソッドを記述するのは間違っているという考え方。
問題となるのは後者である。
まずカプセル化や多態性の観点から、アクセサを用いない変数参照は将来にわたっての変更耐久性や拡張性が著しく劣る。また記法の一貫性からアクセサを指示する向きもある。一方否定派は﹁強力な IDE を用いればリファクタリングは可能であり、むしろフィールドへの直接アクセスを用いる方が意味が明確となる﹂という主張を展開し、時にフレームに発展する場合がある。これは現代の﹁goto文論争﹂ともいうべき、半ば宗教的な対立関係である。
そのほか、C++ではC言語との相互運用時に構造体を用いることがあるが、構造体のメンバー変数は隠蔽しないことが多い。C++におけるclass
キーワードとstruct
キーワードは、デフォルトのアクセシビリティが異なるという違いしかないが、メンバー変数を隠蔽しないC言語互換の構造体として利用するPOD (Plain Old Data) 型を定義する際に、class
キーワードではなくstr
uct
キーワードが使われることもある。
Object Pascal はこの反省から、変数を後からアクセサ化できる仕組みとしてプロパティを導入した。[14]また Ruby など、アクセサを簡単に定義できるメソッドや構文を備えている言語もある。
多重定義
編集参考文献・脚注
編集obj.staticMemberFunc()
のようにインスタンスから静的メンバー関数を呼び出す糖衣構文はサポートされるが、クラス自体をオブジェクトとして扱うことはできない。
(五)^ abJavaにおけるメソッド呼出しの仕組み | Java Magazine | Oracle
(六)^ Performance Tips and Tricks in .NET Applications | Microsoft Docs
(七)^ Writing Faster Managed Code: Know What Things Cost | Microsoft Docs
(八)^ パフォーマンスに関するヒント | Android デベロッパー | Android Developers, Internet Archive
(九)^ Javaの理論と実践: ファイナル・アンサー? finalキーワードを有効に使用するためのガイドライン | IBM, Internet Archive
(十)^ Javaの理論と実践: パフォーマンスの都市伝説 | IBM, Internet Archive
(11)^ プロパティ - C# プログラミング ガイド | Microsoft Docs
(12)^ C++/Java/C#の場合、イミュータブルに関してはpublicなconst/final/readonlyフィールドで代用することが可能なケースも存在する。この場合、getterも不要である。
(13)^ そのほか、C/C++ではコピーのコストを避けるため、関数の戻り値ではなくポインタあるいは参照による引数経由で値を返すことがあるが、コピー省略 (copy elision) およびReturn Value Optimization (RVO) をサポートするコンパイラでは、戻り値で返したとしても不要なコピー処理は除去される。コピー省略 - cppreference.com
(14)^ “Properties (Delphi) - RAD Studio”. docwiki.embarcadero.com. 2023年10月13日閲覧。