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とのことだった。

また、とある関係の先輩に当たる方に聞いてみたところ

  1. 詳しい話は忘れたがEckelの本に載っている
  2. RuntimeExceptionはいいんだけれど、deprecatedを使うとコンパイル時に警告が出る
  3. あと、Javaの場合にはdocumentに書くという方法で対処するのが一般的
  4. C++と同じようなtemplateがjava1.5から出たけど、まだ知らない

という返答が。

別に唯一の解答が欲しいというものではないので、とりあえずこのままで放っておく。