ええと、入門者向けテストにありがちなクイズだと思いますが、以下のJavaコードがあった時、aが増えないステートメントはA~Fのどれでしょうか。
public class IncTest {
static int a;
static int b;
public static void main(String[] args) {
a++; //A
a=a++; //B
a=(a+=1); //C
a=a+1; //D
a=++a; //E
b=a++; //F
System.out.println(a);
System.out.println(b);
}
}
正解はBですね。Javaの仕様では、上記のうちBの文a=a++;のみ、aのインクリメントが意味を持ちません。後置インクリメントa++は代入操作の後に行われることになっていますが、代入先もaなので、インクリメント前の値でアサインが完了してしまい、文の判定としてはインクリメント前のaに戻される(もともとのaが残る)ことになります。上記の実行結果は以下。
5
4
ただですね、ちょっと似たコードをeclipseで書いたとき、その警告文で少し戸惑ったんですよ。eclipseバージョンは2022-6(4.24.0)です。下記キャプチャのように、"assignment to variable a has no effect"の警告が、意味のないはずの文B(a=a++;)ではなく、E(a=++a;)の文の方に出てきたからです。
先に言っておくと、これは正しい仕様です。それでも、「え、no effectなのはE(a=++a;)ではなくてB(a=a++;)の方なのでは・・・」と気になって確認したので、メモとして記事を残しておきます。
まず、この警告メッセージ、「変数への代入(アサインメント)の効果が無い」と言っているのであって、その文(ステートメント)に効果が無いと言っているわけではありません。代入に意味があるか、それ無しで結果が同じかがポイントなわけです。Eのa=++a;は、++a;と同じように1増える。つまり「a=」は無意味なので警告が出る。逆に、Bのa=a++;は、a++;と同じではない。文は結果としてaを変えないが、代入にはインクリメントを無効化する効果がある。なので、Bは警告が出ないのが正しい。・・・いや、考えたらその通りなんですけどね。詳しい方は「当たり前だろ」と思ってるかもしれません。
いちおう、eclipseの気持ちを確認するために、以下のように書き換えてみました。
public class IncTest {
static int a;
static int b;
static volatile int c;
public int d;
IncTest(int d) {
a++; //A
a=a++; //B
a=(a+=1); //C
a=a+1; //D
a=++a; //E
b=a++; //F
c=++c; //G
d=d; //H
this.d=d; //I
}
}
eclipseの言いたいことは、volatile宣言した変数cを見ると、更に明確になります。上記のうち、G(c=++c;)はE(a=++a;)と同じ前置インクリメント後の代入なのに警告が出ません。代入操作(c=と書くこと)に、コンパイラにとってのキャッシュ制御上の意味がある(かもしれない)からです。「=」が無駄なら警告が出るし、何らかのエフェクトがあれば出ないという基準が再確認できます。なお、このvolatile宣言時の規定については、2010年頃にeclipse仕様が修正される前の掲示板のやりとり(BUG 310264)を確認することができました。
また、上記コード、mainメソッドではなく引数:int dのコンストラクタに書き換えているのですが、H(d=d;)とI(this.d=d;)についてはどうでしょうか。当然のように、Hには警告が出てくれました。恐らくですが、この警告のメインターゲットは、このthis.d=d;をd=d;と誤って書いてしまうケースなのではないでしょうか。割と起こりえるミスなので、やはり、この警告の存在自体は助かります。逆に、私が勝手に混乱していたB(a=a++;)とE(a=++a;)のようなコードは、冒頭のクイズ問題のような例外的ケースでしか書くことがないでしょう。
今回は、少し検索すれば出てくるような内容なので、ちょっとネタとしては微妙だったかもしれません。ただ、volatileの話と警告の意義(this抜け防止)を含めて書くと意味があるかもと思ったので・・・。以上、覚書きでした。