概要
JavaでもTupleって使いたくね?
Tupleの実装
Javaでコーディングする際にふと遭遇する下記のような出来事。
- メソッドから一度に二つ以上のオブジェクトを返したい
- Mapのキーに複数のオブジェクトの組を使いたい
- ループカウンタに複数オブジェクトの組が使いたい
そんな場合、他の言語ではTupleを使う。Javaではどうするか… new Object[] とかやって、そこに全部つめこむ??
そんな型安全を放棄する危ない方法を使わず、下記のようなコードを書いてTupleもどきを作ろう。型安全の防御ネットがコンパイル時にあるだけで、Objectの配列なんかを使うよりバグから君の身を守ってくれる。
下記で示したTuple は複数のオブジェクトを単に組にして要素として保持するだけの単純なクラスであり、要素へのアクセスは以下のように行なう。
- 先頭(A)は tuple.car
- 2番目(B)は tuple.cdr.car
- 3番目(C)は tuple.cdr.cdr.car
上記はLispに慣れ親しんでいればリストへのアクセスと同一なのですぐに理解できるだろう。また全ての要素が同値性を持つときにだけ Tuple 同士が同値になるように設計されている。これはMapのキーにTupleを使う際に大事な性質だ。さらにここでは Immutable として設計されているが、これは多分に私の好みを反映していると言えるかもしれない。
長年に渡って個人的に使用している技なのだが、あまり回りに浸透していないようなので共有する。特に先に上げたMapのキーに複数オブジェクトを使うケース…キーとなる複数オブジェクトをtoString()してつなげて使用しているコードなど見かける気がするが、効率悪い上にあまりに危険なので今後はこうしよう。
public class TupleUtil {
public static class Pair<A, B> {
public final A car;
public final B cdr;
public Pair(A car_, B cdr_) {car = car_; cdr = cdr_;}
private static boolean eq(Object o1, Object o2) {return o1 == null ? o2 == null : o1.equals(o2);}
private static int hc(Object o) {return o == null ? 0 : o.hashCode();}
@Override public boolean equals(Object o) {
if (! (o instanceof Pair)) return false;
Pair<?, ?> rhs = (Pair<?, ?>) o;
return eq(car, rhs.car) && eq(cdr, rhs.cdr);
}
@Override public int hashCode() {return hc(car) ^ hc(cdr);}
}
public static class Tuple1<A> extends Pair<A, Object> {
public Tuple1(A a) {super(a, null);}
}
public static class Tuple2<A, B> extends Pair<A, Tuple1<B>> {
public Tuple2(A a, B b) {super(a, new Tuple1<>(b));}
}
public static class Tuple3<A, B, C> extends Pair<A, Tuple2<B, C>> {
public Tuple3(A a, B b, C c) {super(a, new Tuple2<>(b, c));}
}
public static class Tuple4<A, B, C, D> extends Pair<A, Tuple3<B, C, D>> {
public Tuple4(A a, B b, C c, D d) {super(a, new Tuple3<>(b, c, d));}
}
public static class Tuple5<A, B, C, D, E> extends Pair<A, Tuple4<B, C, D, E>> {
public Tuple5(A a, B b, C c, D d, E e) {super(a, new Tuple4<>(b, c, d, e));}
}
public static void main(String[] args) {
Tuple4<Integer, String, Double, String> tpl4_1 = new Tuple4<>(42, "foo", 3.14, "baa");
System.out.println(tpl4_1.car); // 42 と表示
System.out.println(tpl4_1.cdr.car); // foo と表示
System.out.println(tpl4_1.cdr.cdr.car); // 3.14 と表示
System.out.println(tpl4_1.cdr.cdr.cdr.car); // baa と表示
System.out.println();
Tuple4<Integer, String, Double, String> tpl4_2 = new Tuple4<>(42, "foo", 3.14, "foo");
Tuple4<Integer, String, String, String> tpl4_3 = new Tuple4<>(42, "foo", "hoge", "baa");
Tuple4<Integer, String, Double, String> tpl4_4 = new Tuple4<>(42, "foo", 3.14, "baa");
System.out.println(tpl4_1.equals(tpl4_2) + " " + tpl4_1.hashCode() + " " + tpl4_2.hashCode()); // false 二つのハッシュコードは異なる
System.out.println(tpl4_1.equals(tpl4_3) + " " + tpl4_1.hashCode() + " " + tpl4_3.hashCode()); // false 二つのハッシュコードは異なる
System.out.println(tpl4_1.equals(tpl4_4) + " " + tpl4_1.hashCode() + " " + tpl4_4.hashCode()); // true 二つのハッシュコードは等しい
}
}