Co to jest inicjalizacja Double Brace w Javie?

305

Co to jest składnia inicjalizacji Double Brace ( {{ ... }}) w Javie?

sgokhales
źródło
2
Zobacz także stackoverflow.com/q/924285/45935
Jim Ferrans
10
Inicjalizacja Double Brace jest bardzo niebezpieczną funkcją i powinna być używana z rozwagą. Może zerwać równość kontraktu i wprowadzać trudne wycieki pamięci. W tym artykule opisano szczegóły.
Andrii Polunin,

Odpowiedzi:

301

Orteza inicjalizacji tworzy podwójne anonimową klasy pochodzący od określonej klasy (na zewnętrznych wzmocnienia), i stanowi blok initialiser w tej klasie (na wewnętrzne wzmocnienia). na przykład

new ArrayList<Integer>() {{
   add(1);
   add(2);
}};

Zauważ, że efektem tej inicjalizacji podwójnego nawiasu klamrowego jest tworzenie anonimowych klas wewnętrznych. Utworzona klasa ma domyślny thiswskaźnik do otaczającej klasy zewnętrznej. Chociaż zwykle nie jest to problemem, może powodować żal w niektórych okolicznościach, np. Podczas serializacji lub zbierania śmieci, i warto o tym wiedzieć.

Brian Agnew
źródło
11
Dziękujemy za wyjaśnienie znaczenia wewnętrznych i zewnętrznych aparatów ortodontycznych. Zastanawiałem się, dlaczego nagle są dozwolone dwa nawiasy klamrowe o specjalnym znaczeniu, skoro w rzeczywistości są to normalne konstrukcje java, które pojawiają się tylko jako jakaś nowa magiczna sztuczka. Takie rzeczy powodują, że kwestionuję składnię Java. Jeśli nie jesteś już ekspertem, czytanie i pisanie może być bardzo trudne.
jackthehipster
4
„Magiczna składnia” taka jak ta istnieje w wielu językach, na przykład prawie wszystkie języki podobne do C obsługują składnię „idzie do 0” „x -> 0” dla pętli, która jest po prostu „x--> 0” z dziwnym umieszczenie miejsca.
Joachim Sauer
17
Możemy po prostu stwierdzić, że „inicjalizacja podwójnego nawiasu klamrowego” nie istnieje sama w sobie, jest to jedynie połączenie tworzenia anonimowej klasy i bloku inicjalizującego , który po połączeniu wygląda jak konstrukcja składniowa, ale w rzeczywistości nie jest t.
MC Cesarz
Dziękuję Ci! Gson zwraca wartość null, gdy serializujemy coś z podwójną inicjalizacją klamry z powodu anonimowego użycia klasy wewnętrznej.
Pradeep AJ- Msft MVP
294

Za każdym razem, gdy ktoś używa inicjalizacji podwójnego nawiasu klamrowego, kotek ginie.

Oprócz tego, że składnia jest raczej nietypowa i nie bardzo idiomatyczna (smak jest oczywiście dyskusyjny), niepotrzebnie stwarzasz dwa znaczące problemy w swojej aplikacji, o których niedawno pisałem bardziej szczegółowo tutaj .

1. Tworzysz zbyt wiele anonimowych klas

Za każdym razem, gdy używasz inicjalizacji podwójnego nawiasu klamrowego, tworzona jest nowa klasa. Np. Ten przykład:

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

... stworzy te klasy:

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

To dość duże obciążenie dla twojego programu ładującego klasy - na darmo! Oczywiście nie zajmie to dużo czasu inicjalizacji, jeśli zrobisz to raz. Ale jeśli zrobisz to 20 000 razy w całej aplikacji korporacyjnej ... cała ta pamięć sterty tylko na odrobinę cukru składniowego?

2. Potencjalnie tworzysz wyciek pamięci!

Jeśli weźmiesz powyższy kod i zwrócisz mapę z metody, osoby wywołujące tę metodę mogą nieświadomie trzymać się bardzo ciężkich zasobów, których nie można wyrzucać. Rozważ następujący przykład:

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

Zwrócony Mapbędzie teraz zawierał odwołanie do załączającej instancji ReallyHeavyObject. Prawdopodobnie nie chcesz ryzykować, że:

Przeciek pamięci tutaj

Zdjęcie z http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. Możesz udawać, że Java ma literały map

Aby odpowiedzieć na twoje pytanie, ludzie używali tej składni, by udawać, że Java ma coś w rodzaju literałów map, podobnych do istniejących literałów tablic:

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

Niektóre osoby mogą uznać to za stymulujące syntaktycznie.

Lukas Eder
źródło
11
„Tworzysz o wiele za dużo anonimowych klas” - patrząc na to, jak Scala tworzy anonimowe klasy, nie jestem pewien, czy to jest poważny problem
Brian Agnew
2
Czy nie jest to prawidłowy i miły sposób deklarowania map statycznych? Jeśli HashMap zostanie zainicjowany {{...}}i zadeklarowany jako staticpole, nie powinno być żadnego przecieku pamięci, tylko jedna anonimowa klasa i brak odwołań do załączonej instancji, prawda?
lorenzo-s
8
@ lorenzo-s: Tak, 2) i 3) nie mają zastosowania, tylko 1). Na szczęście w Javie 9 jest wreszcie Map.of()ten cel, więc będzie lepsze rozwiązanie
Lukas Eder,
3
Warto zauważyć, że mapy wewnętrzne mają również odniesienia do map zewnętrznych, a zatem pośrednio do ReallyHeavyObject. Również anonimowe klasy wewnętrzne przechwytują wszystkie zmienne lokalne używane w treści klasy, więc jeśli użyjesz nie tylko stałych do zainicjowania kolekcji lub map z tym wzorcem, instancje klasy wewnętrznej przechwycą wszystkie i nadal będą się do nich odwoływać, nawet jeśli faktycznie zostaną usunięte z kolekcja lub mapa. Tak więc w takim przypadku te instancje nie tylko potrzebują dwa razy tyle pamięci, ile potrzeba do referencji, ale mają kolejny wyciek pamięci w tym zakresie.
Holger,
41
  • Pierwszy nawias klamrowy tworzy nową Anonimową klasę wewnętrzną.
  • Drugi zestaw nawiasów tworzy inicjatory instancji, takie jak blok statyczny w klasie.

Na przykład:

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

}

Jak to działa

Pierwszy nawias klamrowy tworzy nową Anonimową klasę wewnętrzną. Te klasy wewnętrzne są w stanie uzyskać dostęp do zachowania swojej klasy nadrzędnej. W naszym przypadku tworzymy podklasę klasy HashSet, więc ta klasa wewnętrzna może używać metody put ().

I drugie zestaw szelki są tylko instancji inicjalizatory. Jeśli przypomnisz podstawowe koncepcje Java, możesz łatwo powiązać bloki inicjatora instancji z inicjalizatorami statycznymi dzięki podobnemu nawiasowi klamrowemu, jak struct. Jedyną różnicą jest to, że inicjator statyczny jest dodawany do słowa kluczowego static i jest uruchamiany tylko raz; bez względu na to, ile obiektów utworzysz.

więcej

Premraj
źródło
24

Zabawna aplikacja inicjalizacji podwójnego nawiasu klamrowego znajduje się tutaj Tablica Dwemthy w Javie .

Fragment

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

A teraz przygotuj się na BattleOfGrottoOfSausageSmellsi… masywny bekon!

akuhn
źródło
16

Myślę, że ważne jest, aby podkreślić, że nie ma czegoś takiego jak „inicjalizacja podwójnego nawiasu klamrowego” w Javie . Witryna Oracle nie ma tego terminu. W tym przykładzie zastosowano dwie funkcje: anonimową klasę i blok inicjujący. Wygląda na to, że stary blok inicjalizujący został zapomniany przez programistów i powoduje pewne zamieszanie w tym temacie. Cytat z dokumentów Oracle :

Bloki inicjalizujące na przykład zmienne wyglądają jak statyczne bloki inicjalizujące, ale bez słowa kluczowego static:

{
    // whatever code is needed for initialization goes here
}
Alex T.
źródło
11

1- Nie ma czegoś takiego jak podwójne nawiasy klamrowe:
Chciałbym zauważyć, że nie ma czegoś takiego jak inicjalizacja podwójnego nawiasu klamrowego. Jest tylko normalny tradycyjny blok inicjujący jeden nawias klamrowy. Drugi blok nawiasów klamrowych nie ma nic wspólnego z inicjalizacją. Odpowiedzi mówią, że te dwa nawiasy inicjują coś, ale tak nie jest.

2- Nie chodzi tylko o anonimowe klasy, ale wszystkie klasy:
prawie wszystkie odpowiedzi mówią, że jest to rzecz używana podczas tworzenia anonimowych klas wewnętrznych. Myślę, że ludzie czytający te odpowiedzi będą mieli wrażenie, że jest to wykorzystywane tylko podczas tworzenia anonimowych klas wewnętrznych. Ale jest stosowany we wszystkich klasach. Czytanie tych odpowiedzi wygląda jak nowa funkcja specjalna poświęcona anonimowym klasom i myślę, że to jest mylące.

3- Celem jest po prostu umieszczenie nawiasów jeden za drugim, a nie nowa koncepcja:
W dalszej części pytanie dotyczy sytuacji, gdy drugi nawias otwierający jest tuż po pierwszym nawiasie otwierającym. Kiedy jest używany w normalnej klasie, zwykle między dwoma nawiasami klamrowymi jest jakiś kod, ale jest to całkowicie to samo. Chodzi więc o umieszczenie nawiasów. Sądzę więc, że nie powinniśmy mówić, że jest to nowa ekscytująca rzecz, ponieważ wszyscy wiemy, ale po prostu napisaliśmy z pewnym kodem między nawiasami. Nie powinniśmy tworzyć nowej koncepcji zwanej „inicjalizacją podwójnego nawiasu klamrowego”.

4- Tworzenie zagnieżdżonych anonimowych klas nie ma nic wspólnego z dwoma nawiasami klamrowymi:
nie zgadzam się z argumentem, że tworzysz zbyt wiele anonimowych klas. Nie tworzysz ich z powodu bloku inicjalizacji, ale tylko dlatego, że je tworzysz. Zostałyby one utworzone, nawet gdybyś nie użył inicjalizacji dwóch nawiasów klamrowych, więc te problemy wystąpiłyby nawet bez inicjalizacji ... Inicjalizacja nie jest czynnikiem tworzącym inicjowane obiekty.

Dodatkowo nie powinniśmy rozmawiać o problemie stworzonym przez użycie tej nieistniejącej rzeczy „inicjalizacji podwójnego nawiasu klamrowego” lub nawet przez normalną inicjalizację jednej nawiasy klamrowej, ponieważ opisane problemy istnieją tylko z powodu utworzenia anonimowej klasy, więc nie ma to nic wspólnego z oryginalnym pytaniem. Ale wszystkie odpowiedzi sprawiają czytelnikom wrażenie, że to nie wina tworzenia anonimowych klas, ale ta zła (nieistniejąca) rzecz zwana „inicjalizacją podwójnego nawiasu klamrowego”.

Ctomek
źródło
9

Aby uniknąć wszystkich negatywnych skutków inicjalizacji podwójnego nawiasu, takich jak:

  1. Zepsuta kompatybilność „równa się”.
  2. Nie wykonuje się żadnych kontroli, gdy używa się bezpośrednich przypisań.
  3. Możliwe wycieki pamięci.

rób kolejne rzeczy:

  1. Utwórz osobną klasę „Builder”, szczególnie do inicjalizacji podwójnego nawiasu klamrowego.
  2. Deklaruj pola z wartościami domyślnymi.
  3. Umieść metodę tworzenia obiektów w tej klasie.

Przykład:

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

Stosowanie:

MyClass my = new MyClass.Builder(){{ first = 1; third = "3"; }}.create();

Zalety:

  1. Po prostu w użyciu.
  2. Nie psuje kompatybilności „równa się”.
  3. Możesz wykonać kontrole metodą tworzenia.
  4. Brak wycieków pamięci.

Niedogodności:

  • Żaden.

W rezultacie mamy najprostszy wzór konstruktora Java.

Zobacz wszystkie próbki na github: java-sf-builder-simple-example

Boris Podchezertsev
źródło
4

Jest to - między innymi zastosowaniami - skrót do inicjowania kolekcji. Ucz się więcej ...

miku
źródło
2
Cóż, to jedna aplikacja, ale w żadnym wypadku nie jedyna.
skaffman
4

masz na myśli coś takiego?

List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}};

jest to inicjalizacja listy tablic w czasie tworzenia (hack)

dhblah
źródło
4

Możesz umieścić kilka instrukcji Java jako pętlę, aby zainicjować kolekcję:

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

Ale ten przypadek wpływa na wydajność, sprawdź tę dyskusję

Anton Dozortsev
źródło
4

Jak wskazał @Lukas Eder, należy unikać podwójnej nawiasy klamrowej inicjalizacji kolekcji.

Tworzy anonimową klasę wewnętrzną, a ponieważ wszystkie klasy wewnętrzne zachowują odwołanie do instancji nadrzędnej, może - i na 99% prawdopodobne - zapobiegnie odśmiecaniu, jeśli do tych obiektów kolekcji odwołuje się więcej obiektów niż tylko deklarująca.

Java 9 wprowadziła metody wygody List.of, Set.oforaz Map.of, które powinny być używane zamiast. Są szybsze i bardziej wydajne niż inicjator z podwójną klamrą.

Michaił Chołodkow
źródło
0

Pierwszy nawias klamrowy tworzy nową klasę anonimową, a drugi zestaw nawiasów tworzy inicjalizatory instancji, takie jak blok statyczny.

Jak zauważyli inni, korzystanie z niego nie jest bezpieczne.

Jednak zawsze możesz użyć tej alternatywy do inicjowania kolekcji.

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");
Ankit Sharma
źródło
-1

Wygląda na to, że jest taki sam jak w przypadku słowa kluczowego with, tak popularnego we flashu i vbscript. To metoda zmiany tego, co thisjest i nic więcej.

Chuck Vose
źródło
Nie całkiem. To tak, jakby powiedzieć, że utworzenie nowej klasy jest metodą zmiany tego, co thisjest. Składnia po prostu tworzy anonimową klasę (więc każde odniesienie do thisniej odnosi się do obiektu tej nowej anonimowej klasy), a następnie używa bloku inicjującego {...}w celu zainicjowania nowo utworzonej instancji.
uśmiechnął się