Java'nın “Çift Ayraç Başlatma” nın Etkinliği?


823

In Java Gizli Özellikleri üst cevabı bahseder Çift Ayraç Başlatma bir ile, çok cazip sözdizimi:

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

Bu deyim, içinde yalnızca "içerme kapsamındaki herhangi bir [...] yöntemi kullanabilen" bir örnek başlatıcısı olan anonim bir iç sınıf oluşturur.

Ana soru: Bu göründüğü kadar verimsiz mi? Kullanımı bir kerelik başlatmalarla sınırlandırılmalı mıdır? (Ve elbette gösteriş!)

İkinci soru: Yeni HashSet, örnek başlatıcıda kullanılan "bu" olmalıdır ... herkes mekanizmaya ışık tutabilir mi?

Üçüncü soru: Bu deyim üretim kodunda kullanmak için çok belirsiz mi?

Özet: Çok, çok güzel cevaplar, herkese teşekkürler. Soru (3) üzerinde, insanlar sözdiziminin açık olması gerektiğini hissettiler (özellikle de kodunuz size aşina olmayabilecek geliştiricilere geçecekse, ara sıra bir yorum öneririm).

Soru (1) 'de, oluşturulan kod hızlı bir şekilde çalışmalıdır. Ekstra .class dosyaları jar dosyası dağınıklığına ve programın yavaş başlatılmasına neden olur (bunu ölçmek için @coobird sayesinde). @Thilo, çöp toplama işleminin etkilenebileceğine ve fazladan yüklenmiş sınıfların bellek maliyetinin bazı durumlarda bir faktör olabileceğine dikkat çekti.

Soru (2) benim için en ilginç oldu. Yanıtları anlarsam, DBI'de olan şey, anonim iç sınıfın yeni işleç tarafından oluşturulan nesnenin sınıfını genişletmesidir ve bu nedenle, oluşturulan örneği referans alan bir "bu" değere sahip olmasıdır. Çok temiz.

Genel olarak, DBI bana entelektüel bir merak olarak bakıyor. Coobird ve diğerleri, Arrays.asList, varargs yöntemleri, Google Collections ve önerilen Java 7 Collection değişmez değerleri ile aynı etkiyi elde edebileceğinizi belirtiyor. Scala, JRuby ve Groovy gibi daha yeni JVM dilleri de liste yapımı için kısa gösterimler sunar ve Java ile birlikte çalışır. DBI sınıf yolunu kümeler, sınıf yükleme biraz yavaşlatır ve kodu biraz daha karanlık hale getirir, muhtemelen ondan uzak utangaç. Bununla birlikte, bunu SCJP'sini yeni alan ve Java semantiği hakkında iyi huylu jousts seven bir arkadaşa yaymayı planlıyorum! ;-) Herkese teşekkürler!

7/2017: Baeldung , çift ​​ayraç başlatmanın iyi bir özetine sahip ve bunu bir anti-desen olarak görüyor.

12/2017: @Basil Bourque, yeni Java 9'da şunları söyleyebileceğinizi söylüyor:

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

Bu kesin yol. Önceki bir sürüme sahipseniz Google Koleksiyonlar'ın ImmutableSet'ine göz atın .


33
Burada gördüğüm kod koku naif okuyucu beklenir yani, flavorsbir olmak HashSet, ama ne yazık ki o anonim bir alt sınıfıdır.
Elazar Leibovich

6
Performansı yüklemek yerine koşmayı düşünüyorsanız fark yoktur, cevabım bakın.
Peter Lawrey

4
Bir özet oluşturduğunuzu seviyorum, bunun hem anlayışınızı hem de toplumu artırmanız için değerli bir egzersiz olduğunu düşünüyorum.
Patrick Murphy

3
Bence bu karanlık değil. Okuyucular bilmeli zaten söyledi @ElazarLeibovich çift ... o bekleme, onun comment . Çift ayraç başlatıcısının kendisi bir dil yapısı olarak mevcut değildir, sadece anonim bir alt sınıf ve bir örnek başlatıcısının birleşimidir. Tek şey, insanların bunun farkında olması gerektiğidir.
MC İmparatoru

8
Java 9, bazı durumlarda DCI kullanımının yerini alabilecek Değişmez Set Statik Fabrika Yöntemleri sunar :Set<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
Basil Bourque

Yanıtlar:


607

Anonim iç sınıflarla çok fazla uğraştığımda sorun şu:

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

Bunlar basit bir uygulama yaparken oluşturulan sınıflardır ve çok sayıda anonim iç sınıf kullanmışlardır - her sınıf ayrı bir classdosyada derlenecektir .

"Çift ayraç başlatma", daha önce de belirtildiği gibi, örnek başlatma bloğu olan anonim bir iç sınıftır, yani her "başlatma" için, genellikle tek bir nesne yapmak amacıyla yeni bir sınıf oluşturulur.

Java Virtual Machine'in bunları kullanırken tüm bu sınıfları okuması gerekeceği göz önüne alındığında, bu bytecode doğrulama sürecinde bir süre sürebilir . Tüm bu classdosyaları saklamak için gerekli disk alanındaki artıştan bahsetmiyorum bile .

İkili parantez başlatmayı kullanırken biraz ek yük var gibi görünüyor, bu yüzden muhtemelen onunla çok fazla gitmek iyi bir fikir değil. Ancak Eddie'nin yorumlarda belirttiği gibi, etkiden kesinlikle emin olmak mümkün değil.


Sadece referans olarak, çift ayraç başlatma şu şekildedir:

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

Java'nın "gizli" bir özelliğine benziyor, ancak aşağıdakilerin yeniden yazılması:

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

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

Bu temelde anonim bir iç sınıfın parçası olan bir örnek başlatma bloğudur .


Joshua Bloch'un Koleksiyon Paraları Projesi için Project önerisi şu şekildeydi :

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

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

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

Ne yazık ki, ne Java 7 ne de 8'e girmedi ve süresiz olarak rafa kaldırıldı.


Deney

İşte test ettik basit bir deney - Marka 1000 ArrayListunsurlarla ler "Hello"ve "World!"üzeri onlara eklenen addyöntemle, iki yöntemi kullanarak:

Yöntem 1: Çift Brace Başlatma

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

Yöntem 2: Bir ArrayListveadd

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

İki yöntemi kullanarak 1000 başlatma gerçekleştirmek için bir Java kaynak dosyası yazmak için basit bir program oluşturdum:

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

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

Lütfen not, geçen zaman 1000 başlatmak için olduğunu ArrayLists 1000 anonim iç sınıflar uzanan ArrayListkullanılarak kontrol edilir System.currentTimeMilliszamanlayıcı çok yüksek çözünürlüğe sahip değildir, bu yüzden. Windows sistemimde çözünürlük yaklaşık 15-16 milisaniyedir.

İki testin 10 çalışması için sonuçlar aşağıdaki gibidir:

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

Görülebileceği gibi, çift ayraç başlatma, 190 ms civarında gözle görülür bir yürütme süresine sahiptir.

Bu arada, ArrayListbaşlatma yürütme süresi 0 ms olarak ortaya çıktı. Tabii ki, zamanlayıcı çözünürlüğü dikkate alınmalıdır, ancak muhtemelen 15 ms'nin altında olması muhtemeldir.

Yani, iki yöntemin uygulama süresinde gözle görülür bir fark var gibi görünüyor. İki başlatma yönteminde gerçekten de bazı ek yükler olduğu görülmektedir.

Ve evet, çift ​​ayraç başlatma test programı .classderlenerek oluşturulan 1000 dosya vardı Test1.


10
"Muhtemelen" operatif kelimedir. Ölçülmedikçe, performansla ilgili hiçbir ifade anlamlı değildir.
Örnek Avcısı

16
Böyle harika bir iş çıkardın, bunu söylemek zor, ama Test1 zamanlarına sınıf yükleri hakim olabilirdi. Birinin 1.000 kez bir for döngüsünde her testin tek bir örneğini çalıştırdığını görmek, daha sonra 1.000 veya 10.000 kez döngü için bir saniye içinde tekrar çalıştırmak ve zaman farkını yazdırmak ilginçtir (System.nanoTime ()). Döngü için ilk tüm ısınma etkilerini (JIT, sınıf yükü, örneğin) geçmelidir. Her iki test de farklı kullanım durumlarını modellemektedir. Bunu yarın iş yerinde çalıştırmaya çalışacağım.
Jim Ferrans

8
@Jim Ferrans: Test1 zamanlarının sınıf yüklerinden geldiğinden oldukça eminim. Ancak, çift ayracı başlatma kullanmanın sonucu sınıf yükleriyle başa çıkmak zorunda kalmaktadır. Bence çift ayraç init için çoğu kullanım durumu. bir kerelik başlatma içindir, test, bu tip başlatma işleminin tipik bir kullanım durumuna daha yakındır. Her testin çoklu yinelemelerinin yürütme süresi boşluğunu daha küçük hale getireceğine inanıyorum.
coobird

73
Bunun kanıtladığı şey, a) çift ayracı başlatma işleminin daha yavaş olması ve b) 1000 kez yapsanız bile, muhtemelen farkı fark etmeyeceksiniz. Ve bu bir iç döngüdeki darboğaz da olabilir gibi değil. ÇOK KÖTÜDE bir kerelik küçük bir ceza uygular.
Michael Myers

15
DBI kullanmak kodu daha okunaklı veya anlamlı hale getirirse kullanın. JVM'nin gerçekleştirmesi gereken işi biraz arttırması, kendi başına, buna karşı geçerli bir argüman değildir. Öyleyse, daha az yöntemle büyük sınıfları tercih ederek ekstra yardımcı yöntemler / sınıflar hakkında endişelenmeliyiz ...
Rogério

105

Şimdiye kadar işaret edilmemiş olan bu yaklaşımın bir özelliği, iç sınıflar yarattığınız için, içerdiği tüm sınıfın kendi kapsamında yakalanmasıdır. Bu, Setiniz hayatta olduğu sürece, içeren örneğe ( this$0) bir işaretçi tutacağı ve bunun çöp toplanmasına engel olacağı anlamına gelir; bu bir sorun olabilir.

Bu ve düzenli bir HashSet iyi çalışsa bile (ya da daha iyisi) ilk etapta yeni bir sınıfın yaratılması, bu yapıyı kullanmak istemediğimi (sözdizimsel şekeri gerçekten özlememe rağmen).

İkinci soru: Yeni HashSet, örnek başlatıcıda kullanılan "bu" olmalıdır ... herkes mekanizmaya ışık tutabilir mi? Ben safça "bu" "tatlar" başlatan nesneye başvurmak beklenebilirdi.

İç sınıflar böyle çalışır. Kendilerine sahipler this, ancak üst nesneye de işaretçileri var, böylece içeren nesnede de yöntemleri çağırabilirsiniz. Bir adlandırma çakışması durumunda, iç sınıf (sizin durumunuzda HashSet) öncelik kazanır, ancak dış yöntemi de almak için "this" ifadesini bir sınıf adıyla önek olarak ekleyebilirsiniz.

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

Oluşturulan anonim alt sınıfta açık olmak için, orada da yöntemler tanımlayabilirsiniz. Örneğin geçersiz kılmaHashSet.add()

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

            @Override
            boolean add(String s){

            }

        };
    }

5
İçeren sınıfa gizli referans üzerinde çok iyi bir nokta. Özgün örnekte, örnek başlatıcısı Test.this.add () yerine yeni HashSet <String> öğesinin add () yöntemini çağırıyor. Bu bana başka bir şeyin olduğunu gösteriyor. Nathan Kitchen'ın önerdiği gibi HashSet <String> için anonim bir iç sınıf var mı?
Jim Ferrans

Kapsayıcı sınıfa yapılan atıf, veri yapısının serileştirilmesi söz konusu olduğunda da tehlikeli olabilir. Kapsayıcı sınıf da serileştirilecek ve bu nedenle Serileştirilebilir olmalıdır. Bu, belirsiz hatalara yol açabilir.
Başka bir Java programcısı

56

Birisi çift ayraç başlatmayı her kullandığında, bir yavru kedi öldürülür.

Dışında sözdiziminden (tat tabii ki tartışılır) oldukça sıradışı ve gerçekten deyimsel olmama, gereksiz yere uygulamanızda iki önemli sorunlara, yaratıyor Sadece son zamanlarda burada daha ayrıntılı olarak blogged ettik .

1. Çok fazla anonim sınıf oluşturuyorsunuz

Çift ayraç başlatmayı her kullandığınızda yeni bir sınıf oluşturulur. Örneğin bu örnek:

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

... şu sınıfları üretecek:

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

Sınıf yükleyiciniz için biraz yük - bu hiçbir şey için! Tabii ki bir kez yaparsanız çok fazla başlatma süresi almayacaktır. Ama bunu kurumsal uygulamanız boyunca 20.000 kez yaparsanız ... tüm bu yığın bellek sadece "sözdizimi şekeri" için?

2. Potansiyel olarak bir bellek sızıntısı oluşturuyorsunuz!

Yukarıdaki kodu alıp bu haritayı bir yöntemden döndürürseniz, o yöntemin arayanları şüphesiz çöp toplanamayan çok ağır kaynaklara tutunuyor olabilir. Aşağıdaki örneği düşünün:

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

Döndürülen Mapartık, ekini içeren örneğine bir başvuru içerecektir ReallyHeavyObject. Muhtemelen bunu riske atmak istemezsiniz:

Burada Bellek Sızıntısı

Http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/ adresinden resim

3. Java'nın harita değişmez değerleri olduğunu iddia edebilirsiniz

Asıl sorunuzu cevaplamak için, insanlar Java'nın mevcut dizi değişmezlerine benzer bir harita değişmez değerleri gibi olduğunu iddia etmek için bu sözdizimini kullanıyorlar:

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

Bazı insanlar bunu sözdizimsel olarak uyarıcı bulabilir.


7
Yavru kedileri kurtar! İyi cevap!
Aris2World

36

Aşağıdaki test sınıfını alarak:

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

ve sonra sınıf dosyasını ayrıştırarak şunu görüyorum:

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

Bu bana çok verimsiz gelmiyor. Böyle bir şey için performans konusunda endişeliysem, profil yapardım. Ve sorunuz # 2 yukarıdaki kodla cevaplanıyor: İç sınıfınız için örtük bir kurucu (ve örnek başlatıcı) içindesiniz, bu nedenle " this" bu iç sınıfa atıfta bulunuyor.

Evet, bu sözdizimi belirsizdir, ancak bir yorum belirsiz sözdizimi kullanımını netleştirebilir. Sözdizimini açıklığa kavuşturmak için, çoğu kişi statik bir başlatıcı bloğuna (JLS 8.7 Statik Başlatıcılar) aşinadır:

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

Yapımcı kullanımı için benzer bir sözdizimi de (" static" kelimesi olmadan ) kullanabilirsiniz (JLS 8.6 Örnek Başlatıcıları), ancak bunu üretim kodunda kullanıldığını hiç görmedim. Bu çok daha az bilinir.

public class Sample2 {
    private final String someVar;

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

Varsayılan bir kurucunuz yoksa, {ve arasındaki kod bloğu }derleyici tarafından bir kurucuya dönüştürülür. Bunu aklınızda tutarak, çift ayraç kodunu çözün:

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

En içteki ayraçlar arasındaki kod bloğu derleyici tarafından bir kurucuya dönüştürülür. En dıştaki parantezler anonim iç sınıfı sınırlandırır. Bunu her şeyi anonim hale getirmenin son adımı olarak almak için:

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

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

Başlatma amaçları için, hiçbir ek yük (ya da ihmal edilebilecek kadar küçük) olmadığını söyleyebilirim. Ancak, her kullanım yerine karşı flavorsdeğil, karşı olacaktır . Muhtemelen buna küçük (ve muhtemelen ihmal edilebilir) bir ek yük vardır. Ama yine de, bu konuda endişelenmeden önce, profil yapardım.HashSetMyHashSet

Yine, soru # 2'ye göre, yukarıdaki kod, çift ayraç başlatmanın mantıksal ve açık eşdeğeridir ve " this" ifadesinin nerede olduğu açıkça belirtilir : Genişleyen iç sınıfa HashSet.

Örnek başlatıcılarının ayrıntıları hakkında sorularınız varsa, JLS belgelerindeki ayrıntıları inceleyin .


Eddie, çok güzel bir açıklama. JVM bayt kodları ayrıştırma kadar temizse, yürütme hızı yeterince hızlı olacaktır, ancak ekstra .class dosya dağınıklığı hakkında biraz endişeliyim. Örnek başlatıcısının yapıcısının neden "this" i Test örneği değil, yeni HashSet <String> örneği olarak gördüğünü merak ediyorum. Bu, deyimi desteklemek için en son Java Dil Spesifikasyonunda açıkça belirlenmiş bir davranış mı?
Jim Ferrans

Cevabımı güncelledim. Karışıklığa neden olan Test sınıfının ortak plakasını dışarıda bıraktım. Her şeyi daha açık hale getirmek için cevabımı verdim. Ayrıca bu deyimde kullanılan örnek başlatıcı blokları için JLS bölümünden de bahsediyorum.
Eddie

1
@Jim "Bu" nun yorumu özel bir durum değildir; sadece HashSet <String> 'in anonim alt sınıfı olan en içteki sınıfın örneğini ifade eder.
Nathan Kitchen

Dört buçuk yıl sonra atladığım için üzgünüm. Ancak decompiled sınıf dosyası (ikinci kod bloğunuz) hakkında güzel bir şey, geçerli bir Java olmamasıdır! It has super()örtülü yapıcı ikinci satır olarak, ama ilk gelmek zorunda. (Test ettim ve derlenmeyecek.)
chiastic-security

1
@ chiastic-security: Bazen ayrıştırıcılar derlenmeyecek kodlar oluşturur.
Eddie

35

sızıntı eğilimli

Ben karar verdim. Performans etkisi içerir: disk işlemi + unzip (kavanoz için), sınıf doğrulama, perma-gen alanı (Sun'ın Hotspot JVM için). Ancak, en kötüsü: sızıntıya eğilimli. Geri dönemezsiniz.

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

Dolayısıyla, küme farklı bir sınıf yükleyici tarafından yüklenen herhangi bir parçaya kaçarsa ve burada bir referans tutulursa, sınıfların + sınıf yükleyicinin tüm ağacı sızdırılır. Bundan kaçınmak için HashMap'in bir kopyası gereklidir new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}). Artık o kadar sevimli değil. Deyimi kullanmıyorum, kendim yerine new LinkedHashSet(Arrays.asList("xxx","YYY"));


3
Neyse ki, Java 8'den itibaren PermGen artık bir şey değil. Sanırım hala bir etkisi var, ama potansiyel olarak çok belirsiz bir hata mesajına sahip olan biri değil.
Joey

2
@Joey, bellek doğrudan GC (perm gen) tarafından yönetiliyorsa veya yönetilmiyorsa sıfır fark yaratır. Metaspace'deki bir sızıntı hala bir sızıntıdır, meta sınırlı olmadığı sürece linux'daki oom_killer gibi şeyler tarafından bir OOM (perm gen dışında) olmayacak.
bestsss

19

Birçok sınıfın yüklenmesi başlangıca milisaniye ekleyebilir. Başlangıç ​​çok kritik değilse ve başlangıçtan sonra sınıfların verimliliğine bakıyorsanız, hiçbir fark yoktur.

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

baskılar

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 aşırı kullanılırsa PermGen alanınızın buharlaşması dışında bir fark yoktur. En azından, PermGen alanının sınıf boşaltma ve çöp toplanmasına izin vermek için bazı belirsiz JVM seçenekleri ayarlamazsanız . Java'nın sunucu tarafındaki bir dil olarak yaygınlığı göz önüne alındığında, bellek / PermGen sorunu en azından bir sözü garanti eder.
2015'te

1
@aroth bu iyi bir nokta. Ben Java üzerinde 16 yıl boyunca hiçbir zaman PermGen (veya Metaspace) ayarlamak zorunda bir sistem üzerinde çalışmadım itiraf ediyorum kod boyutu üzerinde çalıştığım sistemler için her zaman makul küçük tutuldu.
Peter Lawrey

2
Koşullar yerine bir compareCollectionsaraya getirilmemeli mi? Kullanmak sadece anlamsal olarak yanlış görünmekle kalmaz, aynı zamanda sadece ilk koşul test edileceğinden performansı ölçme niyetine karşı koyar. Ayrıca, akıllı bir iyileştirici, yinelemeler sırasında koşulların asla değişmeyeceğini anlayabilir. ||&&&&
Holger

@aroth tıpkı bir güncelleme olarak: Java 8'den beri VM artık herhangi bir perm-gen kullanmıyor.
Angel O'Sphere

16

Kümeler oluşturmak için çift ayraç başlatma yerine varargs fabrika yöntemini kullanabilirsiniz:

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

Google Koleksiyonlar kütüphanesinde bunun gibi pek çok kolaylık yöntemi ve birçok faydalı işlev vardır.

Deyimin belirsizliğine gelince, onunla karşılaşıyorum ve üretim kodunda her zaman kullanıyorum. Üretim kodunu yazmasına izin verilen deyim tarafından karışan programcılar hakkında daha fazla endişelenirim.


Hah! ;-) Aslında 1.2 gün Java dönen bir Rip van Winkle değilim (ben VoiceXML ses web tarayıcısını yazdı evolution.voxeo.com Java). Eğlenceli öğrenme jenerikleri, parametreli tipler, Koleksiyonlar, java.util.concurrent, döngü sözdizimi için yeni, vb. Şimdi daha iyi bir dil. Demek istediğim, DBI'nin arkasındaki mekanizma ilk başta belirsiz görünse de, kodun anlamı oldukça açık olmalıdır.
Jim Ferrans

10

Verimlilik bir yana, kendimi nadiren birim testler dışında bildirici toplama oluşturmak istiyoruz. Çift ayraç sözdiziminin çok okunabilir olduğuna inanıyorum.

Listelerin bildirici yapımını özellikle elde etmenin bir başka yolu, şu şekilde kullanmaktır Arrays.asList(T ...):

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

Bu yaklaşımın sınırlaması elbette oluşturulacak belirli liste türünü kontrol edememenizdir.


1
Arrays.asList () normalde kullanacağım şey, ama haklısın, bu durum esas olarak birim testlerde ortaya çıkıyor; gerçek kod listeleri DB sorguları, XML ve benzerlerinden oluşturur.
Jim Ferrans

7
Yine de asList'e dikkat edin: döndürülen liste eleman eklemeyi veya kaldırmayı desteklemez. AsList'i her kullandığımda, ortaya çıkan listeyi new ArrayList<String>(Arrays.asList("vanilla", "strawberry", "chocolate"))bu sorunu aşmak gibi bir kurucuya geçiriyorum .
Michael Myers

7

Genellikle bu konuda özellikle verimsiz bir şey yoktur. JVM için genellikle bir alt sınıf oluşturup bir kurucu eklediğiniz önemli değildir - bu, nesne yönelimli bir dilde yapılacak normal, günlük bir şeydir. Bunu yaparak verimsizliğe neden olabileceğiniz oldukça tartışmalı vakaları düşünebilirim (örneğin, bu alt sınıf nedeniyle farklı sınıfların bir karışımını alan tekrar tekrar denilen bir yönteminiz var, oysa geçen sınıf tamamen tahmin edilebilir - - ikinci durumda, JIT derleyicisi birincisinde mümkün olmayan optimizasyonlar yapabilir). Ama gerçekten, bunun önemli olacağı davaların çok çelişkili olduğunu düşünüyorum.

Bu konuyu daha çok anonim sınıfla "işleri karıştırmak" isteyip istemediğinizi daha yakından görüyorum. Kaba bir kılavuz olarak, deyimi olay işleyicileri için anonim sınıflar kullandığınızdan daha fazla kullanmamayı düşünün.

(2) 'de, bir nesnenin yapıcısının içindesiniz, bu nedenle "bu" oluşturduğunuz nesneye karşılık gelir. Bu diğer kuruculardan farklı değil.

(3) gelince, bu gerçekten kodunuzu kimin koruduğuna bağlıdır, sanırım. Bunu önceden bilmiyorsanız, kullanmanızı önereceğim bir ölçüt "JDK'nın kaynak kodunda görüyor musunuz?" (bu durumda, anonim sınıfın tek içeriğinin olduğu durumlarda kesinlikle anonim başlatıcıları gördüğümü hatırlamıyorum ). Çoğu orta ölçekli projede, programcılarınızın JDK kaynağını bir noktada anlamaları için gerçekten ihtiyaç duyacağınızı iddia ediyorum, bu nedenle kullanılan herhangi bir sözdizimi veya deyim "adil oyun". Bunun ötesinde, eğer kodu kimin koruduğunu kontrol ediyorsanız, başka bir yorumda bulunmayın veya kaçının.


5

Çift ayraç başlatma, bellek sızıntıları ve diğer sorunları ortaya çıkarabilecek gereksiz bir hack'tir.

Bu "hileyi" kullanmanın meşru bir nedeni yoktur. Guava, hem statik fabrikaları hem de inşaatçıları içeren güzel değişmez koleksiyonlar sağlar ve koleksiyonunuzu temiz, okunabilir ve güvenli bir sözdiziminde bildirildiği yere yerleştirmenizi sağlar .

Sorudaki örnek şu şekildedir:

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

Bu sadece daha kısa ve daha kolay okunmakla kalmaz, aynı zamanda diğer cevaplarda açıklanan çift destekli modelle ilgili sayısız sorunu önler . Elbette, doğrudan yapılandırılmışa benzer bir performans sergiliyor HashMap, ancak tehlikeli ve hataya eğilimli ve daha iyi seçenekler var.

Kendinizi çift bağlantılı başlatmayı düşündüğünüzde , sözdizimsel hilelerden yararlanmak yerine API'larınızı yeniden incelemeli veya sorunu doğru bir şekilde çözmek için yenilerini tanıtmalısınız .

Error-Prone şimdi bu anti-paterni işaretlemektedir .


-1. Bazı geçerli noktalara rağmen, bu cevap "Gereksiz anonim sınıf üretmekten nasıl kaçınılır? Daha fazla sınıfla bir çerçeve kullanın!"
Agent_L

1
"Uygulamanızı çökertecek bir kesmek yerine, iş için doğru aracı kullanmak" için kaynar olduğunu söyleyebilirim. Guava, uygulamaların dahil edilmesi için oldukça yaygın bir kütüphanedir (kullanmıyorsanız kesinlikle kaçırırsınız), ancak kullanmak istemeseniz bile çift ayraç başlatmayı önleyebilirsiniz.
dimo414

Ve çift parantez başlatması tam olarak bir meory sızıntısına neden olur mu?
Angel O'Sphere

@ AngelO'Sphere DBI, bir iç sınıf yaratmanın gizlenmiş bir yoludur ve bu nedenle çevreleyen sınıfına (yalnızca staticbağlamlarda hiç kullanılmadığı sürece) örtük bir referans tutar . Sorumun altındaki Hataya eğilimli bağlantı bunu daha ayrıntılı tartışıyor.
dimo414

Bunun bir zevk meselesi olduğunu söyleyebilirim. Ve gerçekten bu konuda şaşkın bir şey yok.
Angel O'Sphere

4

Bunu araştırıyordum ve geçerli cevaptan daha derinlemesine bir test yapmaya karar verdim.

İşte kod: https://gist.github.com/4368924

ve bu benim sonucum

Çalışma testlerinin çoğunda iç inisiyasyonun aslında daha hızlı olduğunu (bazı durumlarda neredeyse iki kat) bulduğuna şaşırdım. Büyük sayılarla çalışırken fayda kayboluyor gibi görünüyor.

İlginç bir şekilde, döngüde 3 nesne oluşturan durum, diğer durumlarda olduğundan daha kısa sürede faydasını kaybeder. Bunun neden gerçekleştiğinden emin değilim ve herhangi bir sonuca varmak için daha fazla test yapılması gerekiyor. Somut uygulamalar oluşturmak, sınıf tanımının yeniden yüklenmesini önlemeye yardımcı olabilir (eğer böyle oluyorsa)

Bununla birlikte, tek eşya yapımı için çoğu durumda, çok sayıda bile olsa, fazla yükün gözlemlenmediği açıktır.

Geri dönüşlerden biri, çift ayraç başlatmalarının her birinin, uygulamamızın boyutuna (veya sıkıştırıldığında yaklaşık 1k) tüm bir disk bloğu ekleyen yeni bir sınıf dosyası oluşturmasıdır. Küçük bir alan kaplar, ancak birçok yerde kullanılırsa potansiyel olarak bir etkisi olabilir. Bu 1000 kez kullanın ve potansiyel olarak size gömülü bir ortamla ilgili olabilecek bir bütün MiB eklersiniz.

Kanımca? Kötüye kullanılmadığı sürece kullanmak uygun olabilir.

Ne düşündüğü söyle :)


2
Bu geçerli bir test değil. Kod, nesneleri kullanmadan nesneler oluşturur, bu da optimize edicinin tüm örnek oluşturmayı seçmesine olanak tanır. Geriye kalan tek yan etki, yükü zaten bu testlerde başka bir şeyden ağır basan rastgele sayı dizisinin ilerlemesidir.
Holger

3

Ben NatL cevabını ikinci, cevap oluşturmak ve derhal asList (elements) örtülü Liste savurmak yerine kullanmak istiyorum:

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

1
Neden? Yeni nesne eden alanında oluşturulacak ve bu nedenle somutlaştırmak için yalnızca iki veya üç işaretçi eklemesi gerekiyor. JVM hiçbir zaman yöntem kapsamının dışına çıkmadığını fark edebilir ve bu yüzden onu yığına tahsis eder.
Nat

Evet, bu koddan daha verimli olması muhtemeldir ( HashSetönerilen kapasiteyi söyleyerek geliştirebilirsiniz - yük faktörünü hatırlayın).
Tom Hawtin - çakmak hattı

HashSet yapıcısı yinelemeyi yine de yapmak zorunda, bu yüzden daha az verimli olmayacak . Yeniden kullanım için oluşturulan kütüphane kodu her zaman mümkün olan en iyi şekilde çalışmaya çalışmalıdır.
Lawrence Dol

3

Bu sözdizimi uygun olsa da, bunlar iç içe geçtikçe bu 0 $ referansın çoğunu ekler ve her birinde kesme noktaları ayarlanmadığı sürece başlatıcılara hata ayıklama yapmak zor olabilir. Bu nedenle, bunu yalnızca sabitler olarak ayarlanmış banal setterler ve anonim alt sınıfların önemli olmadığı yerler (serileştirme dahil değil) için kullanmanızı öneririm.


3

Mario Gleichman , ne yazık ki değişmez Listeler ile sarılsanız da, Scala List değişmezlerini simüle etmek için Java 1.5 genel işlevlerinin nasıl kullanılacağını açıklar .

Bu sınıfı tanımlar:

package literal;

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

ve bu şekilde kullanır:

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

Artık Guava'nın bir parçası olan Google Collections, liste yapımı için benzer bir fikri destekliyor. Gelen bu görüşmeden Jared diyor Levy:

[...] Yazdığım hemen hemen her Java sınıfında görünen en çok kullanılan özellikler, Java kodunuzdaki tekrarlanan tuş vuruşlarının sayısını azaltan statik yöntemlerdir. Aşağıdaki gibi komutları girebilmek çok kolay:

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

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

7/10/2014: Keşke Python'lar kadar basit olsaydı:

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

21.02.2020: Java 11'de artık şunu söyleyebilirsiniz:

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


2
  1. Bu add()her üye için çağrıda bulunacaktır . Öğeleri bir karma kümesine koymanın daha verimli bir yolunu bulabilirseniz, bunu kullanın. Bu konuda hassassanız, iç sınıfın muhtemelen çöp üreteceğini unutmayın.

  2. Bana öyle geliyor ki bağlam döndürülen nesne new, yani HashSet.

  3. Eğer sormanız gerekiyorsa ... Daha olası: sizden sonra gelen insanlar bunu biliyor mu bilmiyor mu? Anlamak ve açıklamak kolay mı? Her ikisine de "evet" cevabını verebiliyorsanız, çekinmeyin.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.