web-dev-qa-db-ja.com

Javaでのイニシャライザーとコンストラクターの使用

だから、私はJavaスキルをブラッシュアップし、以前は知らなかったいくつかの機能を見つけました。静的およびインスタンス初期化子はそのようなテクニックです。

私の質問は、コンストラクターにコードを含める代わりに、いつイニシャライザーを使用するかです。いくつかの明らかな可能性を考えました。

  • 静的/インスタンス初期化子を使用して、「最終」静的/インスタンス変数の値を設定できますが、コンストラクターは

  • 静的イニシャライザを使用して、クラスの静的変数の値を設定できます。これは、各コンストラクタの開始時にコードの「if(someStaticVar == null)// do stuff」ブロックを持つよりも効率的です。

これらのケースは両方とも、これらの変数を設定するために必要なコードが単に「var = value」よりも複雑であると想定しています。そうでなければ、変数を宣言するときに単に値を設定する代わりに初期化子を使用する理由はないようです。

ただし、これらは些細なゲイン(特に最終変数を設定する機能)ではありませんが、初期化子を使用する必要がある状況はかなり限られているようです。

コンストラクターで行われることの多くにイニシャライザーを使用することは確かにできますが、そうする理由はわかりません。クラスのすべてのコンストラクターが大量のコードを共有している場合でも、プライベートのinitialize()関数の使用は、新しいコードを作成するときにそのコードを実行することを妨げないため、イニシャライザーを使用するよりも理にかなっているようですコンストラクタ。

何か不足していますか?イニシャライザを使用すべき状況は他にもたくさんありますか?それとも、非常に特定の状況で使用されるのは、むしろかなり限られたツールですか?

92
Inertiatic

静的イニシャライザは、cletusが述べたように有用であり、同じように使用します。クラスがロードされたときに初期化される静的変数がある場合、特に複雑な初期化を行いながら静的変数をfinalにすることができるため、静的初期化子を使用する方法があります。これは大きな勝利です。

「if(someStaticVar == null)// do stuff」が乱雑でエラーが発生しやすいことがわかりました。静的に初期化され、finalと宣言されている場合、nullである可能性を回避できます。

しかし、あなたが言うとき、私は混乱しています:

静的/インスタンス初期化子を使用して、「最終」静的/インスタンス変数の値を設定できますが、コンストラクターは

私はあなたが両方を言っていると仮定します:

  • 静的初期化子を使用して「最終」静的変数の値を設定できますが、コンストラクターは
  • インスタンス初期化子を使用して「最終」インスタンス変数の値を設定できますが、コンストラクタは

そして、あなたは最初の点で正しい、2番目の点で間違っています。たとえば、次のことができます。

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

また、多くのコードがコンストラクター間で共有される場合、これを処理する最良の方法の1つは、デフォルト値を提供してコンストラクターをチェーンすることです。これにより、何が行われているのかが明確になります。

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}
53
Eddie

匿名の内部クラスは(匿名であるため)コンストラクタを持つことができないため、インスタンス初期化子にかなり自然に適合します。

52
Alex Martelli

私はほとんどの場合、最終的な静的データ、特にコレクションを設定するために静的初期化ブロックを使用します。例えば:

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

これで、この例は1行のコードで実行できます。

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

ただし、静的なバージョンは、特にアイテムを初期化するのが簡単でない場合は、はるかにきれいになります。

単純な実装では、変更不可能なリストが作成されないこともありますが、これは潜在的な間違いです。上記は、パブリックメソッドなどから喜んで返すことができる不変のデータ構造を作成します。

26
cletus

すでに優れた点をいくつかここに追加します。静的初期化子はスレッドセーフです。クラスがロードされたときに実行されるため、コンストラクターを使用するよりも単純な静的データの初期化を行います。コンストラクターでは、静的データが初期化されているかどうかを確認し、実際に初期化するために同期ブロックが必要になります。

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

versus

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

インスタンスレベルではなく、クラスで同期する必要があることを忘れないでください。これにより、クラスがロードされるときに1回限りのコストではなく、構築されたインスタンスごとにコストが発生します。さらに、それはいです;-)

14
Robin

初期化子とそのコンストラクターの初期化順序に対する答えを探して記事全体を読みました。私はそれを見つけられなかったので、理解を確認するためにいくつかのコードを書きました。この小さなデモンストレーションをコメントとして追加すると思いました。理解度をテストするには、一番下で読む前に答えを予測できるかどうかを確認してください。

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

出力:

Java CtorOrder
A ctor
B initX
B ctor
13
Daniel

静的初期化子は、静的コンテキストのコンストラクタに相当します。インスタンス初期化子よりも頻繁にそれを見るでしょう。静的環境をセットアップするためにコードを実行する必要がある場合があります。

一般に、インスタンスの初期化子は匿名の内部クラスに最適です。 JMock's cookbook を見て、コードをより読みやすくするための革新的な使用方法を確認してください。

場合によっては、コンストラクター間でチェーンするのが複雑なロジックがある場合(たとえば、サブクラス化していて、super()を呼び出す必要があるためthis()を呼び出せない場合)、インスタンスで一般的なことを行うことで重複を避けることができますイニシャライザー。しかし、インスタンス初期化子は非常にまれなので、多くの人にとって驚くべき構文です。そのため、コンストラクターの動作が必要な場合はクラスを具体的にし、匿名ではありません。

JMockは例外です。これは、フレームワークの使用方法を意図しているためです。

7
Yishai

選択する際に考慮する必要がある重要な側面が1つあります。

イニシャライザーブロックはメンバーですクラス/オブジェクト、コンストラクターはメンバーではない。これはextension/subclassingを考慮するときに重要です:

  1. 初期化子は継承されますサブクラスによって。 (ただし、シャドウイングできます)
    これは、サブクラスが親クラスの意図どおりに初期化されることが基本的に保証されることを意味します。
  2. ただし、コンストラクターはnot継承です。 (暗黙的にsuper() [つまりパラメーターなし]を呼び出すか、手動で特定のsuper(...)呼び出しを行う必要があります。)
    これは、暗黙的または明示的なsuper(...)呼び出しが、親クラスの意図どおりにサブクラスを初期化しない可能性があることを意味します。

イニシャライザブロックのこの例を考えてみましょう。

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

出力:
initializing in initializer block of: ChildOfParentWithInitializer init
->サブクラスが実装するコンストラクターに関係なく、フィールドは初期化されます。

次に、コンストラクターを使用したこの例を検討します。

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

出力:
Constructor of ChildOfParentWithConstructor inits to null null
->これは、希望する結果ではない場合でも、デフォルトでフィールドをnullに初期化します。

6
Vankog

また、上記のすばらしい回答すべてに加えて、1つのポイントを追加したいと思います。 Class.forName( "")を使用してJDBCにドライバーを読み込むと、クラスの読み込みが行われ、Driverクラスの静的初期化子が起動され、その中のコードがDriver ManagerにDriverを登録します。これは、静的コードブロックの重要な使用法の1つです。

4
kmrinal

あなたが述べたように、多くの場合、それは有用ではありませんし、あまり使われていない構文と同様に、あなたのコードを見ている次の人が30秒を費やしてボールトから引き出すのを止めるためにそれを避けたいと思うでしょう。

一方、それはいくつかのことを行う唯一の方法です(それらをほとんどカバーしていると思います)。

静的変数自体はとにかくある程度回避する必要があります-常にではありませんが、それらを多く使用する場合、または1つのクラスで多く使用する場合は、異なるアプローチを見つけるかもしれません、あなたの将来はあなたに感謝します。

3
Bill K