web-dev-qa-db-ja.com

JavaのDouble Brace初期化とは何ですか?

JavaのDouble Brace初期化構文({{ ... }})とは何ですか?

272
sgokhales

二重中括弧の初期化は、指定されたクラス(outer中括弧)から派生した匿名クラスを作成し、そのクラス(inner中括弧)内に初期化ブロックを提供します。例えば.

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

この二重括弧の初期化を使用することの効果は、匿名の内部クラスを作成していることに注意してください。作成されたクラスには、周囲の外部クラスへの暗黙的なthisポインターがあります。通常は問題ではありませんが、状況によっては悲嘆を引き起こす可能性があります。シリアル化またはガベージコレクションを行う場合、これに注意する価値があります。

264
Brian Agnew

誰かが二重ブレースの初期化を使用するたびに、子猫は殺されます。

構文がかなり珍しく、実際には慣用的ではない(味はもちろん議論の余地がある)ことは別として、アプリケーションで2つの重要な問題を不必要に作成しています

1.あまりにも多くの匿名クラスを作成している

ダブルブレースの初期化を使用するたびに、新しいクラスが作成されます。例えば。この例:

Map source = new HashMap(){{
    put("firstName", "John");
    put("lastName", "Smith");
    put("organizations", new HashMap(){{
        put("0", new HashMap(){{
            put("id", "1234");
        }});
        put("abc", new HashMap(){{
            put("id", "5678");
        }});
    }});
}};

...これらのクラスを生成します:

Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

それは、クラスローダーにとってはかなりのオーバーヘッドです。もちろん、一度実行すれば、初期化にそれほど時間はかかりません。しかし、エンタープライズアプリケーション全体でこれを20'000回実行すると、ほんの少しの「構文糖」だけのすべてのヒープメモリが必要になります。

2.メモリリークが発生する可能性があります。

上記のコードを取得してそのマップをメソッドから返す場合、そのメソッドの呼び出し元はガベージコレクションできない非常に重いリソースを疑いなく保持している可能性があります。次の例を考えてみましょう。

public class ReallyHeavyObject {

    // Just to illustrate...
    private int[] tonsOfValues;
    private Resource[] tonsOfResources;

    // This method almost does nothing
    public Map quickHarmlessMethod() {
        Map source = new HashMap(){{
            put("firstName", "John");
            put("lastName", "Smith");
            put("organizations", new HashMap(){{
                put("0", new HashMap(){{
                    put("id", "1234");
                }});
                put("abc", new HashMap(){{
                    put("id", "5678");
                }});
            }});
        }};

        return source;
    }
}

返されるMapには、ReallyHeavyObjectを囲むインスタンスへの参照が含まれるようになります。あなたはおそらくそれを危険にさらしたくないでしょう:

Memory Leak Right Here

http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/ からの画像

3. Javaにマップリテラルがあることを装うことができます

実際の質問に答えるために、人々はこの構文を使用して、Javaが既存の配列リテラルに似たマップリテラルのようなものを持っているふりをしている。

String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};

一部の人々は、これが構文的に刺激的だと感じるかもしれません。

265
Lukas Eder
  • 最初の波括弧は、新しい匿名内部クラスを作成します。
  • 2番目のブレースのセットは、クラスの静的ブロックのようなインスタンス初期化子を作成します。

例:

   public class TestHashMap {
    public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<String,String>(){
        {
            put("1", "ONE");
        }{
            put("2", "TWO");
        }{
            put("3", "THREE");
        }
        };
        Set<String> keySet = map.keySet();
        for (String string : keySet) {
            System.out.println(string+" ->"+map.get(string));
        }
    }

}

仕組み

最初の波括弧は、新しい匿名内部クラスを作成します。これらの内部クラスは、親クラスの動作にアクセスできます。したがって、この場合、実際にはHashSetクラスのサブクラスを作成しているため、この内部クラスはput()メソッドを使用できます。

そして、2番目の中括弧セットは、インスタンス初期化子にすぎません。コアJavaの概念を思い出せば、構造体のような中括弧のために、インスタンス初期化子ブロックを静的初期化子に簡単に関連付けることができます。唯一の違いは、静的イニシャライザが静的キーワードで追加され、一度だけ実行されることです。作成するオブジェクトの数に関係なく。

詳細

38
Premraj

ダブルブレースの初期化の楽しいアプリケーションについては、こちらをご覧ください JavaのDwemthyの配列

抜粋

private static class IndustrialRaverMonkey
  extends Creature.Base {{
    life = 46;
    strength = 35;
    charisma = 91;
    weapon = 2;
  }}

private static class DwarvenAngel
  extends Creature.Base {{
    life = 540;
    strength = 6;
    charisma = 144;
    weapon = 50;
  }}

そして今、BattleOfGrottoOfSausageSmellsと…分厚いベーコンに備えてください!

23
akuhn

Javaの「二重ブレース初期化」のようなものは存在しないことを強調することが重要だと思います。 Oracle Webサイトにはこの用語はありません。この例では、匿名クラスと初期化ブロックの2つの機能が一緒に使用されています。古い初期化子ブロックは開発者によって忘れられており、このトピックで混乱を引き起こしているようです。 Oracle docs からの引用:

インスタンス変数の初期化ブロックは、静的初期化ブロックのように見えますが、静的キーワードはありません。

{
    // whatever code is needed for initialization goes here
}
13
Alex T

次のような二重中括弧の初期化のすべての悪影響を回避するには、次のようにします。

  1. 壊れた「等しい」互換性。
  2. 直接割り当てを使用する場合、チェックは実行されません。
  3. メモリリークの可能性。

次のことを行います:

  1. 特に二重ブレースの初期化のために、別個の「Builder」クラスを作成します。
  2. デフォルト値でフィールドを宣言します。
  3. そのクラスにオブジェクト作成メソッドを配置します。

例:

public class MyClass {
    public static class Builder {
        public int    first  = -1        ;
        public double second = Double.NaN;
        public String third  = null      ;

        public MyClass create() {
            return new MyClass(first, second, third);
        }
    }

    protected final int    first ;
    protected final double second;
    protected final String third ;

    protected MyClass(
        int    first ,
        double second,
        String third
    ) {
        this.first = first ;
        this.second= second;
        this.third = third ;
    }

    public int    first () { return first ; }
    public double second() { return second; }
    public String third () { return third ; }
}

使用法:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

利点:

  1. 単に使用する。
  2. 「等しい」互換性を壊さないでください。
  3. 作成方法でチェックを実行できます。
  4. メモリリークはありません。

欠点:

  • 無し。

そして、結果として、これまでで最も単純なJavaビルダーパターンがあります。

Githubのすべてのサンプルを参照してください: Java-sf-builder-simple-example

8

1-二重中括弧のようなものはありません:
ダブルブレースの初期化などはないことを指摘したいと思います。通常の従来の1つのブレースの初期化ブロックのみがあります。 2番目の中括弧ブロックは、初期化とは関係ありません。答えは、これらの2つのブレースが何かを初期化すると言いますが、そうではありません。

2-匿名クラスだけでなく、すべてのクラスについてです:
ほとんどすべての回答は、匿名の内部クラスを作成するときに使用されるものであると述べています。それらの答えを読んでいる人は、これが匿名の内部クラスを作成するときにのみ使用されるという印象を受けると思います。ただし、すべてのクラスで使用されます。これらの回答を読むと、匿名クラス専用のまったく新しい特別な機能であり、誤解を招くと思います。

-目的は新しい概念ではなく、ブラケットを次々に配置することです:
さらに進むと、この質問は、2番目の開始ブラケットが最初の開始ブラケットの直後にある状況について説明しています。通常のクラスで使用する場合、通常、2つの中括弧の間にコードがありますが、まったく同じものです。したがって、ブラケットを配置するだけです。だから、これは新しいエキサイティングなことだと言ってはならない。なぜなら、これは私たち全員が知っていることであるが、括弧で囲んだコードで書かれているからだ。 「二重ブレースの初期化」と呼ばれる新しい概念を作成しないでください。

4-ネストされた匿名クラスの作成は、2つの波括弧とは関係ありません:
作成する匿名クラスが多すぎるという主張には同意しません。初期化ブロックのために作成するのではなく、作成するだけです。これらは、2つのブレース初期化を使用しなかった場合でも作成されるため、初期化なしでもこれらの問題が発生します。初期化は、初期化されたオブジェクトを作成する要素ではありません。

さらに、この存在しない「二重ブレースの初期化」を使用して作成された問題や、通常の1つのブラケットの初期化によって作成された問題について説明するべきではありません。しかし、すべての回答は、匿名クラスを作成することのせいではなく、「二重ブレースの初期化」と呼ばれるこの邪悪な(存在しない)ものであるという印象を読者に与えます。

8
ctomek

あなたはこのようなことを意味しますか?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

作成時の配列リストの初期化(ハック)

4
dhblah

いくつかのJavaステートメントをループとして入れて、コレクションを初期化できます。

List<Character> characters = new ArrayList<Character>() {
    {
        for (char c = 'A'; c <= 'E'; c++) add(c);
    }
};

Random rnd = new Random();

List<Integer> integers = new ArrayList<Integer>() {
    {
         while (size() < 10) add(rnd.nextInt(1_000_000));
    }
};

ただし、この場合はパフォーマンスに影響します。これを確認してくださいdiscussion

4
Anton Dozortsev

それは-他の用途の中でも-コレクションを初期化するためのショートカットです。 詳細...

4
miku

ダブルブレースの初期化は、内部クラスの構文を利用します。配列リストを作成し、それをメソッドに渡したいとします:

ArrayList<String> friends = new ArrayList<>();
friends.add("Mark");
friends.add("Steve");
invite(friends);

再度配列リストが必要ない場合は、匿名にするのが良いでしょう。しかし、その後、どのように要素を追加できますか? (二重ブレースの初期化はここにあります)以下がその方法です:

invite(new ArrayList<String>({{ add("Mark"); add("Steve");}});

二重中括弧に注意してください。外側の中括弧は、ArrayListの匿名サブクラスを作成します。内側の中括弧は オブジェクト構築ブロック です。

2

@Lukas Ederが指摘したようにコレクションの二重括弧の初期化は避けなければなりません。

匿名の内部クラスを作成します。すべての内部クラスは親インスタンスへの参照を保持するため、これらのコレクションオブジェクトが宣言オブジェクトよりも多くのオブジェクトによって参照されている場合、99%がガベージコレクションを防止します。

Java 9では、代わりに使用する必要がある便利なメソッドList.ofSet.of、およびMap.ofが導入されました。ダブルブレース初期化子よりも高速で効率的です。

2

これは、flashやvbscriptでよく使われているwithキーワードと同じように見えます。これは、thisが何であるかを変更する方法です。

0
Chuck Vose