{{ ... }}
JavaのDouble Brace初期化構文()とは何ですか?
{{ ... }}
JavaのDouble Brace初期化構文()とは何ですか?
回答:
ダブルブレースの初期化は、指定されたクラス(外側のブレース)から派生した匿名クラスを作成し、そのクラス(内側のブレース)内に初期化ブロックを提供します。例えば
new ArrayList<Integer>() {{
add(1);
add(2);
}};
この二重ブレースの初期化を使用すると、匿名の内部クラスが作成されるという効果があります。作成されたクラスにはthis
、周囲の外部クラスへの暗黙のポインターがあります。通常は問題ではありませんが、シリアル化やガベージコレクションなどの状況で悲しみを引き起こす可能性があるため、この点に注意する必要があります。
誰かが二重ブレースの初期化を使用するたびに、子猫は殺されます。
構文がかなり変わっていて、あまり慣用的ではない(もちろん味は議論の余地があります)以外は、アプリケーションで2つの重大な問題を不必要に作成しています。これについては、最近ここで詳しく説明しました。
ダブルブレースの初期化を使用するたびに、新しいクラスが作成されます。たとえば、この例:
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万回行うとしたら、少しの "構文糖"のためにすべてのヒープメモリを使用しますか?
上記のコードを受け取り、そのマップをメソッドから返す場合、そのメソッドの呼び出し元は、ガベージコレクションできない非常に重いリソースを無意識に保持している可能性があります。次の例を検討してください。
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/からの画像
あなたの実際の質問に答えるために、人々はこの構文を使用して、Javaが既存の配列リテラルに似たマップリテラルのようなものを持っていると偽っています:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
一部の人々はこれが構文的に刺激的であると感じるかもしれません。
{{...}}
され、static
フィールドとして宣言されている場合、メモリリークが発生する可能性はなく、1つの匿名クラスのみが含まれ、囲まれたインスタンス参照はありません。
Map.of()
その目的があるので、それがより良い解決策になります
ReallyHeavyObject
。また、匿名の内部クラスは、クラス本体内で使用されるすべてのローカル変数をキャプチャするため、定数を使用してこのパターンでコレクションまたはマップを初期化すると、内部クラスインスタンスはそれらすべてをキャプチャし、実際に削除された場合でもそれらを参照し続けますコレクションまたはマップ。したがって、その場合、これらのインスタンスは参照に必要なメモリの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回だけ実行されることです。作成するオブジェクトの数に関係なく。
二重ブレースの初期化の楽しいアプリケーションについては、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
… 分厚いベーコンの準備をしてください!
Javaには「ダブルブレースの初期化」などはないことを強調しておくことが重要だと思います。OracleのWebサイトにはこの用語はありません。この例では、匿名クラスと初期化ブロックという2つの機能が一緒に使用されています。古い初期化ブロックは開発者によって忘れられているようで、このトピックでいくつかの混乱を引き起こしています。Oracle docsからの引用:
インスタンス変数の初期化子ブロックは、静的初期化子ブロックと同じように見えますが、staticキーワードがありません。
{
// whatever code is needed for initialization goes here
}
1-
二重ブレースなどはありません。二重ブレースの初期化などはありません。通常の伝統的な1つのブレース初期化ブロックのみがあります。2番目のブレースブロックは、初期化とは関係ありません。答えは、これらの2つのブレースが何かを初期化すると言いますが、それはそうではありません。
2-匿名クラスだけでなく、すべてのクラスについて:
ほとんどすべての回答は、匿名の内部クラスを作成するときに使用されるものであると述べています。これらの回答を読んだ人は、匿名の内部クラスを作成するときにのみ使用されるという印象を受けると思います。ただし、すべてのクラスで使用されます。それらの回答を読むことは、匿名クラス専用の真新しい特別機能のようであり、誤解を招くと思います。
3-目的は、新しい概念ではなく、括弧を前後に配置することだけです。
さらに、この質問では、2番目の左角括弧が最初の左角括弧の直後にある場合の状況について説明します。通常のクラスで使用する場合、通常、2つの中括弧の間にコードがいくつかありますが、まったく同じものです。したがって、ブラケットを配置することの問題です。これは私たちが皆知っていることなので、これはいくつかの新しいエキサイティングなことだと言ってはいけないと思いますが、大括弧の間にいくつかのコードを書いただけです。「ダブルブレースの初期化」という新しい概念を作成するべきではありません。
4-ネストされた匿名クラスの作成は、2つの中括弧とは関係
ありません。匿名クラスを作成しすぎるという議論には同意しません。初期化ブロックのために作成するのではなく、単に作成するためです。これらは、2つの中括弧の初期化を使用しなくても作成されるため、初期化なしでもこれらの問題が発生します...初期化は、初期化オブジェクトを作成する要素ではありません。
さらに、説明されている問題は匿名クラスを作成したためにのみ存在するため、元の質問とは関係がないため、この存在しない「ダブルブレースの初期化」または通常の1つのブラケットの初期化によって作成された問題については触れないでください。しかし、すべての答えは、匿名クラスを作成するのは誤りではなく、「二重ブレースの初期化」と呼ばれるこの邪悪な(存在しない)ことを読者に印象づけます。
次のような二重ブレースの初期化によるすべての悪影響を回避するには:
次のことをする:
例:
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();
利点:
短所:
その結果、これまでで最も単純なJavaビルダーパターンが得られました。
githubですべてのサンプルを参照してください:java-sf-builder-simple-example
これは、他の用途の中でも特に、コレクションを初期化するためのショートカットです。もっと詳しく知る ...
いくつかの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));
}
};
@Lukas Ederで指摘されているように、コレクションの二重波括弧は初期化を避ける必要があります。
匿名の内部クラスを作成します。すべての内部クラスは親インスタンスへの参照を保持しているため、これらのコレクションオブジェクトが宣言されているオブジェクト以外のオブジェクトによって参照されている場合、99%の確率でガベージコレクションを防ぐことができます。
Java 9 List.of
ではSet.of
、便利なメソッド、、が導入されていますが、Map.of
代わりに使用する必要があります。これらは、ダブルブレース初期化子よりも高速で効率的です。
最初のブレースは新しい匿名クラスを作成し、2番目のブレースのセットは静的ブロックのようなインスタンス初期化子を作成します。
他の人が指摘したように、それを使用するのは安全ではありません。
ただし、コレクションの初期化にはいつでもこの代替手段を使用できます。
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");