Javaで初期化ブロックを使用する必要がありますか?


16

私は最近、これまで見たことのないJavaコンストラクトに出会い、それを使用すべきかどうか疑問に思っていました。初期化ブロックと呼ばれるようです。

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

コードブロックは各コンストラクターにコピーされます。つまり、複数のコンストラクターがある場合、コードを書き換える必要はありません。

ただし、この構文を使用する主な欠点は3つあります。

  1. 複数のコードブロックを定義でき、記述された順序で実行されるため、コードの順序が重要なJavaの非常にまれなケースの1つです。これは、コードブロックの順序を変更するだけで実際にコードが変更されるため、私にとって有害なようです。
  2. 私はそれを使っても何の利点もありません。ほとんどの場合、コンストラクターは事前定義された値を使用して相互に呼び出します。そうでない場合でも、コードを単純にプライベートメソッドに入れて、各コンストラクターから呼び出すことができます。
  3. ブロックをクラスの最後に配置でき、コンストラクタは通常クラスの先頭にあるため、読みやすさが低下します。コードファイルのまったく異なる部分を見る必要があると思わない場合は、直観に反します。

上記の記述が正しい場合、この言語構​​造が導入された理由(およびその時期)は?正当な使用例はありますか?


3
投稿した例には、初期化ブロックのように見えるものは含まれていません。
サイモンB

6
@SimonBarkerをもう一度見てください{ doStuff(); }。クラスレベルでの初期化ブロックです。
アモン

@SimonBarker周囲にあるコードブロックdoStuff()
モニカの復活-dirkk


2
「[S]コードブロックの順序を変更すると、実際にコードが変更されます。」そして、それは変数初期化子または個々のコード行の順序を変更することとどのように違いますか?依存関係がない場合、害は発生せず、依存関係がある場合、依存関係の順序を狂わせることは、コードの個々の行の依存関係を誤って並べることと同じです。Javaでメソッドとクラスを定義する前に参照できるからといって、Javaで順序依存コードがまれであることを意味するわけではありません。
JAB

回答:


20

イニシャライザブロックを使用する場合は2つあります。

最初のものは、最終メンバーを初期化するためのものです。Javaでは、最終メンバーを宣言でインラインで初期化するか、コンストラクターで初期化できます。メソッドでは、最終メンバーに割り当てることは禁止されています。

これは有効です:

final int val = 2;

これも有効です。

final int val;

MyClass() {
    val = 2;
}

これは無効です:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

複数のコンストラクターがあり、最終メンバーをインラインで初期化できない場合(初期化ロジックが複雑すぎるため)、またはコンストラクターが自分自身を呼び出せない場合は、初期化コードをコピー/貼り付けするか、初期化ブロック。

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

イニシャライザブロックのもう1つの使用例は、小さなヘルパーデータ構造の構築です。メンバーを宣言し、独自の初期化ブロックの宣言の直後にメンバーに値を入れます。

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}

無効なのはメソッド呼び出しではありません。無効なのは、initメソッド内のコードです。コンストラクターと初期化ブロックのみが最終メンバー変数に割り当てることができるため、initでの割り当てはコンパイルされません。
バルジャック

4番目のコードブロックはコンパイルされません。Initalizerブロックはすべてのコンストラクターの前に実行されるため、squareVal = val * val初期化されていない値へのアクセスについて文句を言います。初期化子ブロックは、コンストラクタに渡される引数に依存することはできません。この種の問題に対して私が見た通常の解決策は、複雑なロジックを持つ単一の「ベース」コンストラクターを定義し、そのコンストラクターに関して他のすべてのコンストラクターを定義することです。実際、インスタンス初期化子のほとんどの使用は、そのパターンに置き換えることができます。
Malnormalulo

11

一般に、非静的な初期化ブロックを使用しないでください(静的なブロックも避けてください)。

わかりにくい構文

この質問を見ると、3つの答えがありますが、この構文で4人をだましています。私はそのうちの1人で、Javaを16年間書いてきました!明らかに、構文は潜在的にエラーを起こしやすいです!私はそれに近づかないでしょう。

テレスコープコンストラクター

本当にシンプルなものについては、「テレスコープ」コンストラクターを使用してこの混乱を回避できます。

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

ビルダーパターン

各コンストラクターまたはその他の高度な初期化の最後にdoStuff()が必要な場合は、おそらくビルダーパターンが最適です。 Josh Blochは、ビルダーが良いアイデアであるいくつかの理由をリストしています。ビルダーは書くのに少し時間がかかりますが、きちんと書かれていれば、使うのが楽しいです。

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

静的初期化ループ

以前は静的イニシャライザをよく使用していましたが、クラスが完全にロードされる前に呼び出される2つのクラスが互いの静的イニシャライザブロックに依存するループが発生することがありました。これにより、「クラスのロードに失敗しました」または同様にあいまいなエラーメッセージが生成されました。問題が何であるかを理解するために、ファイルをソース管理の最後の既知の作業バージョンと比較する必要がありました。まったく面白くない。

遅延初期化

静的イニシャライザは、パフォーマンス上の理由から、動作する場合に適切であり、混乱を招かないかもしれません。しかし、一般的に、最近では静的初期化子よりも遅延初期化を好んでいます。それらが何をするのかは明らかで、私はまだそれらのクラスローディングのバグに遭遇しておらず、それらはイニシャライザブロックが行うよりも多くの初期化状況で動作します。

データ定義

データ構造を構築するための静的な初期化の代わりに(他の回答の例と比較して)、Paguroの不変のデータ定義ヘルパー関数を使用します

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

けいれん

Javaの初期では、初期化ブロックはいくつかのことを行う唯一の方法でしたが、今では混乱しやすく、エラーが発生しやすく、ほとんどの場合、より優れた代替物に置き換えられています(上記を参照)。レガシコードでそれらを見る場合、またはテストで出てくる場合にイニシャライザーブロックについて知ることは興味深いですが、コードレビューを行っていて、新しいコードで1つを見た場合、なぜ上記の代替案は、コードを評価する前に適切でした。


3

として宣言されているインスタンス変数の初期化に加えてfinalbarjakの答えを参照)、static初期化ブロックについても言及します。

それらを一種の「静的コンストラクター」として使用できます。

そうすることで、クラスが最初に参照されるときに静的変数で複雑な初期化を行うことができます。

以下は、barjakのものに触発された例です。

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}

1

非静的な初期化子ブロックに関するfasのように、それらの基本的な機能は、匿名クラスのデフォルトコンストラクターとして機能することです。それは基本的に彼らが存在する唯一の権利です。


0

ステートメント1、2、3に完全に同意します。これらの理由からブロック初期化子を使用することもありませんし、Javaにブロックが存在する理由もわかりません。

ただし、1つの場合に静的ブロック初期化子を使用せざるを得ません。コンストラクターがチェック例外をスローできる静的フィールドをインスタンス化する必要がある場合です。

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

しかし、代わりにあなたがする必要があります:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

私は(それがまたマークにできないように非常に醜いこのイディオムを見つけるcontextなどfinal)が、これは唯一の方法は、そのようなフィールドを初期化するためにJavaでサポートされています。


context = null;catchブロックに設定すると、コンテキストをfinalとして宣言できる可能性があると思います。
グレンペターソン

@GlenPeterson試しましたが、コンパイルしません:The final field context may already have been assigned
発見

おっとっと!静的ブロック内にローカル変数を導入すると、コンテキストを最終的にすることができますstatic { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
。-グレンペターソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.