Thinking In Javaノート(第5章初期化とクリーンアップ(三))


第五章初期化と整理
5.6メンバー初期化
Javaは、すべての変数が使用前に適切に初期化されることを保証します.メソッドのローカル変数の場合、Javaはコンパイルエラーの形式で保証されます.次のようになります.
void f() {
    int i;
    i++; //Error:i not initialized
}

iが初期化されていない可能性があることを示すエラーメッセージが表示されます.コンパイラはiに初期値を与えることができるが、そうはしない.初期化がないのはプログラマーの油断であり、プログラムの安定性を保証するために、プログラマーがプログラムの欠陥を見つけるのに役立つからだ.
ただし、クラスのデータ・メンバーが基本タイプである場合、クラスの各基本タイプのメンバー変数には初期値が保証されます.次のようになります.
public class Test {
    boolean t;
    char c;
    byte b;
    short s;
    int i;
    long l;
    float f;
    double d;
    InitialValues reference;
}

上記の一連の数値はそれぞれfalse, ( ), 0, 0, 0, 0, 0.0, 0.0, nullです.char値は0なので空白と表示されます.
5.6.1初期化の指定
クラスメンバー変数を定義する場所で直接値を付与する方法があり、C++では許可されません.非基本タイプのオブジェクトを初期化することもできます.たとえば、次のAクラスのオブジェクトでは、クラスTestの各オブジェクトが同じ初期値を持つようになります.次のようになります.
class A{
}
public class Test {
    boolean bool = true;
    char ch = 'x';
    int i = 99;
    A a = new A();
}

5.7コンストラクタ初期化
コンストラクタを使用して初期化することもできます.実行時に、メソッドを呼び出したり、いくつかのアクションを実行して初期値を決定したりすることができ、プログラミングに柔軟性をもたらします.しかし、コンストラクタが呼び出される前に発生する自動初期化の進行、すなわち前述したコンパイラの自動付与は組織できないことを覚えておいてください.例:
public class Test { 
    int i;
    Test() {i = 7;} 
}

iの値は、まず0に設定され、7に割り当てられる.
5.7.1初期化順序
クラスの内部では,変数定義の前後順序が初期化の順序を決定する.変数定義の分散とメソッド定義の間でも、コンストラクタを含む任意のメソッドが呼び出される前に初期化されます.例:
class Window {
    Window(int maker) {
        System.out.println("Window(" + maker + ")");
    }
}

class House {
    Window w1 = new Window(1);
    House() {
        System.out.println("House");
        w3 = new Window(33);
    }
    Window w2 = new WIndow(2);
    void f() {
        System.out.println("f()");
    Window w3 = new Window(3);
    }
}

public class Test {
    public static void main(String[] args) {
        House h = new House();
        h.f();
    }   
}

結果はWindow(1) Window(2) Window(3) House() Window(33) f()であった.
5.7.2静的データの初期化
複数のオブジェクトを作成しても、静的データは1つのストレージ領域を占有します.staticキーワードはローカル変数に適用できません.次の例を見てください.
class Bowl {
    Bowl(int marker) {
        System.out.println("Bowl(" + marker + ")");
    }
    void f1(int marker) {
        System.out.println("f1(" + marker + ")");
    }
}
class Table {
    static Bowl bowl1 = new Bowl(1);
    Table() {
        System.out.println("Table()");
        bowl2.f1(1);
    void f2(int marker) {
        System.out.println("f2(" + marker + ")");
    }
    static Bowl bowl2 = new Bowl(2);
    }

class Cupboard {
    Bowl bowl3 = new Bowl(3);
    static Bowl bowl4 = new Bowl(4);
    Cupboard() {
        System.out.println("Cupboard()");
        bowl4.f1(2);
    }
    void f3(int marker) {
        System.out.println("f3(" + marker + ")");
    }
    static Bowl bowl5 = new Bowl(5);
}
}

public class Test {
    public static void main(String[] args){
        System.out.println("Creating new Cupboard() in main");
        new Cupboard();
        Syste.out.println("Creating new Cupboard() in main");
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }
    static Table table = new Table();
    static Cupboard cupboard = new Cupbpard();
}

出力された結果は次のようになります.
Bowl(1) 
Bowl(2) 
Table() 
f1(1) 
Bowl(4)
Bowl(5)
Bowl(3) 
Cupboard()
f1(2) 
Creating new Cupboard() in main 
Bowl(3)     
Cupboard() 
f1(2) 
Creating new Cupboard() in main 
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)

静的初期化は、必要に応じてのみ行われ、最初のクラスオブジェクトが作成(または静的データへの最初のアクセス)された場合にのみ、静的データが初期化されます.その後、オブジェクトをどのように作成しても、再初期化されません.
初期化の順序は、まだ初期化されていない場合に静的オブジェクト、次に「非静的オブジェクト」です.
Dogというクラスがあるとしたら、オブジェクトの作成手順をまとめます.
  • staticキーワードを明示的に使用しなくても、コンストラクタは実際には静的方法である.したがって、Javaインタプリタは、初めてタイプがDogオブジェクトである場合(コンストラクタが静的メソッドと見なすことができる)、またはDogクラスの静的メソッド、静的ドメインが初めてアクセスする場合、Dogを特定するためにクラスのパスを検索する必要がある.class.
  • をDogにロード.classでは、静的初期化に関するすべての動作が実行されるため、静的初期化はClassオブジェクトが最初にロードされたときにのみ行われます.
  • newでDogオブジェクトを作成する場合、まず、スタック上でDogオブジェクトに十分な記憶領域を割り当てる.
  • は、割り当てられた記憶領域をクリアし、Dogオブジェクト内のすべての基本データ型を自動的にデフォルト値(数値では0、booleanおよびcharタイプでも同様)に設定し、参照はnullに設定します.
  • は、フィールドタイミングに現れるすべての初期化動作を実行する.
  • コンストラクタメソッドを実行します.

  • 5.7.3表示の静的初期化
    Javaは、複数の静的初期化動作を特殊な「静的句」(静的ブロックとも呼ばれる場合もある)に組織することを可能にする.次のようにします.
    public class Spoon {
        static int i;
        static {
            i = 47;
        }
    }
    

    他の静的初期化動作と同様に、このコードは1回のみ実行されます.このクラスのオブジェクトが初めて生成されたとき、またはこのクラスに属する静的メンバーデータに初めてアクセスしたときに実行されます.例:
    class Cup {
        Cup(int marker) {
            System.out.println("Cup(" + marker + ")");
        }
        void f(int marker) {
            System.out.println("f(" + marker + ")");
        }
    }
    
    class Cups {
        static Cup cup1;
        static Cup cup2;
        static {
            cup1 = new Cup(1);
            cup2 = new Cup(2);
        }
    
        Cups() {
            System.out.println("Cups()");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            System.out.println("Inside main()");
            Cups.cup1.f(99);  //  (1)
        }
    
        //static Cups cups1 = new Cups(); //  (2)
        //static Cups cup2 = new Cups();  //  (2)
    }
    

    以上の結果は、Inside main() Cup(1) Cup(2) f(99)です.
    5.7.4非静的インスタンスの初期化
    Javaには、各オブジェクトの非静的変数を初期化するためにインスタンス初期化される類似の構文もあります.静的文ブロックと同じようにstaticが少なくないだけです.クラスのインスタンスを作成しない場合、非静的文ブロックは実行されず、static変数と文ブロックにのみ触れます.
    次に、上記の手順を一例でまとめます.
    class Cup {
    {
        System.out.println("Block - Cup");
    }
    
    static int c;
    static {
        c = 1;
        System.out.println("Static Bolck - Cup");
    }
    
    
        Cup(int marker) {
            System.out.println("Construct - Cup(" + marker + ")");
        }
        void f(int marker) {
            System.out.println("Function - f(" + marker + ")");
        }
    }
    
    class Cups {
    
        static {
            cup1 = new Cup(1);
            cup2 = new Cup(2);
        }
        static Cup cup1;
        static Cup cup2;
        {
            System.out.println("Block - Cups");
        }
    
        Cups() {
            System.out.println("Construct - Cups()");
        }
    }
    public class JavaTest {
        public static void main(String[] args) {
            System.out.println("Inside main()");
            Cups.cup1.f(99);  //  (1)
        }
    }
    

    出力された結果は次のとおりです.
    Inside main()
    Static Bolck - Cup
    Block - Cup
    Construct - Cup(1)
    Block - Cup
    Construct - Cup(2)
    Function - f(99)
    

    以上の結果から,新しいCupsクラスのオブジェクトがない場合,非静的文ブロック,すなわち{}に含まれる文ブロックは実行されないことが分かる.クラスオブジェクトを最初に作成するとき、またはクラスに使用する静的変数の場合、classファイルがロードされ、static変数が初期化され、static{}文ブロックが実行されます.
    5.8配列初期化
    配列は、同じタイプの識別子名でカプセル化されたオブジェクトシーケンスまたは基本タイプのデータシーケンスにすぎません.セルグループは、四角カッコの下付きオペレータ【】によって定義され、使用されます.定義配列は、タイプ名の後に相手カッコ:int[] a1;を付けるだけです.四角カッコは、後に置くこともできます:int a1[];2つのフォーマットの意味は同じで、後者のフォーマットはCとC++プログラマーの習慣に合っています.前のフォーマットは、「int型配列」であることを示すより直感的である.
    コンパイラは配列のサイズを指定できません.現在は配列への参照(参照に十分な記憶領域が割り当てられている)だけで、配列オブジェクト自体に空間を割り当てていません.配列に対応するストレージスペースを作成するには、配列を初期化する必要があります.配列の初期化コードは、コードのどこにでも表示できますが、配列を作成する場所に表示される特殊な初期化式を使用することもできます.この特殊な初期化は一対のカッコで囲まれており,記憶空間の割り当て(newの使用と等価)はコンパイラが担当する.例:
    int[] a1 = {1,2,3,4,5};
    

    では、なぜ配列がないときに配列の参照を定義するのでしょうか.Javaでは配列を別の配列、int[] a2;に割り当てることができ、Javaでは1つの配列を別の配列に割り当てることができるので、a 2=a 1;リファレンスを直接コピーします.次の例を示します.
    public class ArrayTest {
        public static void main(String[] args) {
            int[] a1 = {1,2,3,4,5};
            int[] a2;
            a2 = a1;
            for(int i = 0;i < a2.length; i++)
                a2[i] = a2[i] + 1;
            for(int i = 0;i < a1.length; i++)
                System.out.println("a1[" + i + "]" + a[i]);
        }
    }
    

    出力はa1[0]=1 a1[1]=2 a1[2]=3 a1[3]=4 a1[4]=5すべての配列(要素の開始オブジェクトにかかわらず、基本タイプにかかわらず)には固有のメンバーlengthがあり、配列の長さを得ることができますが、直接変更することはできません.CとC++は似ていて、Java配列カウントは0から始まり、配列は境界を越え、CとC++は黙って受け入れられますが、Javaは直接実行時エラーが発生します.
    プログラミング時にnew再配列で要素を作成できます.基本タイプ配列が作成されているにもかかわらず、newは動作します(newで単一の基本タイプデータを作成することはできません).
    public class ArrayNew {
        public static void main(String[] args) {
            int[] a;
            Random rand = new Random(47);
            a = new int[rand.nextInt(20)];
        }
    }
    

    非基本タイプの配列が作成されている場合は、参照配列です.パッケージクラスIntegerを例に挙げます.
    public class Test {
        public static void main(String[] args) {
            Random rand = new Random(47);
            Integer[] a = new Integer[rand.nextInt(20)];
        }
    }
    

    ここでnewで配列を作成した後も、参照配列は1つであり、新しいIntegerオブジェクトを作成してオブジェクトと参照を接続するまで初期化は終了しません.オブジェクトの作成を忘れ、オブジェクトとリファレンスをリンクすると、配列は空のリファレンスでいっぱいになり、実行時に異常が発生します.
    5.8.1可変パラメータリスト
    可変パラメータリストは、パラメータの個数やタイプが未知の場合に使用できます.たとえば、void f(Object[] args)関数のパラメータです.このようなJava SE 5の前に出現するが、Java SE 5には、可変パラメータリストを定義するために新しいプロパティが追加されている.以下の例では、
    public class NewVarArgs {
        static void printArray(Object... args) {
            for(Object obj : args) {
                System.out.println(obj + " ");
            }
        }
        public static void main(String[] args) {
            printArray(new Integer(47), new Float(3.14), new Double(11.11));
            printArray(47, 3.14F, 11.11);
            printArray("one", "two", "three");
        }
    }
    

    可変パラメータがあれば、表示される配列構文を記述する必要はありません.パラメータを指定すると、コンパイラは実際に配列を埋め、最後に得られたのは配列です.
    5.9列挙タイプ
    Java SE 5には、グループが必要で列挙セットを使用する場合に便利に処理できるように、enumキーワードという小さな特性が追加されています.CとC++および他の多くの言語ではすでに列挙タイプがあり、Javaでは列挙タイプ機能がC/C++の機能よりも完備している.簡単な例を次に示します.
    public enum A {
        NOT, MILD, MEDIUM, HOT, FLAMING
    }
    

    ここでは、列挙タイプのインスタンスが定数であるため、命名習慣に従って通常大文字(下線で区切られた複数のアルファベット)で表される5つの値を持つAという列挙タイプを作成します.
    enumを使用するには、このタイプのリファレンスを作成し、インスタンスに接続する必要があります.
    public class Test {
        public static void main(String[] args) {
            A a = A.MEDUIM;
            System.out.println(a);
        }
    }
    

    列挙タイプを作成すると、コンパイラは自動的にいくつかのプロパティを追加します.例:
  • はtoString()メソッドを作成し、enumインスタンスの名前を簡単に表示できます.
  • ordinal()メソッドを作成し、特定のenum定数の宣言順序を表す.
  • static values()メソッドは、enum定数の宣言順序に従って、これらの定数値からなる配列を生成するために使用される.

  • 例は次のとおりです.
    public class EnumOrder {
        public static void main(String[] args){
            for(A a : A.values) {
                System.out.println(s + ", oridinal " + a.ordinal());
            }
        }
    }
    
         :
    NOT, oridinal 0
    MILD, oridinal 1
    MEDIUM, oridinal 2
    HOT, oridinal 3
    FLAMING, oridinal 4
    

    Enumのもう一つの特に実用的な特性はswitch文と一緒に使用できることです.次の例を見てください.
        enum Pet {
        Cat,
        Dog,
        Bird
    }
    public class JavaTest {
        Pet pet;
        public JavaTest(Pet p) {
            pet = p;
        }
        public void describe() {
            switch(pet) {
            case Cat :
                System.out.println("The pet is Cat");
                break;
            case Dog :
                System.out.println("The pet is Dog");
                break;
            case Bird :
                System.out.println("The pet is Bird");
                break;
    
            }
        }
        public static void main(String[] args) {
            Pet p1 = Pet.Bird;
            JavaTest test = new JavaTest(p1);
            test.describe();
        }
    }
    
       :The pet is Bird