JavaのDouble Brace初期化構文({{ ... }}
)とは何ですか?
二重中括弧の初期化は、指定されたクラス(outer中括弧)から派生した匿名クラスを作成し、そのクラス(inner中括弧)内に初期化ブロックを提供します。例えば.
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
それは、クラスローダーにとってはかなりのオーバーヘッドです。もちろん、一度実行すれば、初期化にそれほど時間はかかりません。しかし、エンタープライズアプリケーション全体でこれを20'000回実行すると、ほんの少しの「構文糖」だけのすべてのヒープメモリが必要になります。
上記のコードを取得してそのマップをメソッドから返す場合、そのメソッドの呼び出し元はガベージコレクションできない非常に重いリソースを疑いなく保持している可能性があります。次の例を考えてみましょう。
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"); }};
一部の人々は、これが構文的に刺激的だと感じるかもしれません。
例:
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の概念を思い出せば、構造体のような中括弧のために、インスタンス初期化子ブロックを静的初期化子に簡単に関連付けることができます。唯一の違いは、静的イニシャライザが静的キーワードで追加され、一度だけ実行されることです。作成するオブジェクトの数に関係なく。
ダブルブレースの初期化の楽しいアプリケーションについては、こちらをご覧ください 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 からの引用:
インスタンス変数の初期化ブロックは、静的初期化ブロックのように見えますが、静的キーワードはありません。
{
// whatever code is needed for initialization goes here
}
次のような二重中括弧の初期化のすべての悪影響を回避するには、次のようにします。
次のことを行います:
例:
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
1-二重中括弧のようなものはありません:
ダブルブレースの初期化などはないことを指摘したいと思います。通常の従来の1つのブレースの初期化ブロックのみがあります。 2番目の中括弧ブロックは、初期化とは関係ありません。答えは、これらの2つのブレースが何かを初期化すると言いますが、そうではありません。
2-匿名クラスだけでなく、すべてのクラスについてです:
ほとんどすべての回答は、匿名の内部クラスを作成するときに使用されるものであると述べています。それらの答えを読んでいる人は、これが匿名の内部クラスを作成するときにのみ使用されるという印象を受けると思います。ただし、すべてのクラスで使用されます。これらの回答を読むと、匿名クラス専用のまったく新しい特別な機能であり、誤解を招くと思います。
-目的は新しい概念ではなく、ブラケットを次々に配置することです:
さらに進むと、この質問は、2番目の開始ブラケットが最初の開始ブラケットの直後にある状況について説明しています。通常のクラスで使用する場合、通常、2つの中括弧の間にコードがありますが、まったく同じものです。したがって、ブラケットを配置するだけです。だから、これは新しいエキサイティングなことだと言ってはならない。なぜなら、これは私たち全員が知っていることであるが、括弧で囲んだコードで書かれているからだ。 「二重ブレースの初期化」と呼ばれる新しい概念を作成しないでください。
4-ネストされた匿名クラスの作成は、2つの波括弧とは関係ありません:
作成する匿名クラスが多すぎるという主張には同意しません。初期化ブロックのために作成するのではなく、作成するだけです。これらは、2つのブレース初期化を使用しなかった場合でも作成されるため、初期化なしでもこれらの問題が発生します。初期化は、初期化されたオブジェクトを作成する要素ではありません。
さらに、この存在しない「二重ブレースの初期化」を使用して作成された問題や、通常の1つのブラケットの初期化によって作成された問題について説明するべきではありません。しかし、すべての回答は、匿名クラスを作成することのせいではなく、「二重ブレースの初期化」と呼ばれるこの邪悪な(存在しない)ものであるという印象を読者に与えます。
あなたはこのようなことを意味しますか?
List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};
作成時の配列リストの初期化(ハック)
いくつかの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));
}
};
それは-他の用途の中でも-コレクションを初期化するためのショートカットです。 詳細...
ダブルブレースの初期化は、内部クラスの構文を利用します。配列リストを作成し、それをメソッドに渡したいとします:
ArrayList<String> friends = new ArrayList<>();
friends.add("Mark");
friends.add("Steve");
invite(friends);
再度配列リストが必要ない場合は、匿名にするのが良いでしょう。しかし、その後、どのように要素を追加できますか? (二重ブレースの初期化はここにあります)以下がその方法です:
invite(new ArrayList<String>({{ add("Mark"); add("Steve");}});
二重中括弧に注意してください。外側の中括弧は、ArrayList
の匿名サブクラスを作成します。内側の中括弧は オブジェクト構築ブロック です。
@Lukas Ederが指摘したようにコレクションの二重括弧の初期化は避けなければなりません。
匿名の内部クラスを作成します。すべての内部クラスは親インスタンスへの参照を保持するため、これらのコレクションオブジェクトが宣言オブジェクトよりも多くのオブジェクトによって参照されている場合、99%がガベージコレクションを防止します。
Java 9では、代わりに使用する必要がある便利なメソッドList.of
、Set.of
、およびMap.of
が導入されました。ダブルブレース初期化子よりも高速で効率的です。
これは、flashやvbscriptでよく使われているwithキーワードと同じように見えます。これは、this
が何であるかを変更する方法です。