この記事を書くきっかけ
関数とメソッド、どう違うの? という話を小耳にはさみました。
で、改めて自分の認識を確認するとともに書き留めておこうと思いました。
また、システム開発を始めたばかりでこの辺りがよくわからないという方の参考にもなればと思います。
何故Javaか? というと、やはり使用者が多そうなこと、また後述しますが関数っぽいものもメソッドの仕組みに乗っかることになるため区別がつきにくいのではと思ったからです。
まず関数とは?
関数の定義について考えると、
「何かの値を受け取って(引数)結果の値を返す(戻り値)、処理のカタマリ」
ということになると思います。
そしてより純粋な関数となると以下の条件が出てきます。
・引数以外から影響を受けない。
・値を返す以外の仕事をしない。
・引数が同じならいつ何回実行しても同じ結果を返す。
副作用がない、とか参照透過、とか言われている概念です。
つまり以下のようなパターンは純粋な関数とはなりません。
・Date、Calendar、LocalDateなどで現在時刻を取得して使用している
→結果が実行時刻により異なる
・ファイルやDBから値を取得して使用している
→取得された内容がさっきと今で変わっていたら結果が異なる
・ファイルやDBに更新を行っている
→値を返す以外の仕事をしている(副作用を起こしている)
・System.out.printlnなどで何か出力している
→標準出力も副作用の一種
・クラス内のフィールドにアクセスしている
→フィールドの値を変えるのは副作用、またフィールドの値が変わると結果も変わってしまう
また微妙な感じですがこれも副作用アリです。
final public void someMethod(final List<String> list, final String text){
list.add(text); // 呼び出し元のlistに影響を与えてしまう
}
一方↓のようなモノは純粋に関数と呼んでいいと思います。
final public String twice(final String text){
return text + text; // text == nullの場合は今は考えない
}
いつ何回呼び出しても、引数の文字列が同じであれば同じ結果が帰ってきます。
Javaではこういった処理のカタマリをクラスの外に置くことはできません。
よって上記のようにメソッドを使用して関数を表現することになります。
(Java8以降なら関数型インタフェースの実装を作っておく方法もあるけどそれは抜きにして)
staticに至る
ここまでで関数、特に純粋な関数とは何かについて考えました。
そしてJavaではこのような「関数的なメソッド」を関数として使うことになります。
関数的なメソッドは何らかのクラスに属しますが、関数的なのでそのクラスのフィールドと干渉しません。
下記処理の1回目、2回目、2回目'、3回目はすべて同じ結果(ABCABC)が出力されます。
final public class Sample {
public static void main(String args[]){
// 1回目
final TextSomething obj1 = new TextSomething();
System.out.println(obj1.twice("ABC"));
// 2回目
final TextSomething obj2 = new TextSomething();
System.out.println(obj2.twice("ABC"));
// 2回目'
System.out.println(obj2.twice("ABC"));
// 3回目
final TextSomething obj3 = new TextSomething();
System.out.println(obj3.twice("ABC"));
}
}
final public class TextSomething {
// 関数的なメソッド
final public String twice(final String text){
return text + text;
}
}
しかしながらよく考えると、twiceはobj1、obj2、obj3どのインスタンスから何度呼び出しても、
その内部データ状態に依らず同じ結果を返してくるものです。
であれば、それを使うためにわざわざnewしてインスタンスの実体を作ることはありません。
というわけで関数的なメソッドをstaticで宣言します。
そして似た系統の関数的でstaticなメソッドを固めたクラスがよく作られます。
ユーティリティクラスなどと呼ばれているこんなやつです。
final public class TextUtil {
final public static String twice(final String text){
return text + text;
}
final public static String twice2(final String text){
return text.toUpperCase() + text.toLowerCase();
}
}
final public class Sample2 {
public static void main(String args[]){
System.out.println(TextUtil.twice("aBc")); // aBcaBc
System.out.println(TextUtil.twice2("aBc")); // ABCabc
}
}
staticなメソッドであれば、クラス内のフィールド変数へのアクセスはそもそもコンパイルエラーとなり不可能です。
では本来のメソッドとは何か
メソッドについては、しばしば「クラスの振る舞い」と表現されます。
しかしながら個人的にはこの表現は微妙と思います。
クラスの振る舞い、という表現はクラス視点です。
ClassAはこんな動作とこんな動作ができて、ClassBはこんな動作とこんな動作ができる……
クラスの擬人化みたいな感じですが、我々はクラスではありません。
システム開発をする我々はクラスを使用して開発を進める立ち位置のプログラマです。
methodという単語も直訳すれば「方法」なので、これは**「クラスを操作する方法」と考えた方がわかりやすいです。
メソッドは「オブジェクト(インスタンス)の操作系統」**です。
何を操作するかといえば、基本、そのインスタンスが持っているフィールドの変数です。
例えば、privateで宣言されている変数を操作するこんなメソッドがあります。
final public class DataClass {
private Integer bet;
// 変数betの値を取得する操作
final public Integer getBet() {
return bet;
}
// 変数betの値を変更する操作
final public void setBet(Integer bet) {
this.bet = bet;
}
}
ゲッターとセッター、いわゆるアクセサメソッドです。
他にも、betの値を10倍にするメソッドや1/2にするメソッドがあったりするかもしれません。
クラスによっては、データベース接続設定(サーバ名、ユーザ名、パスワード、etc)をそれぞれセッターで渡し、
その情報を基にDB接続をしてその接続状態を内部データで保持するpublic void dbConnect()なるメソッドを持っていたりするかもしれません。
というわけで、純粋な関数に対して、「より純粋なメソッド」を考えるとそれはインスタンスの状態に影響を与え、または与えられる処理のカタマリであると思われます。
staticはオブジェクト指向なのか
さて、Javaのオブジェクト指向におけるメソッドの本来の姿がオブジェクトに対する操作系統であるとすると、
副作用のない関数的なstaticメソッドは正しくメソッドなのでしょうか。
そもそもstaticメソッドばかりで、故にフィールド(状態)を持たない(newされることもない)ユーティリティクラスはそもそもオブジェクト指向的に正しいのか否か……
このあたりはオブジェクト指向とはなんぞやみたいな面倒な話になるので置いておきますが、先ほどのユーティリティクラスをオブジェクト指向的に(関数的→本来のメソッド的に)リメイクするとこんな感じでしょうか。
自分はこういった書き方を普段しませんのでこれでいいのか微妙です。どうでしょうか?
final public class TextObject {
private String text;
final public void setText(String text){
this.text = text;
}
final public String twice(){
return text + text;
}
final public String twice2(){
return text.toUpperCase() + text.toLowerCase();
}
}
final public class Sample3 {
public static void main(String args[]){
final TextObject obj = new TextObject();
obj.setText("aBc");
System.out.println(obj.twice()); // aBcaBc
System.out.println(obj.twice2()); // ABCabc
}
}
まあ、個人的にはこのくらいなら(Javaなら)ユーティリティクラスを使って書いた方が早いと思います。
どっちが良いか論は本題ではないので置いておくとして……
まとめ
メソッドとは
オブジェクトの内部状態を変更したり、また内部状態を取り出したりする**「オブジェクトの操作方法」**。
内部状態、というのはざっくり言うとオブジェクト(インスタンス)の変数の値。
(あるいはファイルへのアウトプットやDBへの更新などの機能を発火させるものも)
関数とは
「1つ以上の引数を受けて、何らかの計算などをした結果を戻り値として返すだけの処理のカタマリ」。
Javaではメソッドの仕組みに乗っかって関数を書く。