Vše co jste chtěli vědět o složených závorkách, ale báli jste se zeptat

Stejně jako každý ninja musí prokázat, že dokáže zabít i roličkou toaletního papíru, i já předvedu stupeň své nezměrné zvrhlosti tím, jak dokážu používat složené závorky. Složené závorky asi používáte všichni, ale všichni před ně určitě amatérsky píšete něco jako jméno třídy, metody, if, for nebo něco podobného. To já už jsem dosáhl vyšší úrovně zasvěcení a nepíšu před ně vůbec nic. Předem vás chci upozornit ať to doma radši nezkoušíte, předvedená technika vyžaduje léta tréninku, dřiny a odříkání.

První možností, kde se se závorkou bez ničeho můžete setkat, je inicializační blok. Vypadá to následovně.

public class UglyClass {
	{
		System.out.println("Ugly code");
	}
	{
		System.out.println(" rocks :-/");
	}
}

Krása, že? Ano, opravdu tento kód je v Javě přípustný. Tyto bloky jsou zavolány pokaždé, když je třída instancována a to v pořadí, v jakém jsou uvedeny v kódu. Výhodou má být, že je tento kód sdílený mezi všemi konstruktory. Jenom tiše doufám, že to nikdo nepoužívá. Byla by to opravdu krásná past, schovat podobný blok někam na konec třídy a tam něco ošklivého provést. Dlouho jsem přemýšlel, jestli by existovalo nějaké rozumnější využití. Nebyl by to můj ohavně zvrhlý mozek, kdyby něco nevymyslel. Tady to máte:

		List<Address> addresses = Arrays.asList(
			new Address()
			{
				{
					setStreet("Ugly 13");
					setCity("Brno");
					setZip("000000");
				}
			},
			new Address()
			{
				{
					setStreet("Ugly 15");
					setCity("Brno");
					setZip("000000");
				}
			}
		);

Představte si, že chci nainicalizovat seznam adres (třeba v unit testu) a nemám vhodný konstruktor. Tak si vytvořím anonymního potomka a v jeho inicializačním bloku si nastavím co potřebuji. Cool.

Samozřejmě normální, konzervativní varianta je takováto:

		List<Address> addresses = new ArrayList<Address>(2);
		Address address1 = new Address();
		address1.setStreet("Ugly 13");
		address1.setCity("Brno");
		address1.setZip("000000");
		addresses.add(address1);

		Address address2 = new Address();
		address2.setStreet("Ugly 15");
		address2.setCity("Brno");
		address2.setZip("000000");
		addresses.add(address1);

Nuda co? Navíc má tato varianta jeden velký problém. Často v ní udělám chybu. Jako třeba v uvedeném příkladě. Kdo si té chyby všiml rovnou, má bod, já bych ji tam hodně dlouho hledal.

Abych podobné chybě zabránil, mohu použít druhou možnost, jak napsat osamocené složené závorky. Prostě je fouknu doprostřed metody, čímž vytvořím normální blok kódu.

		List<Address> addresses = new ArrayList<Address>(2);
		{
			Address address1 = new Address();
			address1.setStreet("Ugly 13");
			address1.setCity("Brno");
			address1.setZip("000000");
			addresses.add(address1);
		}
		{
			Address address2 = new Address();
			address2.setStreet("Ugly 15");
			address2.setCity("Brno");
			address2.setZip("000000");
			addresses.add(address2);
		}

Dosáhnu tím toho, že omezím platnost lokálních proměnných. Překladač mě pak nenechá do seznamu vložit stejnou adresu dvakrát. To už je použití, které by se možná i dalo obhájit.

A teď si představte, že člověku jako já, dáte do ruky closures (uzávěry). To by pak museli čtenáře mého kódu rovnou uzavřít do blázince. Aha, už alespoň vím, proč se closures jmenují, tak jak se jmenují.

Pokud máte opravdu silné nervy, podívejte se na tento článek, tam uvidíte, že já jsem v podstatě neškodný.

14 Responses to “Vše co jste chtěli vědět o složených závorkách, ale báli jste se zeptat”

  1. v6ak Says:

    Hustý! Jinak té adesy by šlo dosáhnout i method chainingem?
    BTW: kde je ta chyba?

  2. Ladislav Thon Says:

    Nějak nevidím důvod, proč by používání podobně zvrhlých konstrukcí spolu s lexikálními uzávěry mělo způsobovat větší bolesti hlavy, než kdyby se používaly spolu s anonymními vnitřními třídami, spíš naopak.

    To vytvoření samostatného bloku uvnitř metody je celkem užitečné, inicializátor instance mi přijde ve své odpornosti skoro krásný 🙂

    v6ak: chyba je na posledním řádku... 🙂

  3. Rasto Says:

    Skutočne pekný príklad používania blokov.
    Dá sa tým vyriešiť aj problém dlhých metód, na jednej strane "Príručka mladých svišťov - Metriky kódu" hovorí, že metóda ( alebo skôr blok kódu ) by nemala byť dlhšia ako jednu stranu aby sa dala porozumieť, na druhej strane je blbosť ju rozsekať ju na viac metód, keďže každá z nich je volaná len z jednoho miesta.
    Horeuvedený príklad zlepší čitateľnosť a zachová rozumný design.

    btw: tu chybu som uz urobil niekolkokrat.

  4. ufak Says:

    Pokud kzda inicializace bude v bloku, mohu pouzivat neustale stejne jmeno a nemusim pri kopirovani blbnout s prejmenovanim. Stejne bych mel inicializaci na jeden radek, protoze bych udelal metodu...

  5. Ales Says:

    Tak to ja pouzivam obcas tyto inicializace i jako staticke ( static { ... } ). Napriklad pri nutnosti inicializovat statickou promenou tridy typu HashMap ve tride s pouze privatnim konstruktorem a statickymi metodami. Ale aspon tyhle inicializace pisu na zacatek trid a poradne je oznacim.

    btw: klasicka copy-paste chyba, ktera se blbe hleda.

  6. benzin Says:

    P.S.: tehhle metod se pouziva (obcas) pri inciializaci Enum promenych, kdyz do instance chcete narbat jeste nejake informace.

    Asi to jde udelat i jinak, ale proste se to pouziva.

  7. antaran Says:

    🙂 super clanok, nic nespestri pracovny den tak, ako inicializacny blok v anonymnej triede 🙂

    PS: hadanka 🙂

    public class $pokus {

    public static void main (String...params){

    Integer i1=100, i2=100, i3=5000, i4=5000;

    System.out.println("i1==i2 : "+(i1==i2));
    System.out.println("i3==i4 : "+(i3==i4));

    }
    }

  8. v6ak Says:

    Ladislav Thon: Nevím proč, ale pořád ji nevidím 🙁 setZip? Nebo závorka? Netuším. Já Javu zatím znám víc teoreticky než prakticky.

  9. v6ak Says:

    Aha, ta chyba se týkala konzervativní varianty. Tak to je pak o něčem jiném! Tam už je ta chyba na posledním řádku vidět. 🙂

  10. v6ak Says:

    BTW: Co, když chceme použít konstruktor s parametry? Třeba v J2ME u třídy Alert.

  11. Lukáš Křečan Says:

    To v6ak: Konstruktor s parametry neni problem, inicializacni blok je kompilatorem zkopirovan no kazdeho konstruktoru. Ale znovu upozornuji, ze bych to pouzival opatrne (=vubec). Programatori na to nejsou zvykli.

    To antaran: Kdyz clovek porovnava instance objektu pomoci == tak se nesmi divit

  12. antaran Says:

    To Lukáš Křečan:

    re: == ...je to len hadanka na pobavenie...chcel som vediet, ci niekto len tak zbrucha odpovie spravne 🙂

    re: inicializacne bloky, najdolezitejsie je si uvedomit ako to funguje pri dedicnosti v kombinacii s konstruktormi... 🙂 ... ak toto vies, tak ta nemoze nic prekvapit 🙂

    import static java.lang.System.*;

    class A {
    A(){out.println("CON:A");}
    {out.println("IB:A");}
    static {out.println("SIB:A");}
    }

    class B extends A {
    B(){out.println("CON:B");}
    {out.println("IB:B");}
    static {out.println("SIB:B");}
    }

    public class $pokus2 {

    public static void main (String...params){
    A o = new B();
    }
    }

  13. Lukáš Křečan Says:

    Právě proto by se to mělo používat s rozmyslem (=vůbec). Takovýto kód je možná pěkný v otázce na SCJP, ale v produkčním kódu rozhodně ne.

  14. v6ak Says:

    to Lukáš Křečan: Jasně, ale když vytvořím potomka, tak přece nezdědí konstruktor, ne? Maximálně dostane implicitní konstruktor bez parametrů, který zavolá konstruktor předka. Ale když chci parametry, tak to nestačí.