Java「ダブルブレース初期化」の効率性?


823

Javaの隠された機能トップの答えは言及ダブルブレースの初期化をして、非常に魅力的な構文:

Set<String> flavors = new HashSet<String>() {{
    add("vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

このイディオムは、インスタンスの初期化子だけを含む匿名の内部クラスを作成します。これは、「包含スコープ内の[...]メソッドを使用できます」。

主な質問:これは想像以上に非効率ですか?その使用を1回限りの初期化に限定する必要がありますか?(そしてもちろん自慢して見せます!)

2番目の質問:新しいHashSetは、インスタンス初期化子で使用される「this」である必要があります...誰でもメカニズムに光を当てることができますか?

3番目の質問:このイディオムは、量産コードで使用するにはあいまいすぎますか?

概要:非常に良い答えです。皆さんに感謝します。質問(3)で、人々は構文が明確であるべきだと感じました(ただし、コードに慣れていない開発者にコードが渡される場合は特に、時々コメントをお勧めします)。

質問(1)では、生成されたコードはすぐに実行されます。余分な.classファイルが原因でjarファイルが乱雑になり、プログラムの起動がわずかに遅くなります(それを測定してくれた@coobirdに感謝します)。@Thiloは、ガベージコレクションが影響を受ける可能性があり、余分にロードされたクラスのメモリコストが場合によっては要因になる可能性があることを指摘しました。

質問(2)は私にとって最も興味深いものでした。答えを理解すると、DBIで起こっていることは、匿名の内部クラスがnew演算子によって構築されるオブジェクトのクラスを拡張しているため、構築されるインスタンスを参照する「this」値を持っていることです。とてもきちんとしています。

全体として、DBIは知的好奇心のようなものとして私を襲います。Coobirdなどは、Arrays.asList、varargsメソッド、Googleコレクション、および提案されたJava 7コレクションリテラルで同じ効果を達成できると指摘しています。Scala、JRuby、Groovyなどの新しいJVM言語も、リストの作成に簡潔な表記を提供し、Javaとうまく相互運用できます。DBIがクラスパスを混乱させ、クラスの読み込みを少し遅くし、コードを少しあいまいにすると、おそらく私はそれを避けます。しかし、私はこれをSCJPを取得したばかりの友人に頼み、Javaのセマンティクスについて素朴な冗談を愛するつもりです。;-) みんな、ありがとう!

2017年7月:Baeldung 二重ブレースの初期化の良い要約持ち、アンチパターンと見なします。

2017年12月:@Basil Bourqueは、新しいJava 9では次のように言うことができると述べています。

Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");

それは確かに行く方法です。以前のバージョンに行き詰まっている場合は、GoogleコレクションのImmutableSetをご覧ください


33
私がここで見るコードのにおいは、単純な読者がであると期待flavorsすることですがHashSet、残念ながらそれは匿名のサブクラスです。
Elazar Leibovich、2009

6
ロードパフォーマンスではなく実行を検討する場合、違いはありません。私の答えを参照してください。
Peter Lawrey 2013年

4
あなたが要約を作成したことを嬉しく思います。これは、理解を深め、コミュニティーを増やすための価値ある練習だと思います。
Patrick Murphy

3
それは私の意見では曖昧ではありません。読者はダブルであることを知っているべきです... o待ってください、@ ElazarLeibovichはすでに彼のコメントでそれを述べました。二重ブレース初期化子自体は、言語構造としては存在しません。匿名サブクラスとインスタンス初期化子の組み合わせにすぎません。唯一のことは、人々がこれに気づく必要があるということです。
MC皇帝

8
Javaの9つの申し出不変のセットstaticファクトリメソッドをいくつかの状況ではDCIの使用を置き換える可能性がありますSet<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
バジルボーク

回答:


607

匿名の内部クラスに夢中になっているときの問題は次のとおりです。

2009/05/27  16:35             1,602 DemoApp2$1.class
2009/05/27  16:35             1,976 DemoApp2$10.class
2009/05/27  16:35             1,919 DemoApp2$11.class
2009/05/27  16:35             2,404 DemoApp2$12.class
2009/05/27  16:35             1,197 DemoApp2$13.class

/* snip */

2009/05/27  16:35             1,953 DemoApp2$30.class
2009/05/27  16:35             1,910 DemoApp2$31.class
2009/05/27  16:35             2,007 DemoApp2$32.class
2009/05/27  16:35               926 DemoApp2$33$1$1.class
2009/05/27  16:35             4,104 DemoApp2$33$1.class
2009/05/27  16:35             2,849 DemoApp2$33.class
2009/05/27  16:35               926 DemoApp2$34$1$1.class
2009/05/27  16:35             4,234 DemoApp2$34$1.class
2009/05/27  16:35             2,849 DemoApp2$34.class

/* snip */

2009/05/27  16:35               614 DemoApp2$40.class
2009/05/27  16:35             2,344 DemoApp2$5.class
2009/05/27  16:35             1,551 DemoApp2$6.class
2009/05/27  16:35             1,604 DemoApp2$7.class
2009/05/27  16:35             1,809 DemoApp2$8.class
2009/05/27  16:35             2,022 DemoApp2$9.class

これらはすべて、私が単純なアプリケーションを作成したときに生成されたクラスであり、大量の匿名内部クラスを使用していましたclass。各クラスは個別のファイルにコンパイルされます。

「ダブルブレースの初期化」は、すでに述べたように、インスタンス初期化ブロックを持つ匿名の内部クラスです。つまり、通常はすべて1つのオブジェクトを作成する目的で、「初期化」ごとに新しいクラスが作成されます。

Java Virtual Machineがこれらのクラスを使用する場合、それらすべてのクラスを読み取る必要があることを考えると、バイトコード検証プロセスなどに時間がかかる可能性があります。これらすべてのclassファイルを保存するために必要なディスク容量の増加は言うまでもありません。

ダブルブレースの初期化を利用するときにオーバーヘッドが少しあるように思われるので、多すぎてやり過ぎることはおそらくあまり良い考えではありません。しかし、エディがコメントで述べたように、影響を完全に確信することは不可能です。


参考までに、二重ブレースの初期化は次のとおりです。

List<String> list = new ArrayList<String>() {{
    add("Hello");
    add("World!");
}};

これは、Javaの「隠された」機能のように見えますが、これは単に次のように書き直したものです。

List<String> list = new ArrayList<String>() {

    // Instance initialization block
    {
        add("Hello");
        add("World!");
    }
};

つまり、基本的には匿名の内部クラスの一部であるインスタンス初期化ブロックです。


ジョシュア・ブロックのプロジェクトコインに関するコレクションリテラルの提案は、次のとおりでした。

List<Integer> intList = [1, 2, 3, 4];

Set<String> strSet = {"Apple", "Banana", "Cactus"};

Map<String, Integer> truthMap = { "answer" : 42 };

悲しいことに、それ Java 7にもJava 8にも進まず、無期限に棚上げされました。


実験

これが私がテストした簡単な実験です- ArrayList要素で1000 秒を作成し、2つのメソッドを使用して、メソッドを介して要素"Hello""World!"追加しますadd

方法1:二重ブレースの初期化

List<String> l = new ArrayList<String>() {{
  add("Hello");
  add("World!");
}};

方法2:ArrayListandをインスタンス化するadd

List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");

2つのメソッドを使用して1000の初期化を実行するJavaソースファイルを書き出す単純なプログラムを作成しました。

テスト1:

class Test1 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    List<String> l1 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    /* snip */

    List<String> l999 = new ArrayList<String>() {{
      add("Hello");
      add("World!");
    }};

    System.out.println(System.currentTimeMillis() - st);
  }
}

テスト2:

class Test2 {
  public static void main(String[] s) {
    long st = System.currentTimeMillis();

    List<String> l0 = new ArrayList<String>();
    l0.add("Hello");
    l0.add("World!");

    List<String> l1 = new ArrayList<String>();
    l1.add("Hello");
    l1.add("World!");

    /* snip */

    List<String> l999 = new ArrayList<String>();
    l999.add("Hello");
    l999.add("World!");

    System.out.println(System.currentTimeMillis() - st);
  }
}

1000 ArrayList秒と1000個の匿名内部クラスの拡張を初期化するための経過時間ArrayListSystem.currentTimeMillis、を使用してチェックされるため、タイマーの解像度はそれほど高くないことに注意してください。私のWindowsシステムでは、解像度は約15〜16ミリ秒です。

2つのテストを10回実行した結果は次のとおりです。

Test1 Times (ms)           Test2 Times (ms)
----------------           ----------------
           187                          0
           203                          0
           203                          0
           188                          0
           188                          0
           187                          0
           203                          0
           188                          0
           188                          0
           203                          0

見てわかるように、二重ブレースの初期化には、約190ミリ秒という顕著な実行時間があります。

一方、ArrayList初期化実行時間は0msとなった。もちろん、タイマーの解像度を考慮する必要がありますが、15ミリ秒未満になる可能性があります。

したがって、2つのメソッドの実行時間には顕著な違いがあるようです。2つの初期化方法には確かにオーバーヘッドがあるようです。

そして、はい、二重ブレース初期化テストプログラムを.classコンパイルすることによって生成された1000個のファイルがありましたTest1


10
「たぶん」というのは手術の言葉です。測定されない限り、パフォーマンスに関する記述は意味がありません。
インスタンスハンター、

16
あなたはとても素晴らしい仕事を成し遂げたので、私はこれを言いたくはありませんが、Test1時間はクラスロードによって支配される可能性があります。誰かがforループで各テストの1つのインスタンスを1,000回実行し、1,000秒または10,000回の1秒のforループでもう一度実行し、時間差を出力するのを見るのは興味深いでしょう(System.nanoTime())。最初のforループはすべてのウォームアップ効果(JIT、クラスロードなど)を通過する必要があります。ただし、どちらのテストも異なるユースケースをモデル化しています。明日仕事でこれを実行してみます。
ジムフェラン2009年

8
@ジム・フェラン:Test1の時間がクラスロードからのものであると私はかなり確信しています。ただし、二重ブレースの初期化を使用すると、クラスロードから対処する必要があります。二重ブレースの初期化のほとんどのユースケースを信じています。1回限りの初期化の場合、テストはこのタイプの初期化の一般的な使用例に近い条件で行われます。各テストを複数回繰り返すと、実行時間のギャップが小さくなると思います。
coobird 2009年

73
これが証明していることは、a)ダブルブレースの初期化が遅いこと、およびb)1000回実行しても、おそらく違いに気付かないことです。また、これが内部ループのボトルネックになる可能性もありません。それは非常に最悪の場合、1回限りの小さなペナルティを課します。
マイケルマイヤーズ

15
DBIを使用すると、コードが読みやすくなったり表現力が増したりする場合は、それを使用します。それがJVMが実行しなければならない仕事を少し増やすという事実は、それ自体、それに対する有効な議論ではありません。それがあった場合、我々はまた...少ない方法で代わりに巨大なクラスを好む、余分なヘルパー・メソッド/クラスを心配しなければならない
ホジェリオ

105

これまで指摘されていなかったこのアプローチの1つの特性は、内部クラスを作成するため、包含クラス全体がそのスコープにキャプチャされることです。つまり、Setが有効である限り、それは包含インスタンス(this$0)へのポインターを保持し、それがガベージコレクションされないようにします。これは問題になる可能性があります。

これと、通常のHashSetが正常に機能する(またはさらに優れている)場合でも、最初に新しいクラスが作成されるという事実により、この構文を使用したくありません(構文糖衣が本当に必要な場合でも)。

2番目の質問:新しいHashSetは、インスタンス初期化子で使用される「this」である必要があります...誰でもメカニズムに光を当てることができますか?私は、「これ」が「フレーバー」を初期化するオブジェクトを指すと単純に期待していました。

これが内部クラスのしくみです。それらは独自のを取得しthisますが、親インスタンスへのポインターも持っているため、包含オブジェクトのメソッドも呼び出すことができます。名前が競合する場合は、内部クラス(あなたの場合はHashSet)が優先されますが、「this」の前にクラス名を付けて、外部メソッドを取得することもできます。

public class Test {

    public void add(Object o) {
    }

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // HashSet
              Test.this.add("hello"); // outer instance 
            }
        };
    }
}

作成される匿名サブクラスを明確にするために、そこにメソッドを定義することもできます。たとえばオーバーライドHashSet.add()

    public Set<String> makeSet() {
        return new HashSet<String>() {
            {
              add("hello"); // not HashSet anymore ...
            }

            @Override
            boolean add(String s){

            }

        };
    }

5
含まれているクラスへの隠された参照に関する非常に良い点。元の例では、インスタンス初期化子は、Test.this.add()ではなく、新しいHashSet <String>のadd()メソッドを呼び出しています。それは私に何か他のことが起こっていることを示唆しています。Nathan Kitchenが示唆するように、HashSet <String>の匿名の内部クラスはありますか?
ジムフェラン2009年

データ構造のシリアル化が含まれている場合、包含クラスへの参照も危険な場合があります。包含クラスもシリアル化されるため、Serializableである必要があります。これにより、不明瞭なエラーが発生する可能性があります。
ちょうど別のJavaプログラマ

56

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

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

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


7
子猫を救え!いい答えです!
Aris2World 2018年

36

次のテストクラスを受講します。

public class Test {
  public void test() {
    Set<String> flavors = new HashSet<String>() {{
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }};
  }
}

そしてクラスファイルを逆コンパイルします、私は見ます:

public class Test {
  public void test() {
    java.util.Set flavors = new HashSet() {

      final Test this$0;

      {
        this$0 = Test.this;
        super();
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
    };
  }
}

これは私にはひどく非効率に見えません。このようなパフォーマンスが心配なら、プロファイリングします。そして、あなたの質問#2は上記のコードで答えられます:内部クラスの暗黙のコンストラクター(およびインスタンス初期化子)内にいるので、 " this"はこの内部クラスを指します。

はい、この構文はあいまいですが、コメントを使用すると、あいまいな構文の使用法を明確にすることができます。構文を明確にするために、ほとんどの人は静的初期化ブロック(JLS 8.7静的初期化子)に精通しています。

public class Sample1 {
    private static final String someVar;
    static {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

staticコンストラクターの使用(JLS 8.6インスタンス初期化子)にも同様の構文(単語 " " なし)を使用することもできますが、これは製品コードで使用されたことはありません。これはあまり知られていません。

public class Sample2 {
    private final String someVar;

    // This is an instance initializer
    {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

デフォルトのコンストラクタがない場合、{との間のコードブロックは}、コンパイラによってコンストラクタに変換されます。これを念頭に置いて、二重ブレースコードを解明します。

public void test() {
  Set<String> flavors = new HashSet<String>() {
      {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
  };
}

最も内側のブレースの間のコードのブロックは、コンパイラーによってコンストラクターに変換されます。最も外側のブレースは、匿名の内部クラスを区切ります。これをすべてを非匿名にする最後のステップにするには:

public void test() {
  Set<String> flavors = new MyHashSet();
}

class MyHashSet extends HashSet<String>() {
    public MyHashSet() {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }
}

初期化の目的では、オーバーヘッドはまったくありません(または無視できるほど小さい)。ただし、のすべての使用はflavorsに反対するのではなくHashSet、に反対しMyHashSetます。これには、おそらくわずかな(そしてごくわずかな)オーバーヘッドがあるでしょう。しかし、再び、心配する前に、プロファイリングを行いました。

繰り返しになりますが、あなたの質問#2で、上記のコードは二重ブレースの初期化の論理的かつ明示的な同等物であり、「this」がどこを参照するかを明らかにします:拡張する内部クラスへHashSet

インスタンス初期化子の詳細について質問がある場合は、JLSドキュメントの詳細を確認してください。


エディ、とてもいい説明。JVMバイトコードが逆コンパイルと同じくらいクリーンである場合、実行速度は十分に速くなりますが、余分な.classファイルの乱雑さが気になります。インスタンス初期化子のコンストラクターが「this」を新しいHashSet <String>インスタンスとして認識し、Testインスタンスとして認識しない理由について、私はまだ興味があります。これは、イディオムをサポートするために最新のJava言語仕様で明示的に指定された動作ですか?
ジムフェラン2009年

回答を更新しました。Testクラスのボイラープレートを省略したため、混乱が生じました。私はそれを私の答えに入れて、物事をよりはっきりさせます。このイディオムで使用されるインスタンス初期化ブロックのJLSセクションについても触れます。
エディ

1
@Jim "this"の解釈は特別な場合ではありません。これは、HashSet <String>の匿名サブクラスである、最も内側の包含クラスのインスタンスを単に参照します。
Nathan Kitchen、

4年半後にジャンプしてごめんなさい。しかし、逆コンパイルされたクラスファイル(2番目のコードブロック)の良い点は、有効なJavaではないということです。それは持っているsuper()暗黙のコンストラクタのセカンドラインとして、それが最初に来る必要があります。(テストしましたが、コンパイルされません。)
chiastic-security

1
@ chiastic-security:デコンパイラーがコンパイルできないコードを生成することがあります。
Eddie

35

漏れやすい

パフォーマンスへの影響には、ディスク操作+ unzip(jarの場合)、クラス検証、perm-genスペース(SunのHotspot JVMの場合)が含まれます。ただし、最悪の場合、リークが発生しやすくなります。単純に戻ることはできません。

Set<String> getFlavors(){
  return Collections.unmodifiableSet(flavors)
}

したがって、セットが別のクラスローダーによってロードされた他の部分にエスケープし、そこに参照が保持されている場合、クラス+クラスローダーのツリー全体がリークされます。これを回避するには、HashMapへのコピーが必要ですnew LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}})。可愛くないです。私自身は慣用句を使わず、代わりに new LinkedHashSet(Arrays.asList("xxx","YYY"));


3
幸い、Java 8以降、PermGenはもはや存在しません。影響はまだあると思いますが、非常に不明瞭なエラーメッセージが表示される可能性はありません。
Joey

2
@Joeyは、メモリがGC(perm gen)によって直接管理されているかどうかにかかわらず、違いはありません。メタスペースのリークメタは、Linuxでoom_killerようなもので(パーマ世代のうちの)OOMがありません制限されていない限り、まだ漏れているにキックしようとしている。
bestsss

19

多くのクラスをロードすると、最初に数ミリ秒かかる場合があります。起動がそれほど重要ではなく、起動後にクラスの効率を調べている場合、違いはありません。

package vanilla.java.perfeg.doublebracket;

import java.util.*;

/**
 * @author plawrey
 */
public class DoubleBracketMain {
    public static void main(String... args) {
        final List<String> list1 = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("!!!");
            }
        };
        List<String> list2 = new ArrayList<String>(list1);
        Set<String> set1 = new LinkedHashSet<String>() {
            {
                addAll(list1);
            }
        };
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(list1);
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
            {
                put(1, "one");
                put(2, "two");
                put(3, "three");
            }
        };
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
        map2.putAll(map1);

        for (int i = 0; i < 10; i++) {
            long dbTimes = timeComparison(list1, list1)
                    + timeComparison(set1, set1)
                    + timeComparison(map1.keySet(), map1.keySet())
                    + timeComparison(map1.values(), map1.values());
            long times = timeComparison(list2, list2)
                    + timeComparison(set2, set2)
                    + timeComparison(map2.keySet(), map2.keySet())
                    + timeComparison(map2.values(), map2.values());
            if (i > 0)
                System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
        }
    }

    public static long timeComparison(Collection a, Collection b) {
        long start = System.nanoTime();
        int runs = 10000000;
        for (int i = 0; i < runs; i++)
            compareCollections(a, b);
        long rate = (System.nanoTime() - start) / runs;
        return rate;
    }

    public static void compareCollections(Collection a, Collection b) {
        if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
            throw new AssertionError();
    }
}

プリント

double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns

2
DBIを過度に使用すると、PermGenスペースが蒸発することを除いて、違いはありません。少なくとも、PermGenスペースのクラスのアンロードとガベージコレクションを許可するために、あいまいなJVMオプション設定しない限り、それは可能です。サーバーサイド言語としてのJavaの普及を考えると、memory / PermGenの問題は少なくとも言及に値します。
aroth

1
@arothこれは良い点です。私はJavaでの16年間の作業で、PermGen(またはメタスペース)を調整する必要のあるシステムで作業したことがないことを認めます。
Peter Lawrey、2015年

2
条件はないはずcompareCollectionsと組み合わせ得る||のではなく&&&&最初の条件のみがテストされるため、使用は意味的に間違っているだけでなく、パフォーマンスを測定する意図を妨げます。さらに、スマートオプティマイザーは、反復中に条件が変化しないことを認識できます。
Holger

@arothは単なるアップデートです。Java8以降、VMはperm-genを使用しなくなりました。
Angel O'Sphere

16

セットを作成するには、二重括弧の初期化の代わりにvarargsファクトリメソッドを使用できます。

public static Set<T> setOf(T ... elements) {
    return new HashSet<T>(Arrays.asList(elements));
}

Googleコレクションライブラリには、このような便利なメソッドが多数あり、その他の便利な機能もたくさんあります。

イディオムの曖昧さに関しては、私はそれに遭遇し、それを常にプロダクションコードで使用しています。私は、イディオムが本番用コードを書くことを許可されていることに混乱するプログラマーについてもっと心配したいと思います。


ハァッ!;-)私は実際には1.2日からJavaに戻るRip van Winkleです(私は、VoiceXML音声WebブラウザーをJavaのevolution.voxeo.comで作成しました)。ジェネリック、パラメーター化された型、コレクション、java.util.concurrent、新しいforループ構文などを学ぶのは楽しいです。今ではより良い言語になっています。要するに、DBIの背後にあるメカニズムは最初はあいまいに見えるかもしれませんが、コードの意味はかなり明確なはずです。
ジムフェラン2009年

10

効率性はさておき、単体テスト以外で宣言的なコレクションの作成を希望することはほとんどありません。二重ブレース構文は非常に読みやすいと思います。

リストの宣言的構築を実現する別の方法は、具体的には次のように使用するArrays.asList(T ...)ことです。

List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");

このアプローチの制限はもちろん、生成するリストの特定のタイプを制御できないことです。


1
Arrays.asList()は通常使用するものですが、そうです、この状況は主に単体テストで発生します。実際のコードは、DBクエリやXMLなどからリストを作成します。
ジムフェラン2009年

7
ただし、asListには注意してください。返されたリストは要素の追加または削除をサポートしていません。asListを使用するときは常に、new ArrayList<String>(Arrays.asList("vanilla", "strawberry", "chocolate"))この問題を回避するために、結果のリストをコンストラクターに渡します。
マイケルマイヤーズ

7

一般に、それについて特に非効率的なことは何もありません。サブクラスを作成してそれにコンストラクターを追加したことは、JVMにとって一般的に重要ではありません。これは、オブジェクト指向言語で行う通常の日常的なことです。これを行うことで非効率になる可能性のあるかなり考えられたケースを考えることができます(たとえば、このサブクラスのために異なるクラスの混合を取得するメソッドが繰り返し呼び出されるのに対し、渡された通常のクラスは完全に予測可能です- -後者の場合、JITコンパイラーは最初の段階では実行できない最適化を行う可能性があります)。しかし、本当に、それが問題になるケースは非常に不自然だと思います。

多くの匿名クラスで「混乱させる」かどうかの観点から、この問題はもっとよくわかります。おおまかなガイドとして、イディオムの使用は、イベントハンドラーで匿名クラスを使用する場合に限って検討してください。

(2)では、オブジェクトのコンストラクター内にいるので、「this」は作成するオブジェクトを指します。それは他のコンストラクターと何の違いもありません。

(3)に関しては、それは本当にあなたのコードを誰が保守しているのかに依存すると思います。これが事前にわからない場合、私が使用することをお勧めするベンチマークは、「JDKのソースコードでこれを確認していますか?」です。(この場合、私は多くの匿名イニシャライザを目にしたことを思い出しません、そしてそれが匿名クラスの唯一のコンテンツである場合は確かにそうではありません)。ほとんどの適度なサイズのプロジェクトでは、プログラマーがJDKソースをいつかどこかで理解する必要があるので、そこで使用される構文やイディオムは「公正なゲーム」です。それ以上に、だれがコードを保守しているのかを制御できる場合は、その構文について人々にトレーニングし、そうでない場合はコメントまたは回避します。


5

ダブルブレースの初期化は、メモリリークやその他の問題を引き起こす可能性がある不要なハックです

この「トリック」を使用する正当な理由はありません。Guavaは静的ファクトリーとビルダーの両方を含む素晴らしい不変のコレクションを提供し、クリーンで読みやすく安全な構文で宣言されている場所にコレクションを追加できます。

質問の例は次のようになります。

Set<String> flavors = ImmutableSet.of(
    "vanilla", "strawberry", "chocolate", "butter pecan");

これは、短くて読みやすいだけでなく、他の回答で説明されている二重ブレースパターンに関する多くの問題を回避します。もちろん、直接作成されたと同様に機能しますHashMapが、危険でエラーが発生しやすく、より優れたオプションがあります。

二重ブレースの初期化を検討している場合は常に、構文トリックを利用するのではなく、APIを再検討するか、新しい APIを導入して問題に適切に対処する必要があります。

エラーが発生しやすくなりましフラグこのアンチパターン


-1。いくつかの有効な点があるにもかかわらず、この答えは「不要な匿名クラスの生成を回避するにはどうすればよいですか?さらに多くのクラスでフレームワークを使用してください!」
Agent_L 2018年

1
つまり、「アプリケーションをクラッシュさせる可能性のあるハックではなく、ジョブに適したツールを使用する」ということになります。Guavaは、アプリケーションに含めるかなり一般的なライブラリです(使用しない場合は間違いなく見逃します)が、それを使用しない場合でも、二重中かっこでの初期化を回避できます。
dimo414

また、二重ブレースの初期化はどのようにしてメモリリークを引き起こしますか?
Angel O'Sphere

@ AngelO'Sphere DBIは、内部クラスを作成する難読化された方法であるため、そのstaticコンテキストを含むクラスへの暗黙的な参照を保持します(コンテキストでのみ使用される場合を除く)。私の質問の下部にあるエラーが発生しやすいリンクで、これについてさらに説明しています。
dimo414

それは好みの問題だと思います。そして、それについては本当に難読化されていません。
Angel O'Sphere

4

私はこれを調査していて、有効な回答で提供されたものよりも詳細なテストを行うことにしました。

これがコードです:https : //gist.github.com/4368924

これが私の結論です

ほとんどの実行テストで、内部の開始が実際に高速だった(一部のケースではほぼ2倍)ことを知って驚いた。大きな数を扱う場合、利点は消えていくようです。

興味深いことに、ループ上に3つのオブジェクトを作成するケースでは、他のケースよりも早く利益がなくなるという利点が失われます。なぜこれが起こっているのかはわかりません。結論を出すにはさらにテストを行う必要があります。具体的な実装を作成すると、クラス定義がリロードされるのを防ぐのに役立つ場合があります(それが起こっている場合)。

ただし、多数の場合でも、単一のアイテムの構築ではほとんどの場合オーバーヘッドがそれほど大きくないことは明らかです。

1つのセットバックは、二重ブレースの開始のそれぞれが、ディスクブロック全体をアプリケーションのサイズ(または圧縮すると約1k)に追加する新しいクラスファイルを作成するという事実です。設置面積は小さいですが、多くの場所で使用すると影響が出る可能性があります。これを1000回使用すると、アプリケーションにMiB全体が追加される可能性があります。これは、組み込み環境で問題になる可能性があります。

私の結論は?乱用されない限り、問題なく使用できます。

どう考えているか教えてください :)


2
これは有効なテストではありません。コードは、オブジェクトを使用せずにオブジェクトを作成します。これにより、オプティマイザはインスタンス全体の作成を省略できます。残っている唯一の副作用は、いずれにしても、これらのテストでオーバーヘッドが他の何よりも優れている乱数シーケンスの進行です。
Holger

3

暗黙のリストをasList(elements)から作成してすぐに投げるのではなくループを使用することを除いて、私はナットの答えを2番目にします。

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T elm: elements) { set.add(elm); }
    return set;
    }

1
どうして?新しいオブジェクトはedenスペースに作成されるため、インスタンス化するには2つまたは3つのポインターを追加するだけで済みます。JVMは、メソッドスコープを超えてエスケープしないことに気づき、スタックに割り当てる場合があります。
Nat

ええ、それはそのコードよりも効率的になる可能性があります(HashSet提案された容量を伝えることで改善できます-負荷係数を覚えておいてください)。
トム・ホーティン-

とにかく、HashSetコンストラクターは反復を実行する必要があるため、効率が低下することはありません。再利用のために作成されたライブラリコードは、常に最高の状態になるように努力する必要があります。
Lawrence Dol、

3

この構文は便利な場合がありますが、this $ 0参照がネストされるため多くのthis $ 0参照も追加され、ブレークポイントがそれぞれに設定されていないと、初期化子にデバッグをステップインすることが困難になる場合があります。そのため、これは、通常のセッター、特に定数に設定したり、匿名のサブクラスが問題にならない場所(シリアル化が必要ないなど)にのみ使用することをお勧めします。


3

Mario Gleichman が、Java 1.5の汎用関数を使用してScalaリストのリテラルをシミュレートする方法について説明しますが、不幸ことに、不変のリストを使用することになります。

彼はこのクラスを定義します:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

そしてそれをこのように使用します:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "java", "list" ) )
            System.out.println( elem );
    }
}

グアバの一部となったGoogleコレクションは、リスト構築の同様のアイデアをサポートしています。ではこのインタビュー、ジャレッド・レヴィ氏は述べています:

[...]私が書いたほとんどすべてのJavaクラスに現れる最も頻繁に使用される機能は、Javaコード内の繰り返しのキーストロークの数を減らす静的メソッドです。次のようなコマンドを入力できるのでとても便利です。

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

7/10/2014:それがPythonのように単純であるとすれば:

animals = ['cat', 'dog', 'horse']

2020年2月21日:Java 11では、次のように言うことができます。

animals = List.of(“cat”, “dog”, “horse”)


2
  1. これはadd()各メンバーを呼びます。アイテムをハッシュセットに入れるより効率的な方法を見つけた場合は、それを使用してください。あなたがそれについて敏感であるならば、内部クラスはおそらくゴミを生成することに注意してください。

  2. コンテキストが返されるオブジェクトであるかのように私には思わnewあり、HashSet

  3. 質問する必要がある場合...より可能性が高い:あなたの後に来る人はこれを知っていますか?理解して説明するのは簡単ですか?両方に「はい」と答えられる場合は、自由に使ってください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.