Javaとかそんなの
前提
以下のようなクラスNodeがある。
class Node{ private int x; private int y; public Node(int x, int y){ this.x = x; this.y = y; } }
やりたいこと
これを格納するListクラスを考える。通常のListクラスであれば、以下のようになるだろう。
Node node = new Node(1,2); List list = new LinkedList(); list.add(node); Node node2 = (Node)list.get(1);
問題は、listからgetするときにNode型にキャストする必要があるという点である。
このlistはNodeクラス以外を格納するつもりがないのだから、キャストしなくてはいけないと言うのは嫌だ。
通常、解決策として提示されるのは、委譲を用いたラッパークラスを生成することとになる。
すなわち
class NodeList{ private LinkedList list; public NodeList(){ this.list = new LinkedList(); } public boolean add(Node node){ return this.list.add(node); } public Node get(int i){ return (Node)this.list.get(i); } }
のようなクラスを生成することになる。だがこれ以外の解決策を考えてみたい。
その1
import java.util.*; class NodeList2 extends LinkedList{ public Node get(int i){ return (Node)(super.get(i)); } public boolean add(Node node){ return (super.add(node)); } }
これだとコンパイルエラーがでる。
NodeList2.java:3: NodeList2 の get(int) は java.util.AbstractList の get(int) をオーバーライドできません。 互換性のない戻り値の型を使おうとしました。 検出値 : Node 期待値 : java.lang.Object public Node get(int i){ ^ エラー 1 個
これは、Listのgetメソッドの返り値はObject型であることが理由である。これを勝手に子クラスで変更してしまってはいけない、というのが理由だ。
その2
そこで、Perlプログラマ的な解決策を提示してみることとした。(俺はPerlプログラマです)
import java.util.*; class NodeList3 extends LinkedList{ public Node getNode(int i){ return (Node)(super.get(i)); } public boolean add(Node node){ return (super.add(node)); } }
だが、これでは仮にgetメソッドを呼ばれたときに不便である。
仕方がないので、以下のような方法をとることにした。
import java.util.*; class NodeList3 extends LinkedList{ public Node getNode(int i){ return (Node)(super.get(i)); } public Object get(int i){ throw new RuntimeException(); } public boolean add(Node node){ return (super.add(node)); } public boolean add(Object o){ throw new RuntimeException(); } }
すなわち、仮に通常のgetを呼ばれた場合に、ランタイムエクセプションを投げることで、強制終了にしてしまおうと言う力技である。
当初は、このメソッドをprivateにしてしまいたいと考えた。しかしながら、それは不可能であることがわかった。つまり、publicなクラスをprivateとして継承することは、アクセス権を狭める結果となるので、出来ないのだ。
どこがPerl的かと言うと、PerlでAbstructクラスを生成する際には、この方法を使う。つまり、Abstructクラスで定義されたメソッドを呼ぼうとすると、dieさせてしまうのだ。
ただ、Javaにはこのやり方は妥当とは言いがたい。なぜなら、この問題は本来コンパイル時にエラーを発見するべきものであるからだ。それを、実行時にそのメソッドが呼ばれるまで発見できないのは痛い。
だが、言語仕様上、避けられないことではあるようだ。
さて
これに対していろいろと聞いてみたところ。
話していた相手であるところの友人からの解答はhttp://www.komaba.utmc.or.jp/~plaster/diary/?050113とのことだった。
また、とある関係の先輩に当たる方に聞いてみたところ
- 詳しい話は忘れたがEckelの本に載っている
- RuntimeExceptionはいいんだけれど、deprecatedを使うとコンパイル時に警告が出る
- あと、Javaの場合にはdocumentに書くという方法で対処するのが一般的
- C++と同じようなtemplateがjava1.5から出たけど、まだ知らない
という返答が。
別に唯一の解答が欲しいというものではないので、とりあえずこのままで放っておく。