静的メンバー変数と非静的メンバー変数の違い
例として次の例を見てみましょう
package cn.galc.test;public class Cat { /** * 静的メンバー変数 */ private static int sid = 0; private String name; this.name = name; } public void info() { System.out.println("私の名前は " + name + ",NO." + id); } public static void main(String[] args) { Cat.sid = 100; 猫のミミ = 新しい猫のピピ = 新しい猫のピピ.info();メモリ解析図を描くことでプログラム全体の実行過程を理解する
プログラムの最初の文 Cat.sid = 100; を実行すると、ここでの sid は静的メンバー変数です。静的変数はデータ領域 (データ セグメント) に格納されるため、最初にデータ領域に小さな領域 sid を割り当てます。文が実行されると、sid には値 100 が含まれます。
この時のメモリ配置図は以下の通りです。
次に、プログラムが実行されます。
猫 mimi = new Cat(“mimi”);
ここでは、Cat クラスのコンストラクター メソッド Cat(String name) を呼び出します。コンストラクター メソッドは次のように定義されています。
Cat (文字列名){ this.name = name;呼び出し時には、まずスタック メモリに小さなメモリ mm を割り当てます。これには、ヒープ メモリ内の Cat クラスのインスタンス オブジェクトのアドレスが含まれます。 mm は、ヒープ メモリ内の Cat クラス オブジェクトの参照オブジェクトです。このコンストラクタは文字列型の仮パラメータ変数を宣言しているため、「mimi」が実パラメータとしてコンストラクタに渡されます。文字列定数がデータ領域に割り当てられて格納されるため、データ領域には余分な小さなメモリが存在します。文字列「mimi」を格納するために使用されます。このときのメモリ分布は次の図に示されています。
コンストラクターを呼び出すときは、最初にスタック メモリ内に仮パラメータ名用の小さな領域を割り当て、次に文字列 "mimi" を実際のパラメータとして name に渡します。この文字列も参照型です。ただし、これら 4 つと 8 つを除きます。基本的なデータ型であり、その他はすべて参照型であるため、文字列もオブジェクトであると考えることができます。したがって、これは「mimi」オブジェクトの参照を name に渡すことと同じなので、name は「mimi」を指すようになります。したがって、この時点のメモリレイアウトは次のようになります。
次に、コンストラクター本体のコードを実行します。
this.name=名前;
ここでは、これは現在のオブジェクトを指し、ヒープ メモリ内の cat を指します。ここで、スタック内の名前に含まれる値は、ヒープメモリ上の cat オブジェクトの name 属性に渡されるため、このとき、名前に含まれる値は、このとき、この名前は文字列オブジェクト「mimi」の参照オブジェクトでもあり、その属性値によってデータ領域にある文字列オブジェクト「mimi」を見つけることができます。このときのメモリ分布は次の図に示されています。
次に、メソッド本体で別のコード行を実行します。
id=sid++;
ここではidにsidの値を渡しているのでidの値は100です。sidを渡したら1を足します。このときsidは101になります。この時のメモリ配置を下図に示します。
この時点でコンストラクタメソッドが呼び出され、このコンストラクタメソッドに割り当てられたローカル変数が占有していたメモリ空間がすべて消滅するため、スタック空間にあった名前メモリも消滅します。このとき、スタックメモリ上のデータ領域の文字列オブジェクト「mimi」への参照も消滅し、ヒープメモリ上の文字列オブジェクト「mimi」への参照のみが残る。この時のメモリ配置は以下の通りです。
次に実行します:
猫のピピ = new Cat("ピピ");ここでは、コンストラクター メソッド Cat() の 2 回目の呼び出しを示します。呼び出しプロセス全体は、呼び出しが完了したときと同じです。この時点のメモリ レイアウトは次の図のようになります。
コードの最後の 2 行は、info() メソッドを呼び出して出力されます。出力結果は次のとおりです。
このプログラムを通じて、この静的メンバー変数 sid の役割を確認することができます。これはカウントできます。新しい猫が出てきたら、番号を付けます。単独で 1 を追加してみましょう。
プログラムが実行されると、メモリ内の全体のレイアウトは上図のようになります。 main メソッドの呼び出しが完了する直前まで続行されます。
ここでは、コンストラクター メソッド Cat(String name) を呼び出して 2 つの猫を作成します。まず、2 つの小さなスペース mimi と pipi がスタック メモリに割り当てられます。これらのスペースには、mimi と pipi が対応するアドレスが含まれます。ヒープ メモリ内のアドレスに 2 つの猫の引用符。ここでの構築方法では文字列型の変数を宣言しており、文字列定数をデータ領域に配置しているため、渡された文字列 mimi と pipi はデータ領域に格納されます。したがって、データ領域には、文字列「mimi」と「pipi」を格納するための 2 つの小さなメモリ ブロックが割り当てられます。文字列は、4 つと 8 つの基本データ型に加えて、参照型でもあります。すべてのデータ型は参照型です。したがって、文字列をオブジェクトとして考えることができます。
ここに 2 つの新しい猫があります。どちらの猫も独自の id 属性と name 属性を持っているため、ここでの id と name は非静的メンバー変数です。つまり、静的な変更はありません。したがって、新しい猫が作成されるたびに、この新しい猫は独自の ID と名前を持ちます。つまり、非静的メンバー変数の ID と名前はオブジェクトごとに個別のコピーを持ちます。ただし、静的メンバー変数の場合、新しいオブジェクトがいくつであっても、静的メンバー変数のコピーは 1 つだけデータ領域に保持されます。ここの sid と同様に、sid はデータ領域に保存されます。ヒープ メモリに新しい cat がいくつあっても、sid のコピーは 1 つだけあり、データ領域にも 1 つのコピーだけが保持されます。
静的メンバー変数はクラス全体に属し、特定のオブジェクトには属しません。では、この静的メンバー変数の値にアクセスするにはどうすればよいでしょうか?まず、どのオブジェクトでもこの静的値にアクセスでき、アクセスするときは同じメモリにアクセスします。 2 番目のポイントは、オブジェクトがなくてもこの静的な値にアクセスできることです。この静的な値には、「クラス名.静的なメンバー変数名」を通じてアクセスできるため、将来的には、特定のクラス名に「.」を加えたものが表示されるようになります。何かが 1 つある場合、次のものは「System.out」のように静的である必要があります。ここでは、この out はクラス名 (System クラス) に「.」を加えたものを通じてアクセスされるため、この out は静的である必要があります。
クラス メンバーが静的として宣言されている場合は、クラスのオブジェクトが作成される前に、オブジェクトを参照することなくアクセスできます。静的メンバーの最も一般的な例は main() です。 main() はプログラムの実行開始時に呼び出す必要があるため、静的として宣言されます。
static として宣言された変数は、本質的にはグローバル変数です。オブジェクトが宣言されると、静的変数のコピーは生成されませんが、クラスのすべてのインスタンス変数は同じ静的変数を共有します。たとえば、静的変数 count を新しいクラス インスタンスの数として宣言します。静的として宣言されたメソッドには次の制限があります。
(1) 他の静的メソッドのみを呼び出すことができます。
(2) 静的データのみにアクセスできます。
(3) いかなる方法でも this または super を参照することはできません。
計算によって静的変数を初期化する必要がある場合は、静的ブロックを宣言できます。静的ブロックは、クラスのロード時に 1 回だけ実行されます。以下の例は、
このクラスには、静的メソッド、いくつかの静的変数、および静的初期化ブロックがあります。 public class UserStatic { static int a = 3; static void meth(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b);初期化されました。"); b = a * 4; } public static void main(String args[]) { meth(42); } } UseStatic クラスがロードされると、すべての静的ステートメントが実行されます。まず、a が 3 に設定され、次に静的ブロックが実行され (メッセージの出力)、最後に b が a*4 または 12 に初期化されます。次に main() が呼び出され、main() が meth() を呼び出し、値 42 を x に渡します。 3 つの println () ステートメントは、2 つの静的変数 a および b とローカル変数 x を参照します。
注: 静的メソッドでインスタンス変数を参照することは違法です。
このプログラムの出力は次のとおりです。
静的ブロックが初期化されました。x = 42 a = 3 b = 12
静的メソッドと変数は、それらが定義されているクラス外のオブジェクトとは独立して使用できます。この場合、クラス名の後にドット (.) 演算子を追加するだけで済みます。たとえば、クラスの外部から静的メソッドを呼び出したい場合は、次の一般的な形式を使用できます。
クラス名.メソッド()
ここで、classname は、静的メソッドが定義されているクラスの名前です。ご覧のとおり、この形式は、オブジェクト参照変数を介して非静的メソッドを呼び出す形式に似ています。静的変数には、クラス名にドット演算子を加えた同じ形式でアクセスできます。これは、Java がグローバル関数とグローバル変数の制御されたバージョンを実装する方法です。
要約:
(1) 静的メンバーは、それが配置されているクラスによって作成されたインスタンスからはアクセスできません。
(2) 静的変更を行わないメンバがオブジェクトメンバの場合、各オブジェクトが所有することになります。
(3) static で変更されたメンバはクラスメンバであり、クラスから直接呼び出すことができ、すべてのオブジェクトに共通です。
Java Static: 修飾子として、変数、メソッド、コード ブロックを変更するために使用できます (ただし、クラスを変更してはなりません)。
(1) 変数を変更します。
クラスのすべてのオブジェクトによって共有されるプロパティ。クラス変数とも呼ばれます。これは C 言語のグローバル変数に似ています。クラス変数はクラスがロードされるときに初期化され、初期化されるのは 1 回だけです。プログラム内のオブジェクトが静的変数を変更すると、他のオブジェクトには変更された値が表示されます。したがって、クラス変数をカウンターとして使用できます。さらに、オブジェクトを必要とせずに、クラス名を使用して Java 静的変数に直接アクセスできます。
(2) 修正方法:
クラスのすべてのオブジェクトに共通の関数を静的メソッドと呼びます。静的メソッドには、オブジェクトを必要とせずに、クラス名を使用して直接アクセスすることもできます。したがって、静的メソッドでは非静的変数および非静的メソッドに直接アクセスすることはできず、静的メソッドでは this や super などのキーワードを使用できません。
(3) Java コード ブロックを変更します。
static を使用して、クラス内の独立したコード ブロック (静的コード ブロックと呼ばれます) を変更します。静的コード ブロックは、クラスが初めてロードされるときに 1 回だけ実行されます。静的コード ブロックには名前がないため、明示的に呼び出すことはできませんが、クラスがロードされるときに仮想マシンによってのみ呼び出されます。これは主に、いくつかの初期化操作を完了するために使用されます。
(4) クラスローディングについて話しましょう:
JVM が初めてクラスを使用するとき、クラスパスで指定されたパスに移動して、そのクラスに対応するバイトコード ファイルを見つけ、それを JVM に読み込んで保存します。このプロセスはクラス ロードと呼ばれます。
変数、メソッド、コード ブロックのいずれであっても、static で変更されている限り、クラスがロードされた時点で「準備完了」、つまり使用できる、または実行済みであることがわかります。すべてオブジェクトなしで実行できます。逆に、静的がない場合は、オブジェクトを通じてアクセスする必要があります。