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


307

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




10
ダブルブレースの初期化は非常に危険な機能であり、慎重に使用する必要があります。イコールコントラクトを壊し、トリッキーなメモリリークを引き起こす可能性があります。この記事では詳細を説明します。
Andrii Polunin

回答:


303

ダブルブレースの初期化は、指定されたクラス(外側のブレース)から派生した匿名クラスを作成し、そのクラス(内側のブレース)内に初期化ブロックを提供します。例えば

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

この二重ブレースの初期化を使用すると、匿名の内部クラスが作成されるという効果があります。作成されたクラスにはthis、周囲の外部クラスへの暗黙のポインターがあります。通常は問題ではありませんが、シリアル化やガベージコレクションなどの状況で悲しみを引き起こす可能性があるため、この点に注意する必要があります。


11
中括弧と外括弧の意味を明確にしていただきありがとうございます。特別な意味を持つ2つの中かっこが、実際には魔法のような新しいトリックとしてのみ表示される通常のJavaコンストラクトであるのに、なぜ突然使用できるのか疑問に思いました。しかし、そのようなことから、Java構文に疑問を投げかけます。まだエキスパートでない場合は、読み書きするのが非常に難しい場合があります。
jackthehipster 14

4
このような「マジック構文」は多くの言語に存在します。たとえば、ほとんどすべてのCのような言語は、奇妙な "x--> 0"であるforループで "goes to 0"構文の "x-> 0"をサポートします。スペース配置。
ヨアヒムザウアー2018年

17
「二重ブレースの初期化」はそれ自体では存在しないと結論付けることができます。これは、匿名クラスの作成と初期化ブロックの組み合わせであり、一度組み合わせると構文構造のように見えますが、実際にはt。
MC皇帝

ありがとうございました!匿名の内部クラスの使用法のため、二重ブレースの初期化で何かをシリアル化すると、Gsonはnullを返します。
Pradeep AJ

295

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

構文がかなり変わっていて、あまり慣用的ではない(もちろん味は議論の余地があります)以外は、アプリケーションで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

これはクラスローダーにとってかなりのオーバーヘッドです。もちろん、一度行うと初期化にそれほど時間はかかりません。しかし、エンタープライズアプリケーション全体でこれを2万回行うとしたら、少しの "構文糖"のためにすべてのヒープメモリを使用しますか?

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。あなたはおそらくそれを危険にさらしたくないでしょう:

ここでメモリリーク

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"); }};

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


11
「あまりにも多くの匿名クラスを作成している」-(たとえば)Scalaが匿名クラスを作成する方法を見て、これが大きな問題であるかどうかはあまりわかりません
Brian Agnew

2
静的マップを宣言するための有効で優れた方法であり続けませんか?HashMapがフィールドで初期化{{...}}され、staticフィールドとして宣言されている場合、メモリリークが発生する可能性はなく、1つの匿名クラスのみが含まれ、囲まれたインスタンス参照はありません。
lorenzo-s

8
@ lorenzo-s:はい、2)と3)該当しない、1)のみ。幸い、Java 9にはついにMap.of()その目的があるので、それがより良い解決策になります
Lukas Eder

3
内部マップも外部マップへの参照を持っているため、間接的にへの参照があることに注意する価値があるかもしれませんReallyHeavyObject。また、匿名の内部クラスは、クラス本体内で使用されるすべてのローカル変数をキャプチャするため、定数を使用してこのパターンでコレクションまたはマップを初期化すると、内部クラスインスタンスはそれらすべてをキャプチャし、実際に削除された場合でもそれらを参照し続けますコレクションまたはマップ。したがって、その場合、これらのインスタンスは参照に必要なメモリの2倍を必要とするだけでなく、その点で別のメモリリークが発生します。
Holger、2018

41
  • 最初のブレースは、新しい匿名内部クラスを作成します。
  • ブレースの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の概念を思い出せば、構造体のような中かっこにより、インスタンス初期化子ブロックを静的初期化子に簡単に関連付けることができます。唯一の違いは、静的イニシャライザがstaticキーワードで追加され、1回だけ実行されることです。作成するオブジェクトの数に関係なく。

もっと


24

二重ブレースの初期化の楽しいアプリケーションについては、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分厚いベーコンの準備をしてください!


16

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

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

{
    // whatever code is needed for initialization goes here
}

11

1-
二重ブレースなどはありません。二重ブレースの初期化などはありません。通常の伝統的な1つのブレース初期化ブロックのみがあります。2番目のブレースブロックは、初期化とは関係ありません。答えは、これらの2つのブレースが何かを初期化すると言いますが、それはそうではありません。

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

3-目的は、新しい概念ではなく、括弧を前後に配置することだけです。
さらに、この質問では、2番目の左角括弧が最初の左角括弧の直後にある場合の状況について説明します。通常のクラスで使用する場合、通常、2つの中括弧の間にコードがいくつかありますが、まったく同じものです。したがって、ブラケットを配置することの問題です。これは私たちが皆知っていることなので、これはいくつかの新しいエキサイティングなことだと言ってはいけないと思いますが、大括弧の間にいくつかのコードを書いただけです。「ダブルブレースの初期化」という新しい概念を作成するべきではありません。

4-ネストされた匿名クラスの作成は、2つの中括弧とは関係
ありません。匿名クラスを作成しすぎるという議論には同意しません。初期化ブロックのために作成するのではなく、単に作成するためです。これらは、2つの中括弧の初期化を使用しなくても作成されるため、初期化なしでもこれらの問題が発生します...初期化は、初期化オブジェクトを作成する要素ではありません。

さらに、説明されている問題は匿名クラスを作成したためにのみ存在するため、元の質問とは関係がないため、この存在しない「ダブルブレースの初期化」または通常の1つのブラケットの初期化によって作成された問題については触れないでください。しかし、すべての答えは、匿名クラスを作成するのは誤りではなく、「二重ブレースの初期化」と呼ばれるこの邪悪な(存在しない)ことを読者に印象づけます。


9

次のような二重ブレースの初期化によるすべての悪影響を回避するには:

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

次のことをする:

  1. 特に二重ブレースの初期化のために別個の「ビルダー」クラスを作成します。
  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


4

これは、他の用途の中でも特に、コレクションを初期化するためのショートカットです。もっと詳しく知る ...


2
まあ、それはそのための1つのアプリケーションですが、決して唯一のものではありません。
skaffman 2009

4

このような意味ですか

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

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


4

いくつかの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));
    }
};

ただし、このケースはパフォーマンスに影響します。このディスカッションを確認してください


4

@Lukas Ederで指摘されているように、コレクションの二重括弧は初期化を避ける必要があります。

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

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


0

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

他の人が指摘したように、それを使用するのは安全ではありません。

ただし、コレクションの初期化にはいつでもこの代替手段を使用できます。

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");

-1

これは、フラッシュやvbscriptでよく使われているwithキーワードと同じように見えます。それthisは何であるか、何も変更しない方法です。


あんまり。それは、新しいクラスを作成することが、内容を変更するための方法だと言っているようなものthisです。構文は匿名クラスを作成するだけで(参照thisはすべて、その新しい匿名クラスのオブジェクトを参照することになります)、初期化ブロック{...}を使用して、新しく作成されたインスタンスを初期化します。
2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.