Common Lisp クックブックもぼちぼちやってます。
2007-12-02
■[Objective-C][Java] Objective-CのプロトコルとJavaのインターフェースの機能の比較

某所の諸々の丁々発止の様を (・∀・) もしくは (0゚・∀・) しながら見ている方の中には、Objective-Cのプロトコルに興味を持った方もいると思う。さればObjCのプロトコルとJavaのインターフェースの機能を比較してみるのも一興かと存じ、不肖僭越ながら此処にしたためる所存に候。
※ここで比較するのはあくまで言語仕様です。以下「インターフェース」は「JavaのInterface」、「プロトコル」は「ObjCのProtocol」を指します。
定義できる要素
インターフェースはクラス-2(インスタンスを生成できない、メソッドを実装できない)の機能を持つ。インターフェースと強く関連するクラスやインターフェースを、ネストすることで定義できる。
一方プロトコルはメソッドしか定義できない。Javaと異なり、ObjCのクラスとプロトコルは専用の変数・定数を持てない。もしプロトコルに定数を含めたい場合、その定数を返すようなクラスメソッドを宣言する。
プロトコルと強く関連するクラスやプロトコルを定義したい場合、特に方法はない。コメントやドキュメントにでも書いておくしかない。
拡張(継承)
インターフェース、プロトコル共に、複数のインターフェース、プロトコルを継承できる。
定義
比較メソッドを宣言したインターフェース・プロトコルの例。どちらも宣言のみで、実装はできない。名前が異なる理由は後述。
Java:
public interface Comparable { int compareTo(Object o); }
ObjC:
@protocol ComparisonMethods - (int)compareTo:(id)anObject; @end
メソッドの実装
Java:
public class Body implements Comparable { int compareTo(Object o) { return ...; } }
ObjC:
@interface Body <ComparisonMethods> - (int)compareTo:(id)anObject; @end @implementation Body - (int)compareTo:(id)anObject { return ...; } @end
型指定
Java:
Comparable var;
ObjC:
id <ComparisonMethods> var;
ObjCではプロトコルとクラスは別のもので、プロトコルを型としては指定できない。その代わりにプロトコルを指定する文法が用意されているので、id 型(オブジェクト型。JavaのObjectに相当)とプロトコルを指定する。上の例では ComparisonMethods プロトコルを実装したオブジェクトを宣言することになる。もっともObjCではクラスやプロトコルを指定する意味はあってないようなもので、間違ったら警告が出てラッキー、程度のもの。上の例のオブジェクトが本当にComparisonMethodsプロトコルに準拠しているかどうかはまったく保証されない。
宣言したメソッドをすべて定義せずにコンパイルした場合
- インターフェース
- エラー
- プロトコル
- 警告。ただしコンパイラオプション -Wno-protocol か、もしくはXcodeのアクティブターゲットの「Objective-C未実装のプロトコル」をチェックすることでエラーにできる。
インフォーマル(非公式)プロトコル
仕様ではないが、ObjCにはインフォーマルプロトコルというプロトコルの使い方がある。プロトコルは定義しておくが、どのクラスもそのプロトコルを適用しない。ただし、その中から必要なメソッドのみを実装する。コード上ではメソッドとプロトコルに何の関係もないのが特徴。
例えばWebObjectsでデータベースと通信するEOAdaptorChannelにはEOAdaptorChannel Delegateというインフォーマルプロトコルが用意されていて、EOAdaptorChannelから呼ばれるメソッドがたくさん宣言されている。もしEOFが発行するSQL文をチェックしたければ、デリゲートクラスに adaptorChannelShouldEvaluateExpression だけ実装しておけばいい。インフォーマルプロトコルのメソッドは、無視しても問題ないようになっている(呼び出し側のオブジェクトがメソッドの有無をチェックしている)。
名前
上の例なら、比較メソッドを宣言したインターフェースはComparable、プロトコルはComparisonMethodsとなる。
まとめ、というか個人的な所感
インターフェースとプロトコルは、案外似てるようで使い勝手が異なる。インターフェースはクラスの性質に、プロトコルは振る舞いに注目しているらしい。
- インターフェース名はそれを実装するクラスの形容詞になるのに対し、プロトコル名はオブジェクトの振る舞いを表すか(動名詞)、メソッドの集合を表す(名詞)。
- インターフェースは型として使えるが、
プロトコルは型ではない。*1プロトコルは「〜プロトコルに準拠したid(オブジェクト)」という型ではあるけれども、独立したデータ型としては使えない。 - インターフェースのメソッドはすべて実装しなければいけないが、プロトコルは(デフォルトでは)そうではない。コード上で指定する必要すらないインフォーマルプロトコルもある。
こうしてまとめてみると、おそらくインターフェースとプロトコルの根っこは同じなんだと思う*2。Javaはオブジェクトをクラスで縛ろうとする世界で、ObjCはメッセージで縛ろうとする世界だけど、どちらも単一継承の世界でもある。その世界で多重継承のいいとこどりをしようとした結果、クラス中心に考えるJavaの世界に合うのがインターフェース、メソッド中心に考えるObjCの世界に合うのがプロトコルだったんじゃなかろうか。ObjCもSmalltalkと同じく、メッセージを送る相手のクラスをたいして気にしない。それよりも相手がどんなメソッドを持っているかが重要だから、メソッドの集合がクラスよりも重要な型になる。クラスだけが型じゃないよ。*3 *4
sumim
2007/12/03 10:28
「プロトコルは型ではない」というのがちょっとよく分かりませんでした。たとえば、プロトコルは(Obj-C 独特の弱いものながらも)型チェックの対象になるようですし、“Only instances can be statically typed to a protocol, just as only instances can be statically typed to a class. ”のようなクラスと対比した言い回しも許容されるように見受けられます。
sumim
2007/12/03 10:35
あ、分かりました。よく読んでいませんでした(^_^;)。「プロトコルを型としては指定できない。」というあたりですね。
carver
2007/12/03 13:15
あああ、ご指摘ありがとうございます。その文章修正して考え直したら結論が真逆になりましたw