Efektyvumas „Java“ „Initialize Double Bracket“?

Aukščiausio atsakymo „ Hidden Java“ funkcijose minimas dvigubas petnešų inicijavimas su labai viliojančia sintakse:

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

Ši idioma sukuria anoniminę vidinę klasę, kurioje yra tik egzemplioriaus iniciatorius, kuris „gali naudoti bet kokius metodus [...] turinio srityje“.

Pagrindinis klausimas yra toks: kiek tai neveiksminga ? Ar jos naudojimas turėtų apsiriboti vienkartiniais inicijavimais? (Ir, žinoma, garbinimas!)

Antrasis klausimas: naujasis „HashSet“ turėtų būti „šis“, naudojamas egzemplioriaus iniciatoriuje ... ar kas nors gali apšviesti mechanizmą?

Trečiasis klausimas: ar ši idioma yra pernelyg neaiški, kad ją būtų galima naudoti gamybos kode?

Santrauka: Labai, labai geri atsakymai visiems dėkingi. Klausime (3) žmonės manė, kad sintaksė turėtų būti aiški (nors norėčiau rekomenduoti atsitiktinį komentarą, ypač jei jūsų kodas bus perduotas programuotojams, kurie to nežino).

Klausimu (1) generuojamas kodas turi būti įvykdytas greitai. Papildomi „.class“ failai sukelia painiavą „jar“ faile ir lėtai paleisdami programą (dėka „@coobird“ už tai, kad tai matuojama). @Thilo nurodė, kad gali būti paveiktas šiukšlių surinkimas, o atminties kaina papildomoms pakrautoms klasėms kai kuriais atvejais gali būti veiksnys.

Klausimas (2) pasirodė esąs įdomiausias man. Jei suprantu atsakymus, tai, kas vyksta DBI, yra ta, kad anoniminė vidinė klasė praplečia naujojo operatoriaus sukurto objekto klasę ir todėl turi „tai“ reikšmę, remdamasi sukurtu egzemplioriumi. Labai tvarkingas.

Apskritai, DBI man atrodo kaip intelektualus smalsumas. „Coobird“ ir kiti nurodo, kad jūs galite pasiekti tą patį poveikį su „Arrays.asList“, „varargs“, „Google“ kolekcijomis ir siūlomomis „Java 7“ kolekcijos literatūromis. Naujos „JVM“ kalbos, pvz., „Scala“, „JRuby“ ir „Groovy“, taip pat siūlo trumpą užrašą sąrašo sudarymui ir sąveikai su „Java“. Atsižvelgiant į tai, kad DBI pralenkia klasės kelią, truputį sulėtina klasės pakrovimą ir kodą dar labiau neaiškina, tikriausiai jį būtų išvengta. Vis dėlto aš ketinu jį palikti draugui, kuris ką tik gavo savo SCJP ir myli gera nuoširdus anekdotus apie Java semantiką !;-) Ačiū visiems!

7/2017: „Baeldung“ turi gerą dvigubo laikiklio inicijavimo santrauką ir mano, kad jis yra antikorozinis.

12/2017: @Basil Bourque pažymi, kad naujojoje „Java 9“ galite pasakyti:

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

Tai yra būtent taip. Jei įstrigo ankstesnėje versijoje, žr. „ Google“ kolekcijos „ImmutableSet“ .

726
29 мая '09 в 6:40 2009-05-29 06:40 Jim Ferrans paklausė gegužės 29 d., 09:40, 2009-05-29 06:40
@ 15 atsakymų

Čia yra problema, kai esu pernelyg mėgstamas anoniminėmis vidinėmis klasėmis:

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

Tai yra visos klasės, kurios buvo sukurtos kuriant paprastą programą, ir naudojosi gausiais anoniminių vidinių klasių kiekiais - kiekviena klasė bus sukompiliuota į atskirą class failą.

„Dvigubų skliaustų inicijavimas“, kaip jau minėta, yra anoniminė vidinė klasė su egzemplioriaus inicijavimo bloku, o tai reiškia, kad kiekvienam „inicijavimui“ sukuriama nauja klasė, kuria siekiama sukurti vieną objektą.

Atsižvelgiant į tai, kad „Java“ virtualioji mašina, naudodama jas, turės perskaityti visas šias klases, tai gali sukelti tam tikrą laiką baitekodo verifikavimo procese ir pan. Jau nekalbant apie reikiamo disko vietos padidinimą, kad būtų galima saugoti visus šiuos class failus.

Atrodo, kad naudojant dvigubą nuorodą, yra šiek tiek pridėtinės vertės, todėl tikriausiai nėra tokia gera idėja būti per aukšta. Tačiau, kaip Eddie pastabose paminėjo, neįmanoma visiškai pasitikėti smūgiu.


Nuoroda, dukart skliaustų inicijavimas yra toks:

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

Tai atrodo kaip „paslėpta“ „Java“ funkcija, tačiau tai tik perrašymas:

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

Taigi, iš esmės tai yra atvejo inicijavimo blokas , kuris yra anoniminio pasiūlymo dėl kolekcijų išleidimo į monetų projektą dalis :

 List<Integer> intList = [1, 2, 3, 4]; Set<String> strSet = {"Apple", "Banana", "Cactus"}; Map<String, Integer> truthMap = { "answer" : 42 }; 

Deja, ji neveikė nei „Java 7“, nei 8 ir nebuvo atidėta neribotam laikui.


Eksperimentuokite

Štai paprastas bandymas, kurį aš išbandžiau - padarykite 1000 ArrayList su "Hello" ir "World!" naudojant metodą, naudodami du metodus:

1 metodas: inicijuokite dvigubą laikiklį

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

2 metodas: sukurkite „ ArrayList ir add

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

Sukūriau paprastą programą, skirtą rašyti „Java“ šaltinio failą, kad atliktumėte 1000 inicijavimų naudojant du metodus:

1 bandymas:

 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!"); }};  List<String> l999 = new ArrayList<String>() {{ add("Hello"); add("World!"); }}; System.out.println(System.currentTimeMillis() - st); } } 

2 bandymas:

 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!");  List<String> l999 = new ArrayList<String>(); l999.add("Hello"); l999.add("World!"); System.out.println(System.currentTimeMillis() - st); } } 

Atkreipkite dėmesį, kad praėjusio laiko pradžia 1000 ArrayList ir 1000 anoniminių vidinių klasių, pratęsiančių „ ArrayList , tikrinama naudojant System.currentTimeMillis , todėl laikmatis neturi labai didelės skiriamosios gebos. Mano „Windows“ sistemoje skiriamoji geba yra apie 15–16 milisekundžių.

Dviejų bandymų rezultatai buvo tokie:

 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 

Kaip matote, dvigubos nuorodos inicijavimas turi pastebimą maždaug 190 ms vykdymo laiką.

Tuo tarpu ArrayList inicijavimo laikas ArrayList 0 ms. Žinoma, jums reikia apsvarstyti laikmačio skiriamąją gebą, tačiau tikriausiai jis bus mažesnis nei 15 ms.

Taigi, atrodo, kad šių dviejų metodų vykdymo laikas yra pastebimas. Matyt, dviejuose inicijavimo metoduose yra keletas pridėtinių.

Taip, buvo 1000 .class failų, sukurtų sudarydami Test1 dvigubo susiejimo inicijavimo testavimo programą.

557
29 мая '09 в 6:59 2009-05-29 06:59 atsakymas pateikiamas „ coobird “ gegužės 29 d., 09:59, 2009-05-29 06:59

Vienas iš tokio požiūrio savybių, kuri iki šiol nebuvo nurodyta, yra ta, kad nuo to laiko, kai kuriate vidines klases, visa įtraukta klasė yra įtraukta į jos taikymo sritį. Tai reiškia, kad tol, kol jūsų rinkinys bus gyvas, jis išsaugos žymeklį į turinį ( this$0 ) ir saugo jį nuo šiukšlių surinkimo, o tai gali būti problema.

Tai ir tai, kad sukurta nauja klasė pirmiausia, net jei įprastas „HashSet“ veikia tik gerai (ar net geriau), neleidžia man naudoti šio statinio (nors aš tikrai turiu ilgą sintaksinį cukrų).

Antrasis klausimas: naujasis „HashSet“ turėtų būti „šis“, naudojamas egzemplioriaus iniciatoriuje ... ar kas nors gali apšviesti mechanizmą? Aš naiviai tikiuosi, kad „tai“ reiškia objekto „flavors“ inicijavimą.

Taip veikia vidinės klasės. Jie gauna savo savybes, tačiau jie taip pat turi nuorodų į tėvų instanciją, todėl galite skambinti metodais, kuriuose yra objektas. Vardinio konflikto atveju pirmenybė teikiama vidinei klasei (jūsų atveju HashSet), bet galite įvesti „šį“ su klasės pavadinimu, kad gautumėte išorinį metodą.

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

Kad būtų aišku, kad sukuriate anoniminį poklasį, taip pat galite nustatyti metodus. Pavyzdžiui, nepaisyti „ HashSet.add()

  public Set<String> makeSet() { return new HashSet<String>() { { add("hello"); // not HashSet anymore ... } @Override boolean add(String s){ } }; } 
94
29 мая '09 в 8:37 2009-05-29 08:37 Thilo atsakymą pateikė gegužės 29 d. 09:37 2009-05-29 08:37

Kaskart, kai kažkas naudoja dvigubą ryšį, kačiukas nužudomas.

Be sintaksės, gana neįprastos ir ne labai idiomatinės (skonis, žinoma, yra ginčytinas), jums nereikalingai sukuriamos dvi reikšmingos problemos, kurias neseniai papasakojau apie išsamesnius pranešimus .

1. Jūs sukuriate per daug anoniminių klasių.

Kiekvieną kartą, kai naudojate dvigubą laikiklį, sukuriama nauja klasė. Pavyzdžiui. šis pavyzdys:

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

... sukurs šias klases:

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

Tai yra šiek tiek pridėtinės vertės jūsų klasės krautuvui - jokiu būdu! Žinoma, inicijuoti nereikia ilgai, jei tai darote vieną kartą. Bet jei tai padarysite 20 000 kartų per visą įmonės taikomąją programą ... visa tai atminties krūva tik „sintaksiniam cukrui“?

2. Galima sukurti atminties nuotėkį!

Jei paimsite pirmiau nurodytą kodą ir grąžinate šią kortelę iš metodo, metodai, kuriais remiamasi, gali būti neįtikėtini labai sunkiais ištekliais, kurių negalima surinkti. Apsvarstykite šį pavyzdį:

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

Grąžintame Map dabar bus nuoroda į pridedamą „ ReallyHeavyObject pavyzdį. Jūs tikriausiai nenorite rizikuoti, kad:

2019

41
17 дек. Atsakymą pateikė Lukas Ederis gruodžio 17 d. 2014-12-17 11:56 '14 at 11:56 2014-12-17 11:56

Atsižvelgiant į šią bandymų klasę:

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

ir tada dekompiliuokite klasės failą, matau:

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

Tai man neatrodo siaubingai neveiksminga. Jei buvau susirūpinęs dėl panašių rezultatų, norėčiau pakomentuoti. Anksčiau minėtas kodas atsako į jūsų klausimą Nr. 2: jūs esate numanomo konstruktoriaus (ir instancijos iniciatoriaus) viduje savo vidinei klasei, todėl „ this “ reiškia šią vidinę klasę.

Taip, ši sintaksė yra neaiški, tačiau komentaras gali paaiškinti netiesioginį sintaksės naudojimą. Norėdami išaiškinti sintaksę, dauguma žmonių yra susipažinę su statiniu blokavimo iniciatoriu (JLS 8.7 Static Initializers):

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

Taip pat galite naudoti tą pačią sintaksę (be žodžio " static "), kad galėtumėte naudoti konstruktorių (JLS 8.6 egzempliorių iniciatoriai), nors aš niekada nemačiau to naudoti gamybos kode. Tai yra daug mažiau žinoma.

 public class Sample2 { private final String someVar; // This is an instance initializer { String temp = null; ..... // block of code setting temp someVar = temp; } } 

Jei neturite numatytojo konstruktoriaus, kodo blokas tarp { ir } kompiliatoriaus paverčia konstruktoriumi. Turint tai omenyje, išspręskite dvigubo kodo kodą:

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

Kompiliatorius kodo bloką tarp vidinių skliaustų konvertuoja į konstruktorių. Atokiausi breketai riboja anoniminę vidinę klasę. Norėdami tai padaryti, paskutinis žingsnis yra padaryti viską ne anonimiškai:

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

Norint inicijuoti, norėčiau pasakyti, kad nėra pridėtinės vertės (arba taip mažos, kad jis gali būti ignoruojamas). Tačiau kiekvienas flavors naudojimas neprieštaraus „ HashSet , o vietoj „ MyHashSet . Tai tikriausiai yra maža (ir galbūt nedidelė) viršutinė. Bet tada dar kartą, prieš tai susirūpinęs, norėčiau pakomentuoti.

Vėlgi, į jūsų klausimą Nr. 2, aukščiau nurodytas kodas yra logiškas ir aiškus dvigubo įpareigojimo inicijavimo ekvivalentas, todėl akivaizdu, kur „ this “ reiškia vidinę klasę, kuri išplečia HashSet .

Jei turite klausimų apie pavyzdžių iniciatorių informaciją, žr. JLS dokumentacijos informaciją.

35
29 мая '09 в 6:52 2009-05-29 06:52 atsakymą pateikė Eddie , gegužės 29 d., 09:52, 2009-05-29 06:52

nuotėkio polinkis

Aš nusprendžiau paskambinti. Veiklos efektas apima: disko valdymą + unzip (jar), klasės tikrinimą, perm-gen erdvę („Sun Hotspot“ JVM). Tačiau blogiausia: ji nuteka. Jūs negalite tiesiog grįžti.

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

Taigi, jei rinkinys pereina į kitą dalį, kurią pakrauna kitas klasės krautuvas, ir nuoroda yra saugoma, visas klasės medis + klasės krautuvas bus sunaikintas. Norėdami to išvengti, jums reikia kopijos „HashMap“, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}) . Ne ​​taip mielas. new LinkedHashSet(Arrays.asList("xxx","YYY"));

35
25 янв. atsakymas duotas bestsss 25 sausio. 2011-01-25 13:12 '11, 13:12, 2011-01-25 13:12

Įkeliant daugelį klasių, pradžioje gali būti kelios milisekundės. Jei paleidimas nėra toks svarbus, ir jūs žiūrite į klasių efektyvumą po paleidimo, nėra jokio skirtumo.

 package vanilla.java.perfeg.doublebracket; import java.util.*;  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(); } } 

spausdina

 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 
19
31 янв. Atsakymas, kurį pateikė Peter Lawrey Jan 31 2013-01-31 16:46 '13, 16:46, 2013-01-31 16:46

Jei norite sukurti rinkinius, galite naudoti varargs gamyklos metodą, o ne inicijuoti dvigubą prijungimą

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

„Google“ kolekcijų bibliotekoje yra daug patogių metodų, pvz., Tai, taip pat daugybė kitų naudingų funkcijų.

Kalbant apie besąlygiškumo ikrų, aš su juo susitinku ir nuolat ją panaudoju gamybos kode. Man labiau rūpi programuotojai, kurie supainioti, kai idiomui leidžiama rašyti gamybos kodą.

16
29 мая '09 в 7:20 2009-05-29 07:20 atsakymas pateikiamas „ Nat“ gegužės 29 d., 09:20, 2009-05-29 07:20

Veiksmingumas atmetamas, aš retai noriu sukurti deklaracinę kolekciją už vienetų testų. Manau, kad dvigubo susiejimo sintaksė yra labai suprantama.

Kitas būdas pasiekti deklaracinį sąrašą - naudoti Arrays.asList(T ...) taip:

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

Šio požiūrio apribojimas, žinoma, negali kontroliuoti specifinio sąrašo, kurį reikia generuoti.

9
29 мая '09 в 6:59 2009-05-29 06:59 atsakė Paulius Morie'ui gegužės 29 d., 09:59 2009-05-29 06:59

Nėra nieko ypatingai neveiksmingo. JVM paprastai nesvarbu, ar sukūrėte poklasį ir pridėjote konstruktorių - tai yra įprastas kasdienis dalykas į objektą orientuota kalba. Galiu sugalvoti gana išgalvotus atvejus, kai tai galite padaryti neveiksmingai (pvz., Jūs turite daug kartų vadinamą metodą, kuris dėl šio poklasio susimaišo su skirtingomis klasėmis, o įprasta klasė, kuri praėjo, bus visiškai nuspėjamas - pastaruoju atveju JIT kompiliatorius gali atlikti optimizavimą, kuris neįmanomas pirmoje). Bet iš tikrųjų manau, kad atvejai, kai tai svarbu, yra labai toli.

Labiau pastebiu problemą, kalbant apie tai, ar norite „sugadinti dalykus“ su daugybe anoniminių klasių. Apskaičiuokite, kaip naudoti idiomas ne daugiau kaip, pavyzdžiui, anoniminių renginių tvarkytojų klasių naudojimą.

(2) esate objekto konstruktoriaus viduje, todėl „tai“ reiškia objektą, kurį kuriate. Tai nesiskiria nuo kitų konstruktorių.

Kalbant apie (3), manau, tai tikrai priklauso nuo to, kas palaiko jūsų kodą. Jei tai iš anksto nežinote, bandymas, kurį siūlau naudoti, yra „galite matyti JDK šaltinio kode?“ (šiuo atveju nepamenu nematyti daugelio anoniminių iniciatorių ir, žinoma, ne tais atvejais, kai tai yra vienintelis anoniminės klasės turinys). Labiausiai vidutinio dydžio projektuose norėčiau pasakyti, kad jums iš tikrųjų reikia jūsų programuotojų suprasti JDK šaltinį tam tikru momentu, todėl bet kokia naudojama sintaksė ar idioma yra „sąžiningas žaidimas“. Be to, norėčiau pasakyti, kad mokyti žmones pagal šią sintaksę, jei turite kontroliuoti, kas palaiko kodą, kitaip pakomentuoja ar vengia.

7
29 мая '09 в 8:26 2009-05-29 08:26 atsakymą pateikė Neil Coffey gegužės 29 d. 08:26 2009-05-29 08:26