今更ここを更新するのか、という話なのですが、まあちょっとした小ネタで埋めておこう。
某サイトに以下のような話が書いてありました。
Integer i = 123;
i++;
とやるとこれは
Integer i = 123;
int temp = i.intValue();
temp++;
i = Integer.valueOf(temp);
することになるんだ、無駄だ、という話。Integer#increment()
とかInteger#decrement()
とかないんかい、と思ったんだけど、そもそもInteger
、immutableですしね…。で、これもうちょっと続きがあった。
C言語のいい意味でいい加減なところなんですが、i++
だと式の返す値は加算される前のi
の値になる。++i
だと式の返す値は加算された後の値になる、という規則があります。さて、これにboxing/unboxingが絡む。ほら、面倒くさそうでしょ。
javapしてみました。
i++;
は、こうなる。
astore_1
aload_1
astore_2
aload_1
invokevirtual Integer#intValue()
iconst_1
iadd
invokestatic Integer#valueOf()
astore_1
aload_2
ほら面倒だ。[1]
に保存、[1]
から取り出す、[2]
に保存、[1]
から取り出す、intValue()
呼び出す、定数1
を加算、valueOf()
呼び出す、[1]
に保存、[2]
から取り出す。
つまりi
の値を演算用と最後に返す用の二箇所に保存して、演算用はintValue()
→1
を足す→valueOf()
を実行して結果を保存したあと、返すように置いといた値を取り出して式の値として返す。
これが
++i;
だったら
aload_1
invokevirtual Integer#intValue()
iconst_1
iadd
invokestatic Integer#valueOf()
astore_1
で、bytecodeにして4ステップも違うわけです。まあとはいえこの程度ならJITコンパイラが賢く処理してくれるんじゃないのという気もしなくもないんですが…ねえ。
そもそもautoboxingを多用するべきではない(だからC#嫌いなんだよな)のだけど、最悪使わないといけないときは++i
を使いましょう、という話…でもないな。やっぱりwrapper classは気をつけて使わないとね。
そもそも++
/–-
は単独の変数にしか使えないし、この式の返す値を右辺に持ってきてなにかに代入するというコードをあまり見たことがない。forループの第3式にこれを書くのが一番多い利用例だと思うけど、明らかに戻り値は利用しないわけだし。いやwrapper classをループカウンターに使うのはよしたほうがいいけどね…whileで書いてて成り行きでとかはありそうですね。
あと、気がついてしまったのだけど、この演算子、関数の形で書けない。Cだとprimitiveもポインタ渡しで値を戻せるので
#include <stdio.h>
int prePlusPlus(int* i) {
*i = *i + 1;
return *i;
}
int postPlusPlus(int* i) {
int j = *i;
*i = *i + 1;
return j;
}
int main(void) {
int i = 0;
printf("%d %d %d\n", i, prePlusPlus(&i), i);
printf("%d %d %d\n", i, postPlusPlus(&i), i);
}
と書けるのだけど、prePlusPlus()
はこのままでJavaにできると思いますが、postPlusPlus()
はJavaでは書けない気がするんだな…。
wrapperのwrapperを書けばできる…けどこれは面倒くさいな。
public class PlusPlus {
public Integer prePlusPlus(IntWrapper i) {
Integer s = Integer.valueOf(i.add(1));
return s;
}
public Integer postPlusPlus(IntWrapper i) {
Integer s = i.integerValue();
i.add(1);
return s;
}
private class IntWrapper {
Integer i = null;
public IntWrapper(int i) {
this.i = Integer.valueOf(i);
}
public IntWrapper(Integer i) {
this.i = i;
}
public int intValue() {
return this.i.intValue();
}
public Integer integerValue() {
return this.i;
}
public int add(int i) {
this.i = Integer.valueOf(this.i.intValue() + i);
return this.i.intValue();
}
}
public static void main(String[] args) {
PlusPlus plusplus = new PlusPlus();
plusplus.go();
}
public void go() {
Integer i = Integer.valueOf(0);
IntWrapper iWrap = new IntWrapper(i);
System.out.printf("%d\n",iWrap.intValue());
System.out.printf("%d\n",prePlusPlus(iWrap));
System.out.printf("%d\n",iWrap.intValue());
System.out.printf("%d\n",iWrap.intValue());
System.out.printf("%d\n",postPlusPlus(iWrap));
System.out.printf("%d\n",iWrap.intValue());
}
}
まあprimitiveにはprimitiveの長所短所があって…という話でした。