Java や Objective-C におけるクラスの“振る舞い”で驚かされたこと


タイトルのとおりの印象を持ったのをきっかけにして、JavaObjective-C の勉強を兼ねて調べてみたことをメモ。三猫さんと odz さんとのやりとりを見ていて、ちょっと関係することもあるかな…とも。


Smalltalk のクラスの“振る舞い”

よく言われるように、Smalltalk ではクラスもオブジェクトです。このことは、クラスも別のクラス(俗に言うところのメタクラス)のインスタンスであり、また、メッセージのレシーバになることができることを意味します。

もっとも後者の「レシーバに云々…」については、ケイのオブジェクト指向かそれに準ずるオブジェクト指向に立脚したときに限る…との但し書きが必要でしょう。つまり、C++ や Eiffel、Java など、ストラウストラップのオブジェクト指向の強い影響下にある言語においては、そもそもメッセージングという概念はそぐわないので後者は無用というわけです。


もちろん、言語設計者の意図に反してでも、これらの言語にメッセージングという考え方を持ち込むこと自体は(それについて、お門違いの不平を漏らしたりして周りに迷惑をかけたりしない限りにおいては)、ユーザーの自由にしていいはずです。ただ、あくまで自分の勝手な嗜好で、仮想メンバ関数の動的な起動を「メッセージング」に、メンバアクセス演算子(あるいはドット演算子)の第一オペランドを「レシーバ」に見立てているだけだということを忘れぬよう心がけたほうが幸せになれるような気はします。


ところで、Smalltalk ではクラス名の記述はクラスのリテラルを兼ねるので(厳密には、クラスリテラルというもの自体存在せず、クラスというオブジェクトがクラス名と同じグローバル変数に関連付けられているだけ…)、コード上ではクラス名にメッセージを送る記述を書くことができます。

String name        " => #String "
String superclass  " => ArrayedCollection "


こんなことをふまえながら…。


Java では、String.getName() とは書けない!?

Java のクラスはオブジェクト(java.lang.Class のインスタンス)ですが、オブジェクトとしてのクラスにアクセスするには、クラス名だけではダメで、クラスリテラルなどの記述を介する必要があります。

String.class.getName()         //=> java.lang.String
String.class.getSuperclass()   //=> java.lang.Object


ただ、Smalltalk 好きにはやっかいなことに、この Java のクラスリテラルの表記をメッセージングと見立てたとき、送信しているメッセージにあたる「class」を Smalltalk のオブジェクト(もちろん、クラスも含む…)が解釈できるため、この結果には少々混乱させられます。

なぜなら、Smalltalk でメッセージ「class」を送信した際に起動されるメソッド「Object >> #class」は、Java でいうところの Object#getClass() に相当するものなので、レシーバの属するクラス(レシーバがクラスならメタクラス)が返されることを期待してしまうからです。

"Smalltalk(Squeak)"
String class                     " => String class "
String class class               " => Metaclass "
String class class class         " => Metaclass class "
String class class class class   " => Metaclass "
//Java
String.class.getClass()              //=> java.lang.Class
String.class.getClass().getClass()   //=> java.lang.Class


一方、Java でクラスをオブジェクトとして扱いたいとき名前そのままでは駄目で、クラスリテラルなどを用いなければならないことは、Ruby のブロックをオブジェクトとして扱いたいときに proc への変換を意識しないといけないのと、ちょっと似ているな…との印象を持ちました。


Objective-C では、[NSString class] がクラス「NSString」自身を返す!?

Objective-CSmalltalk のサブセットを言語内に持っているちょっと変わった C 言語…的な存在で、もともとは Smalltalk のコードを C コンパイラに通すためのプリプロセッサとして開発された経緯を持ちます。Smalltalk の式(メッセージ式)は [ ] でくくってコード中に埋め込まれます。

ちなみに Smalltalk では式を [ ] でくくるとブロック(ブロッククロージャSqueakSmalltalk-80 のような古典的な実装ではブロックコンテキスト)と呼ばれる手続きオブジェクトになりますが、Objective-C は原則としてブロッククロージャを機能として欠いているのでこれでも問題はないわけです(ブロッククロージャを扱える処理系もあります)。


Objective-C のクラスは Java のそれと違って(そして Smalltalk のと同様に…)それぞれに固有のメタクラスを有しています。しかし、残念ながらこのメタクラスは実行時には内部的に使われるのみで、Smalltalkメタクラスと違い、少なくともメッセージ式を介してそれにアクセスすることはできません。


Objective-C で、メッセージ「class」は、通常のオブジェクトに関しては属するクラスを返すものの、クラスでは自身を返す…という振る舞いをします。やはり Smalltalk 使いは面食らいますが、Java のクラスリテラルみたいな感覚かな…と思えば、そう驚くほどのことでもないような気がしてきました(^_^;)。

//Objective-C
[@"hoge" class]            //=> NSConstantString
[[@"hoge" class] class]    //=> NSConstantString
[NSConstantString class]   //=> NSConstantString
"Smalltalk"
'hoge' class               " => String "
'hoge' class class         " => String class "
String class               " => String class "

クラス変数(static 変数) vs クラスインスタンス変数

Smalltalk でクラス変数はクラス、そのサブクラス群、それらに属するインスタンスで共有できるグローバル変数の一種です。「すべてをメッセージングで…」というポリシー上、public にこそできませんが、Java の static 変数と似た部分が多くありそうです。Smalltalk には、このクラス変数とは別に、クラスがオブジェクトとして固有の値を保持できるインスタンス変数もあり、こちらは「クラスインスタンス変数」と呼ばれます。


Java のクラスは、すべて java.lang.Class のインスタンスなので、その建前上、オブジェクトとしてのクラスに固有の属性を持たせることができず、Smalltalk のクラスインスタンス変数に相当するものは存在しません。


余談ですが、Ruby のクラスも Java のクラス同様、個別のメタクラスは持たず、すべて共通の「Class」クラスのインスタンスとなっているにもかかわらず、しかしクラスインスタンス変数を持つことができています。これは 「特異クラス」と呼ばれるオブジェクト特異的な匿名クラスをアドホックに作り、本来そのオブジェクトが属しているはずのクラス(この場合、「Class」クラス)と、ユーザーにはそれと気づかれないようにすげ替えておく…という仕組みにより実現されています。


他方で、Objective-C は内部的でこそあれメタクラス(クラス固有の…)を有するので、クラスインスタンス変数を使えるか…と思いきや、なぜかそういうふうにはなっていません。メタクラスはあくまでメソッドホルダという位置づけのなのでしょう。なお、Apple の開発者向け資料などでは、語感を優先してクラスインスタンス変数のことを、単に「クラス変数」と呼称したり、クラス変数とクラスインスタンス変数を区別しない慣習があるので注意してください。よく、Objective-C にはクラス変数がない…と書かれていますが、Java のように static 変数は使えるわけなので、ないのは「クラスインスタンス変数」で「クラス変数(に類似の機能)」はあるわけです。ややこしいですね。


クラスメソッド vs static メソッド

クラス変数が static 変数同様に共有されるなら、クラスメソッドも…と思いたいところですが、少なくとも Smalltalk において「クラスメソッド」といえば、メタクラスに定義され、そのインスタンスであるクラスからのみ起動可能なメソッドのことを指します。Java の static メソッドのようにインスタンスからも起動できる共有メソッドにはなっていません(継承機構によりサブクラスからは起動可能です。念のため)。


JavaSmalltalk のクラスメソッドに相当するものは java.lang.Class に定義されたメソッド…ということになりますが、クラス固有のメタクラスを持たない都合上、これに固定されてしまいます。その代わり Java 界隈では、static メソッドをクラスメソッドと呼称する習慣があるようです。


Objective-C ではメタクラスがあるので、クラス独自のクラスメソッドを定義して使えます。static 関数もありますが、こちらは Java などとは意味合いが違い、本来の C 言語の仕様としての…ということになるでしょう。


ここいらへんをうまく表にまとめられないかな…と思ったのですが、面倒くさいのでやめにしました(^_^;)。まあ、とにかく言語をまたいでこの種の機能(変数もしくはメソッドが、クラス特異的かインスタンスと共有か…)について論じるときは、はじめに用語についてコンセンサスを得ておいたほうがよい、ということは確かなようです。