JavaRush /Java Blog /Random-JA /Java での代入と初期化
Viacheslav
レベル 3

Java での代入と初期化

Random-JA グループに公開済み

導入

コンピュータ プログラムの主な目的はデータ処理です​​。データを処理するには、何らかの方法でデータを保存する必要があります。データがどのように保存されるかを理解することを提案します。
Java での代入と初期化 - 1

変数

変数は、あらゆるデータを保存するコンテナーです。Oracle の公式チュートリアル「メンバー変数の宣言」を見てみましょう。このチュートリアルによると、変数にはいくつかの種類があります。
  • フィールド: クラス内で宣言された変数。
  • ローカル変数: メソッドまたはコード ブロック内の変数。
  • パラメータ: メソッド宣言 (シグネチャ内) 内の変数。
すべての変数には変数タイプと変数名が必要です。
  • 変数のタイプは、その変数がどのようなデータを表すか (つまり、どのようなデータを格納できるか) を示します。ご存知のとおり、変数の型はプリミティブ(primitives primitives ) または非プリミティブ (Non-primitive)のオブジェクトになります。オブジェクト変数の場合、その型は特定のクラスによって記述されます。
  • 変数名はキャメルケースで小文字にする必要があります。名前付けの詳細については、「変数:名前付け」を参照してください。
また、クラスレベル変数の場合、つまり がクラスフィールドである場合、アクセス修飾子を指定できます。詳細については、「クラスのメンバーへのアクセスの制御」を参照してください。

変数の宣言

そこで、変数とは何かを思い出します。変数の操作を開始するには、変数を宣言する必要があります。まず、ローカル変数を見てみましょう。便宜上、IDE の代わりに、チュートリアルポイントのオンライン ソリューションであるオンラインIDEを使用します。オンライン IDE でこの簡単なプログラムを実行してみましょう。
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
ご覧のとおり、 namenumberと typeのローカル変数を宣言しましたint。「実行」ボタンを押すと、次のエラーが表示されます。
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
どうしたの?変数を宣言しましたが、その値を初期化しませんでした。このエラーは実行時 (つまり、ランタイムではなく) ではなく、コンパイル時に発生したことに注意してください。スマート コンパイラは、ローカル変数にアクセスする前に、ローカル変数が初期化されるかどうかをチェックしました。したがって、ここから次のような記述が得られます。
  • ローカル変数は、初期化された後にのみアクセスしてください。
  • ローカル変数にはデフォルト値がありません。
  • ローカル変数の値はコンパイル時にチェックされます。
したがって、変数を初期化する必要があると言われます。変数の初期化とは、変数に値を代入することです。それでは、それが何なのか、そしてその理由を考えてみましょう。

ローカル変数の初期化

変数の初期化は、Java で最も注意が必要なトピックの 1 つです。これは、メモリ、JVM 実装、JVM 仕様、およびその他の同様に怖くて注意が必要なことの操作と非常に密接に関係しています。しかし、少なくともある程度は理解しようとすることはできます。単純なものから複雑なものへ進みましょう。変数を初期化するには、代入演算子を使用し、前のコードの行を変更します。
int number = 2;
このオプションではエラーは発生せず、値が画面に表示されます。この場合はどうなるのでしょうか? 推論してみましょう。変数に値を割り当てたい場合は、その変数に値を格納する必要があります。値はどこかに保存する必要があることがわかりましたが、どこに保存するのでしょうか? ディスク上ですか?ただし、これは非常に時間がかかるため、制限が課される可能性があります。「今ここ」でデータを迅速かつ効率的に保存できる唯一の場所はメモリであることがわかりました。これは、メモリ内にある程度のスペースを割り当てる必要があることを意味します。これは本当です。変数が初期化されると、プログラムが実行される Java プロセスに割り当てられたメモリ内にその変数用のスペースが割り当てられます。Java プロセスに割り当てられるメモリは、いくつかの領域またはゾーンに分割されます。どちらがスペースを割り当てるかは、変数が宣言された型によって異なります。 メモリは、ヒープ、スタック、非ヒープのセクションに分かれています。スタックメモリから始めましょう。Stack はスタック (たとえば、本の山) と訳されます。これは LIFO (後入れ先出し) データ構造です。つまり、本の山のようなものです。本を追加するときは一番上に置き、取り除くときは一番上のもの (つまり、最後に追加したもの) を取ります。そこで、プログラムを起動します。ご存知のとおり、Java プログラムは JVM、つまり Java 仮想マシンによって実行されます。JVM は、プログラムの実行をどこから開始するかを認識する必要があります。これを行うには、「エントリ ポイント」と呼ばれる main メソッドを宣言します。JVM で実行するために、メイン スレッド (Thread) が作成されます。スレッドが作成されると、メモリ内に独自のスタックが割り当てられます。このスタックはフレームで構成されます。新しいメソッドがスレッド内で実行されると、新しいフレームがそのメソッドに割り当てられ、スタックの一番上に追加されます (書籍の山の中の新しい本と同様)。このフレームには、オブジェクトとプリミティブ型への参照が含まれます。はい、はい、int はスタックに保存されます。なぜなら... int はプリミティブ型です。フレームを割り当てる前に、JVM はそこに何を保存するかを理解する必要があります。「変数が初期化されていない可能性があります」というエラーが表示されるのはこのためです。変数が初期化されていないと、JVM がスタックを準備できないためです。したがって、プログラムをコンパイルするときに、スマート コンパイラーは、間違いを犯したり、すべてを壊したりすることを避けるのに役立ちます。 (!)わかりやすくするために、超一流の記事「Java スタックとヒープ: Java メモリ割り当てチュートリアル」をお勧めします。同様に素晴らしいビデオへのリンクもあります。
メソッドの実行が完了すると、これらのメソッドに割り当てられたフレームはスレッドのスタックから削除され、すべてのデータを含むこのフレームに割り当てられたメモリもクリアされます。

ローカルオブジェクト変数の初期化

コードをもう少しトリッキーなものに再度変更してみましょう。
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
ここで何が起こるでしょうか?それについてはまた話しましょう。JVM はプログラムをどこから実行すべきかを知っています。つまり、彼女はメインのメソッドを確認しました。スレッドを作成し、それにメモリを割り当てます (結局のところ、スレッドは実行に必要なデータをどこかに保存する必要があります)。このスレッドでは、main メソッドにフレームが割り当てられます。次に、HelloWorld オブジェクトを作成します。このオブジェクトはスタック上ではなくヒープ上に作成されます。object はプリミティブ型ではなくオブジェクト型であるためです。そして、スタックはヒープ内のオブジェクトへの参照のみを保存します (何らかの方法でこのオブジェクトにアクセスする必要があります)。次に、main メソッドのスタックに、println メソッドを実行するためのフレームが割り当てられます。main メソッドを実行すると、すべてのフレームが破棄されます。フレームが破壊されるとすべてのデータが破壊されます。オブジェクト object はすぐには破棄されません。まず、そのオブジェクトへの参照が破棄されるため、誰もオブジェクト object を参照できなくなり、メモリ内のこのオブジェクトにアクセスできなくなります。スマート JVM には、このための独自のメカニズム、ガベージ コレクター (ガベージ コレクターまたは略して GC) があります。次に、誰も参照していないオブジェクトをメモリから削除します。このプロセスについては、上記のリンクで再度説明されています。解説付きの動画もあります。

フィールドを初期化しています

クラスで指定されたフィールドの初期化は、フィールドが静的かどうかに応じて特別な方法で行われます。フィールドにキーワード static がある場合、このフィールドはクラス自体を参照し、単語 static が指定されていない場合、このフィールドはクラスのインスタンスを参照します。これを例で見てみましょう。
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
この例では、フィールドは異なる時点で初期化されます。数値フィールドは、HelloWorld クラス オブジェクトの作成後に初期化されます。ただし、カウント フィールドは、クラスが Java 仮想マシンによってロードされるときに初期化されます。クラスの読み込みについては別のトピックなので、ここでは混ぜません。静的変数は、クラスが実行時に認識されるときに初期化されることを知っておく価値があります。ここでもっと重要なことは他にありますが、これについてはすでにお気づきでしょう。どこにも値を指定しませんでしたが、機能します。本当に。フィールドである変数は、値が指定されていない場合、デフォルト値で初期化されます。数値の場合、これは 0 または浮動小数点数の場合は 0.0 です。ブール値の場合、これは false です。そして、すべてのオブジェクト型変数の値は null になります (これについては後で説明します)。なぜそうなるのでしょう?ただし、オブジェクトは Heap (ヒープ内) に作成されるためです。この領域の作業はランタイムで実行されます。また、実行前にメモリを準備する必要があるスタックとは異なり、これらの変数は実行時に初期化できます。これが Java でのメモリの仕組みです。しかし、ここにはもう一つの特徴があります。この小さな作品は、記憶のさまざまな隅々に触れています。覚えているとおり、フレームは main メソッドのスタック メモリに割り当てられます。このフレームは、オブジェクトへの参照をヒープ メモリに保存します。しかし、カウントはどこに保存されるのでしょうか? 覚えているとおり、この変数は、オブジェクトがヒープ内に作成される前に、すぐに初期化されます。これは本当に難しい質問です。Java 8 より前には、PERMGEN と呼ばれるメモリ領域がありました。Java 8 以降、この領域は METASPACE と呼ばれるように変更されました。基本的に、静的変数はクラス定義の一部です。そのメタデータ。したがって、メタデータ リポジトリ METASPACE に保存されるのは論理的です。MetaSpace は同じ非ヒープ メモリ領域に属しており、その一部です。変数が宣言される順序を考慮することも重要です。たとえば、次のコードにはエラーがあります。
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

ヌルとは何ですか

上で述べたように、オブジェクト型の変数は、クラスのフィールドである場合、デフォルト値に初期化され、そのデフォルト値は null です。しかし、Java における null とは何でしょうか? 最初に覚えておくべきことは、プリミティブ型を null にすることはできないということです。それはすべて、null がどこにも、どのオブジェクトにも参照されない特別な参照だからです。したがって、null にできるのはオブジェクト変数のみです。2 番目に理解することが重要なことは、null は参照であるということです。重さも参考にさせていただきます。このトピックについては、stackoverflow の質問「null 変数にはメモリ内のスペースが必要ですか」を読むことができます。

初期化ブロック

変数の初期化を考えるときに、初期化ブロックを考慮しないのは罪です。次のようになります。
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
出力順序は、静的ブロック、ブロック、コンストラクターになります。見てわかるように、初期化ブロックはコンストラクターの前に実行されます。また、これは初期化の便利な手段になる場合もあります。

結論

この短い概要が、それがどのように機能するのか、そしてその理由について何らかの洞察を提供できれば幸いです。#ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION