web-dev-qa-db-ja.com

インスタンス変数を宣言またはコンストラクターでインスタンス化する必要がありますか?

どちらのアプローチにも利点はありますか?

例1:

class A {
    B b = new B();
}

例2:

class A {
    B b;

    A() {
         b = new B();
    }
}
213
DonX
  • 違いはありません-インスタンス変数の初期化は、実際にはコンパイラーによってコンストラクターに入れられます。
  • 最初のバリアントは読みやすくなっています。
  • 最初のバリアントで例外処理を行うことはできません。
  • さらに、初期化ブロックがあります。これは、コンパイラーによってコンストラクターにも配置されます。

    {
        a = new A();
    }
    

チェック Sunの説明とアドバイス

このチュートリアル から:

ただし、フィールド宣言はメソッドの一部ではないため、ステートメントのように実行することはできません。代わりに、Javaコンパイラは、インスタンスフィールド初期化コードを自動的に生成し、クラスのコンストラクターに配置します。初期化コードは、ソースコードに現れる順序でコンストラクターに挿入されます。つまり、フィールド初期化子は、その前に宣言されたフィールドの初期値を使用できます。

また、フィールドを遅延初期化することもできます。フィールドの初期化がコストのかかる操作である場合、必要に応じてすぐに初期化できます。

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}

そして最終的に(Billが指摘したように)依存関係管理のために、クラス内のどこでもnew演算子を使用してavoidを使用することをお勧めします。代わりに、 Dependency Injection を使用することをお勧めします。つまり、他の誰か(別のクラス/フレームワーク)にクラスの依存関係をインスタンス化して注入させます。

256
Bozho

別のオプションは Dependency Injection を使用することです。

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}

これにより、BのコンストラクターからAオブジェクトを作成する責任がなくなります。これにより、コードがよりテストしやすくなり、長期的に維持しやすくなります。考え方は、2つのクラスABの間の結合を減らすことです。これにより得られる利点は、Bを拡張する(またはインターフェイスの場合はBを実装する)オブジェクトをAのコンストラクターに渡すことができ、機能することです。 1つの欠点は、Bオブジェクトのカプセル化を放棄するため、Aコンストラクターの呼び出し元に公開されることです。メリットがこのトレードオフの価値があるかどうかを検討する必要がありますが、多くの場合はメリットがあります。

36
Bill the Lizard

今日は面白い方法で火傷しました。

class MyClass extends FooClass {
    String a = null;

    public MyClass() {
        super();     // Superclass calls init();
    }

    @Override
    protected void init() {
        super.init();
        if (something)
            a = getStringYadaYada();
    }
}

間違いがありますか? a = null初期化子が呼び出されることが判明しましたafterスーパークラスコンストラクターが呼び出されます。スーパークラスコンストラクターはinit()を呼び出すため、aの初期化は、a = null初期化によって後続になります。

17
Edward Falk

私の個人的な「ルール」(ほとんど壊れない)は次のとおりです。

  • ブロックの先頭ですべての変数を宣言します
  • できない場合を除き、すべての変数を最終的にする
  • 行ごとに1つの変数を宣言します
  • 宣言された変数を初期化しない
  • 初期化を行うためにコンストラクターからのデータが必要な場合にのみ、コンストラクターで何かを初期化します

だから私は次のようなコードを持つでしょう:

public class X
{
    public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
    private static final int A;
    private final int b;
    private int c;

    static 
    { 
        A = 42; 
    }

    {
        b = 7;
    }

    public X(final int val)
    {
        c = val;
    }

    public void foo(final boolean f)
    {
        final int d;
        final int e;

        d = 7;

        // I will eat my own eyes before using ?: - personal taste.
        if(f)
        {
            e = 1;
        }
        else
        {
            e = 2;
        }
    }
}

このように、変数宣言を探す場所(ブロックの先頭)とその割り当て(宣言後意味があるとすぐに)を常に100%確信しています。これは、使用されていない値で変数を初期化しないため(たとえば、変数を宣言して初期化し、値を保持する必要のある変数の半分の前に例外をスローするため)、より効率的になる可能性があります。また、無意味な初期化を行うことはありません(int i = 0;など。その後、「i」が使用される前に、i = 5;を実行します。

私は一貫性を非常に重視しているので、この「ルール」に従うことは私が常にしていることであり、物事を見つけるために探し回る必要がないので、コードでの作業がはるかに簡単になります。

あなたのマイレージは異なる場合があります。

15
TofuBeer

例2は柔軟性が低くなります。別のコンストラクターを追加する場合は、そのコンストラクターでもフィールドをインスタンス化することを忘れないでください。フィールドを直接インスタンス化するか、ゲッターのどこかに遅延読み込みを導入します。

インスタンス化に単純なnew以上のものが必要な場合は、初期化ブロックを使用します。これは、使用されるコンストラクタのとにかくで実行されます。例えば。

public class A {
    private Properties properties;

    {
        try {
            properties = new Properties();
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
        } catch (IOException e) {
            throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
        }
    }

    // ...

}
7
BalusC

すでに説明したように、dependency injectionまたはlazy初期化のいずれかを常に使用することをお勧めします他の回答で徹底的に。

これらのパターンが不要または使用できない場合、およびプリミティブデータ型の場合、コンストラクターの外部でクラス属性を初期化することが望ましい理由を考えることができる3つの説得力のある理由があります。

  1. 繰り返しの回避 =複数のコンストラクターがある場合、またはコンストラクターを追加する必要がある場合、すべてのコンストラクター本体で繰り返し初期化を繰り返す必要はありません。
  2. 読みやすさの向上 =クラス外から初期化する必要がある変数を一目で簡単に確認できます。
  3. コードの削減された行 =宣言で行われるすべての初期化に対して、コンストラクターの行が少なくなります。
4
Marco Lackovic

初期化が単純で、ロジックを必要としない限り、それはほとんど好みの問題です。

イニシャライザーブロックを使用しない場合、コンストラクターアプローチは少し壊れやすくなります。後で2つ目のコンストラクターを追加し、そこでbを初期化することを忘れると、その最後のコンストラクターを使用している場合にのみnull bを取得するためです。

Javaでの初期化の詳細について(および初期化子の説明については http://Java.Sun.com/docs/books/tutorial/Java/javaOO/initial.html を参照してください。ブロックおよびその他のあまり知られていない初期化機能)。

4
Vinko Vrsalovic

私は返信に以下を見ていません:

宣言時に初期化を行うことの考えられる利点は、コードのどこからでも変数の宣言(主にCtrl-<hover_over_the_variable>-<left_mouse_click>)に非常に簡単にジャンプできる最近のIDEの場合です。その後、その変数の値がすぐに表示されます。それ以外の場合は、初期化が行われた場所を検索する必要があります(主にコンストラクター)。

もちろん、この利点は他のすべての論理的推論に次ぐものですが、一部の人々にとっては「機能」がより重要かもしれません。

1
GeertVc

どちらの方法も受け入れられます。後者の場合、別のコンストラクタが存在する場合、b=new B()は初期化されない可能性があることに注意してください。コンストラクター外の初期化コードを共通のコンストラクターと考え、コードが実行されます。

1
Chandra Patni

例2が望ましいと思います。ベストプラクティスは、コンストラクターの外で宣言し、コンストラクターで初期化することだと思います。

1
jkeesh

コンストラクターの外部で初期化するもう1つの微妙な理由があります。 UMLツールを使用してコードからクラス図を生成する場合(リバースエンジニアリング)、私が信じるツールのほとんどは、例1の初期化に注意し、それを図に転送します(初期値を表示したい場合は、私がやります)。繰り返しますが、これは非常に具体的な理由です。UMLツールを使用している場合、それを学んだ後は、そうでない限り、コンストラクターの外ですべてのデフォルト値を取得しようとしています。前述のとおり、例外がスローされたり、複雑なロジックが発生したりする可能性があります。

0
tulu

2番目のオプションは、クラスのインスタンス化のためにctorで異なるロジックを使用し、ctorチェーンを使用できるため、望ましい方法です。例えば。

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

したがって、2番目のオプションはより柔軟です。

0
Andriy Kryvtsun

実際にはかなり異なります:

宣言は構築の前に行われます。したがって、両方の場所で変数(この場合はb)を初期化した場合、コンストラクターの初期化はクラスレベルで行われたものを置き換えます。

クラスレベルで変数を宣言し、コンストラクターで変数を初期化します。

0
Imad S

2番目は、遅延初期化の例です。 1つ目は、より単純な初期化です。基本的に同じです。

0
fastcodejava