3:staticコンテキストから参照することはできません



 私がJavaプログラミングをしていて、よく引っかかっていたコンパイルエラーの一つに


static でない 変数 x を static コンテキストから
参照することはできません。
System.out.println(test2.x);
^
というものがあった。この解決法が良く分からなかった。そもそも「staticコンテキスト」というものが良く分からない。解説書にも載っていない。


訳の分からん用語をしたり顔で使うなっ!
貴様は日本語で表現出来ない大蔵官僚かッ!?


 staticは「静的な」という意味で、クラス変数やクラスメソッドに付ける修飾子だというのは知っているのだが、だから何なのだ?という状況だった。このエラーは下の実験ソースで発生したもので、「inst2はインスタンスだが、test2はクラスの名前だからクラス変数だろ!このダボハゼがッ(怒)!」と毒づいていたものだ。(ようするに良く分かっていなかったのだ。)




class test1{
public static void main(String[] args){
System.out.println(test2.x);//クラス変数xを読んで出力
test2 inst2 = new test2(); //インスタンス生成

System.out.println(inst2.x);//インスタンス変数xを読んで出力
inst2.x = test2.x;
//インスタンス変数xにクラス変数xを代入
System.out.println(inst2.x);//インスタンス変数xを読んで出力
System.out.println(test2.x);//クラス変数xを読んで出力

inst2.changeX();
//インスタンスのクラスメソッドを実行
System.out.println(test2.x);//クラス変数xを読んで出力
inst2.changeX2();
//インスタンスメソッドを実行
System.out.println(test2.x);//クラス変数xを読んで出力
test2.changeX();
//クラスの   クラスメソッドを実行
System.out.println(test2.x);//クラス変数xを読んで出力
}
}

class test2{
int x = 0; //test2クラスの変数x
public static void main(String[] args){
x = 1;
changeX();
}
static void changeX(){
x = 2;
System.out.println(x);
// x = inst2.x;//「シンボルを見つけられません」
//というコンパイルエラーが発生する。

}
test2(){ //コンストラク
x = 3;
}
}

 仕方が無いので変数をstaticと宣言するか、さもなければこの命令が属するメソッドをstaticにしてしまう(ここでは無理だが)などして無理やりstatic性を合わせていたが、当然そんなその場しのぎでは正常に作動しない場合が生まれる。
なんでじゃぁぁぁ!?


 動かないのも当然なのだが。


 なお、正解は


class test2{
static int x = 0; //クラス変数x
である。
 実行時出力は

0
3
3
3
2
4
2




 そもそも、

  • なぜmainメソッドはstaticがつかねばならないのか?

の理由も分かっていなかった。

  • なぜインスタンス化されるクラスにはmainメソッドが無いサンプルがほとんどなのか?
  • なぜインスタンスにはmainメソッドが無くてもいいのか?

も分かっていないどころか問題として意識すらしていなかった。




 全ての原因は解説書のクラスとインスタンスとメソッドの関係の説明にあったのだ!


 通常、


「クラスは設計図」
「クラスのインスタンスが実体でオブジェクト」
と説明される。
また、
「クラスはインスタンスの型である。」
「しかしクラスだけでも動作させることもできる。」
「クラス変数・クラスメソッドはインスタンス全てにおいて共通で、参照可能。」


と説明がある。(ホントはインスタンスメソッドもインスタンスで共通だと思うが。・・・もちろんローカル変数は別々の値になるけど。)
そして
生成したインスタンス変数・インスタンスメソッドを元のクラス・クラスメソッドから、参照する事は出来ない。
とは説明しない。だが、出来ないのだ。*1
 悲しいことに、かなりの本でこれが説明されていない。
 手元の本をざっと見た所、
「本格学習Java入門:佐々木整」
「逆引き速引きリファレンスJava:ASCII」
「例題で学ぶJava言語:加藤 暢,白川 洋充,溝渕 昭二」
Javaによるプログラミング入門:久野 禎子, 久野 靖」
Javaプログラム クイックリファレンス 第2版:Devid Flanagan」
で説明が無い。


「Javaスタートブック:高田美樹」(P323)にはかろうじて書いてあった。

 クラスメソッドは、メソッド内部で宣言した変数やオブジェクトと「static」のついたグローバル変数(クラス変数)のみ参照することができます。



 クラスとインスタンスの性質を考えたら熟練者にはすぐ分かることなのかもしれないが、初心者には思いもつかない規制である。
 上記のソースでもtest2クラスのchangeX()クラスメソッドでinst2.xを呼び出そうとしているが、プログラムの流れ上インスタンスinst2はちゃんと存在するはずにもかかわらず「見つけられません」である。

以下管理人独り言:
 どうやらクラスメソッドではこれをインスタンス変数ではなくクラス変数としてのみ解釈するらしい。その証拠にinst2クラスを作ってクラス変数x=5を定義したらコンパイルエラーは無く、その値5が表示される。test2クラスのインスタンスinst2の生成も問題なく行われているのが奇妙だが。(さらに奇妙と言えば inst2.x = test2.x; が問題なくコンパイルされてしまうのも面白い。結局同じものを指しているのだからプログラム上は問題ないのだろうが・・・・)
 更に余談だがメインメソッドではどちらの値を参照するのか知りたかったので、念のため全く別のinst3クラス{フィールドでstatic int x = 10;と定義}を作り、test1のメインメソッドに

    System.out.println(inst3.x);
test2 inst3 = new test2();
System.out.println(inst3.x);
を追加したら
上記の結果に続いて
10
3
が表示された。直前にクラス変数を参照していても、同名のインスタンスが生成されるとそっちが優先されるらしい。これをインスタンスによるクラスの隠蔽とでも呼ぼうか。なぜなら、クラスinst3に例えばクラス変数yを定義しておいても、クラスtest2のインスタンスinst3が生成されるとyを参照できなくなる(コンパイルエラー「シンボルを見つけられません」)のだ。隠蔽の解除方法は・・・分からん(笑)。(独り言終わり)
後日追記:
「クラスの隠蔽」には既に他の意味が与えられていた。
=「パッケージ内部の処理でだけ利用するデフォルト・アクセス」にクラスを設定しておけばそうなる。メンバの隠蔽はprivateで指定する。つまり「隠蔽」とはアクセス修飾子によるアクセス制御だ。だから同名インスタンスによってクラスにアクセスできなくなる事は別の用語で呼んだ方がよさそうだ。
 とりあえず”インスタンスによるクラスのオーバーライド”とでも呼んでおくか。



 基本的にクラス変数もインスタンス変数も他のクラス・インスタンスから呼び出すのがJavaの普通のやり方なのでそんなこと知らなくてもいいのかも知れんが、別クラスを作る事を嫌がる初心者もいるのだ!ここに!
 なにしろクラスが異なるとクラス変数・インスタンス変数の情報(例えばエラーとかグラフィックコンテキスト)を一々他のクラスからもらってこないといけない。面倒な上にミスの元だ!と思っていたのだ。(というか情報の持ち運び方を良く知らなかった。)
 二行で済むんだからちゃんと出来ないと書いとけ!!この横着者どもがぁぁぁぁっ!!
てめーらのお陰でどんだけ時間を無駄にしたか分かっとンのかぁっ!!?

*1:正確には、インスタンスを生成した時のインスタンス名を介してならば「出来る」のだが、ここではそれを介さずに直接インスタンスのメンバを参照しようとすることは「出来ない」という話をしています。