Co to jest składnia inicjalizacji Double Brace ( {{ ... }}
) w Javie?
Co to jest składnia inicjalizacji Double Brace ( {{ ... }}
) w Javie?
Odpowiedzi:
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 this
wskaź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ć.
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 .
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?
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 Map
będzie teraz zawierał odwołanie do załączającej instancji ReallyHeavyObject
. Prawdopodobnie nie chcesz ryzykować, że:
Zdjęcie z http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
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.
{{...}}
i zadeklarowany jako static
pole, nie powinno być żadnego przecieku pamięci, tylko jedna anonimowa klasa i brak odwołań do załączonej instancji, prawda?
Map.of()
ten cel, więc będzie lepsze rozwiązanie
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.
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.
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 BattleOfGrottoOfSausageSmells
i… masywny bekon!
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
}
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”.
Aby uniknąć wszystkich negatywnych skutków inicjalizacji podwójnego nawiasu, takich jak:
rób kolejne rzeczy:
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:
Niedogodności:
W rezultacie mamy najprostszy wzór konstruktora Java.
Zobacz wszystkie próbki na github: java-sf-builder-simple-example
Jest to - między innymi zastosowaniami - skrót do inicjowania kolekcji. Ucz się więcej ...
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));
}
};
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.of
oraz Map.of
, które powinny być używane zamiast. Są szybsze i bardziej wydajne niż inicjator z podwójną klamrą.
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.
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> list = List.of("A", "B", "C");
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 this
jest i nic więcej.
this
jest. Składnia po prostu tworzy anonimową klasę (więc każde odniesienie do this
niej odnosi się do obiektu tej nowej anonimowej klasy), a następnie używa bloku inicjującego {...}
w celu zainicjowania nowo utworzonej instancji.