Chapter 13

クラスの要点(重要なポイント)

KeitoYuasa
KeitoYuasa
2023.02.19に更新

クラス内で定義したメンバの読み込み

ページ末尾で具体例を用いて要点を説明している。

同じクラスで定義したメンバ間のアクセスには、以下のルールが存在する。
まずは、クラスの基本としてこれらを理解することが重要。

No. ルール
1 クラス内で定義したインスタンスメンバ同士、staticメンバ同士はそれぞれ直セスアクセスできる。
2 クラス内で定義したインスタンスメンバは、クラス内で定義したstaticメンバに直接アクセスできる。
3 クラス内で定義したstaticメンバは、クラス内で定義したインスタンスメンバに直接アクセスできない。アクセスする際は、インスタンス化する必要がある。

※staticメンバは、直接アクセスできる場合は、「クラス.staticメンバ」でも呼び出すことができる。(ただしこれは、外部クラスで定義したメンバを呼び出す際に使用するのが一般的。)

以下では、同じクラスで定義したメンバ間のアクセスについて示す。
staticメンバからインスタンスメンバに直接アクセスはできない。インスタンス化することでアクセス可能になる。しかし、staticメソッドのローカル変数としてインスタンス化する必要がある。(インスタンスメンバになるため)

Training.java
class Test{
    String instanceVal = "instance変数";
    static String staticVal = "static変数";

    void instanceMethod(){System.out.println(instanceVal);}
    static void staticMethod(){System.out.println(staticVal);}
    
    void instanceMethodB(){System.out.println(staticVal);}
    //static void staticMethodB(){System.out.println(instanceVal);} コンパイルエラー
    
    //Test t = new Test(); ここに定義したらtもインスタンスメンバ
    static void staticMethodB(){
        Test t = new Test(); //ローカル引数として定義しないとNG
        System.out.println(t.instanceVal);
    }

}

また、main()メソッドもstaticメンバである。
そのため、同じクラス内のインスタンスメンバには直接アクセスできない。
staticメンバ同士の直接アクセスは可能。

Training.java
class Training {
    String instanceVal = "instance変数";
    static String staticVal = "static変数";

    void instanceMethod(){System.out.println(instanceVal);}
    static void staticMethod(){System.out.println(staticVal);}
    
    public static void main(String[] args){
        //インスタンスメンバへのアクセス
        Training t = new Training();
        System.out.println(t.instanceVal);
        //System.out.println(instanceVal); コンパイルエラー

        t.instanceMethod();
        //instanceMethod(); コンパイルエラー
        
        //staticメンバへのアクセス
        System.out.println(staticVal);
        System.out.println(Training.staticVal);
        System.out.println(t.staticVal);
        
        staticMethod();
        Training.staticMethod();
        t.staticMethod();
    }
}
//出力結果:
//instance変数
//instance変数
//static変数
//static変数
//static変数
//static変数
//static変数
//static変数

重要なのが、生成されるオブジェクトのメモリ領域。
クラス内で定義したメンバを呼び出す際は、以下のことを意識する。

メンバ 要点
インスタンスメンバ インスタンス単位でメモリ領域を生成する。つまり、同じインスタンスならインスタンスメンバを共有できるので、同クラスのインスタンスメンバ同士は直接アクセスが可能。また、staticメンバとは違う領域にインスタンスを生成するので、staticメンバ側からはインスタンス化して呼び出す必要がある。(クラスが同じでも生成されるインスタンスはそれぞれ異なるため)
staticメンバ クラス単位でメモリ領域を生成する。つまり、クラスが同じならstaicメンバを共有できるので、同クラスのstaticメンバ同士、そのクラスをもとに生成したインスタンスも直接アクセスが可能。

※staticメンバはクラスが呼び出される時(クラスロード時)に、同じメモリ領域(static領域)上に生成されるメンバで、クラスに1つだけ生成されます。
そのため、クラス単位でstaticメンバが管理される。
つまり、クラス内で定義したstaticメンバを使用する場合は、領域が同じなのでクラス名とセットでなくてもJVMが識別できる。
外部クラスのstaticメンバを利用する際は、領域が異なるのでクラス名をセットにして識別する必要がある。

別クラスで定義したメンバの読み込み

別クラスのメンバにを呼び出す際は簡単。主に以下の2点だけを意識すればいい。

メンバ 要点
インスタンスメンバ 必ず、インスタンス化して呼び出す。
staticメンバ インスタンス化してもいいが「クラス.staticメンバ」によって呼び出す。

以下では、別クラスで定義したメンバを読み込んでいる。

Training.java
class Test{
    String instanceVal = "instance変数";
    static String staticVal = "static変数";

    void instanceMethod(){System.out.println(instanceVal);}
    static void staticMethod(){System.out.println(staticVal);}
    
    void instanceMethodB(){System.out.println(staticVal);}
    //static void staticMethodB(){System.out.println(instanceVal);} コンパイルエラー

}

class Training {
    public static void main(String[] args){
        
        // インスタンスメンバ
        Test t = new Test();
        System.out.println(t.instanceVal);
        t.instanceMethod();
        t.instanceMethodB();

        // staticメンバ
        System.out.println(Test.staticVal);
        Test.staticMethod();
        
        // staticメンバの場合、以下でも問題ない
        // System.out.println(t.staticVal);
        // t.staticMethod();
    }
}

//出力結果:
//instance変数
//instance変数
//static変数
//static変数
//static変数

補足(クラス内のメンバに直接アクセス)

上記では、自分(クラス)のメンバをコールする(=直接コール)する際にルールがあることを説明した。

なぜこのルールがあるのか、一言で言えば 「クラスをもとにオブジェクトを生成した時、メンバの生成領域が異なるため」

なぜ混乱するのか

また、なぜここで混乱するのか、それは 「クラス定義の話をインスタンス化する前提で説明しているため」
そもそもクラスは設計書で、インスタンスを作らないと実体はない(何もない)ため、このような説明になる。
混乱するポイントは、同じクラスのインスタンスメンバ同士の直接コールがokな点。staticメンバからインスタンスメンバを直接コールできない点ではないだろうか。

要点

要点は、2つ。「メンバが生成されるタイミング」、「メンバの生成領域の違い」である。
順に説明していく。

①メンバが生成されるタイミング

メンバ 生成タイミング 生成される数
staticメンバ クラスがコールされた時。 1クラス1つ。
インスタンスメンバ コールしたクラスをもとにインスタンスを生成した時。 1インスタンス1つ(インスタンスごとに1つずつ)。

具体例を用いて生成タイミングを説明する。
以下のコードを2つに分解して見ていく。

Cat mike = new Cat(); //以下の2つに分解する
分解後
Cat mike; //Catクラスのstaticメンバ生成(クラスコール)
mike = new Cat(); //Catクラスを元にしたインスタンスのインスタンスメンバ生成(インスタンス生成)

staticメンバは、クラスコールでできるもの。インスタンスメンバは、インスタンスを生成したときにできるもの。
クラスコールしただけでは、インスタンスメンバの実体はない。

②メンバの生成領域の違い

①で用いたコードをもとに、生成される領域の違いを以下で描いてみた。
staticメンバは、static領域上に生成される。
インスタンスメンバは、ヒープ領域上に生成される。

つまり、クラスが同じ場合、全インスタンスに対してそのクラスのstaticメンバを共有する。

逆に、インタンスメンバはインスタンスごとに固有のものになる。
つまり、staticメンバからインスタンスメンバをコールする際は、同じクラス内のものでもどのインスタンスのインスタンスメンバなのかをインスタンス化して知らせる必要がある。(直接コールできない)

具体例