プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

いつかこうなったらいいなぁ…

コメントを残してきて、ふと思ったのでメモ。
Javaにはオートボクシングという機能があります。
私的にはJavaの失敗の一つだと思っていますけど。
オートボクシングは非常に中途半端なのです。


私はプリミティブか、ラッパークラスのどちらかを無くすべきだと思っています。(それを実際にやったのがScala)
無くすとまでは行かなくとも(無くすと困るところがある)、少なくともプリミティブをオブジェクトと同じように扱えるべきだ。


ボクシングでは

Integer i = 10;
Integer i = Integer.valueOf(10);

この2行は等価です。


逆にアンボクシングは

int i = new Integer(10);
int i = new Integer(10).intValue();

この二行が等価になります。


これらのオートボクシングはプリミティブ型とそのラップクラスの境界を曖昧にしたけど、曖昧にしただけで依然としてしっかりその差が残ってる。

たとえば、Integer型の変数iがあったとき

int t = i;


が成立するかどうかは分からないわけだ。なぜならiがnullの可能性があるから。
これがiがint型なら失敗すると言うことは起こりえない。


まぁ、これは我慢できる。仕方がない。nullの時は0であるとかする事も可能だが、nullという情報を揉み消すのは少々横暴だ。(ただ、浮動小数ならNaNで良い気もする。)だが、以下から述べるオートボクシングの中途半端なところはいかがな物だろうか。


int型変数a,bとInteger型変数c,dがあったとき

a==b //aの値とbの値で一意に決まる
a==c //aの値とcの表す値で一意に決まる
c==d //cとdの表す値で決まらない (cのオブジェクトとdのオブジェクトが同じ時にtrueそれ以外はfalse)

非常に中途半端。
なぜ、c==dがオブジェクト同士の比較なのか。
オートボクシングで境界を曖昧にしたなら、Integer型の==比較はc.intValueとd.intValueで行うべきである。そして、オブジェクトとしての比較はequalにすることにしてしまえば、==は数値としての比較、.equalはオブジェクトとしての比較を表しているんだな、と非常にわかりやすい。
実行の後方互換を維持したいという場合でも*1、別に既存のクラスに変更を加えなくてもコンパイラ側で、==をequalに、equalを==に変換すればいいだけだから、問題は全然無いわけだ。


もちろん、Integer型にはnullの場合があるので==では起こりえなかったぬるぽの危険性が発生するが、その辺は、先にnull判定が入るとか何とか適当に言語仕様で決められるでしょう。




さらに、中途半端なのは、プリミティブ型から関数が呼べない点。
特にInteger.bitCount等のstaticな関数がint型から呼べるべきだ。

int count = 10.bitCount();
int count = Integer.bitCount(10);

この2行が等価であるべきだと私は考える。
理由は、上は「(10という)インスタンスの状態で関数を呼んだ結果がcount」と読める一方で、下は「関数に引数10を作用させた結果がcount」としか読めない。
あきらかに上の記述の方がオブジェクト指向にあっていると思われる。


特に問題が見当たらないし、後方互換とも関係ないと思うのだが、何故組み込まれないのか謎である。



一方で、Integer型のメンバー関数も呼べるようにするべきかどうかは若干微妙な線である。
一つに、呼べることに対するうま味があまりないと言うこともある。
だがそれよりも、wait等のObjectに定義されているfinal関数の扱いである。
メンバー関数を呼ぶには当然、Integer型のインスタンスにボクシングする必要がある。このとき、

final int i=10000;
i.wait();
i.notifyAll();

同期化は省いたがこれが通ることになる。だが、残念なことに上のnotifyAllは空振りする。ボクシングで生成されるインスタンスが異なるからだ。

単純にwaitやnotifyAllは呼び出し禁止にすればいいようでもあるが、i.getClassで返ってくるのはInteger型のClassになるはずなのに、waitやnotifyAllが使えないというのも何か妙な話な気もする。*2まぁ、実際にやるなら禁止するのが一番だろう。






ここまでして、初めてオートボクシングって意味ある物になると思うんだけどなぁ〜


最後にジェネリックで<プリミティブ>が許可できたらなぁ〜、と思うんだけど、実際これは無理だよなぁ。C++のテンプレート見たく、新たなプリミティブ用のクラスを作成するのは現実的じゃない。関数はインターフェースで宣言できてもpublicメンバー変数がある場合、そいつらはどうするのよ、って事になる。あと後方互換も崩れそう。



ま〜、文句言わずにScalaやれって事だけどさ。



あと、bitCountと同じ論調で、

int i = Integer.parseInt(string);
int i = string.parseInt();

が等価になるべきじゃないでしょうか?この設計には何か理由があると思うんだけど、どうしてだ?

*1:コンパイラでも後方互換を重要視するJAVAでは出来ない、という言い分は分からないでもないが…(いい加減、コンパイラぐらい後方互換切り捨てればいいのに。)

*2:この辺の同期とかの話はScalaだったらばっさり切られてる。実際それでもいいと思う。Integerでwaitした事なんて無いし。