9. díl - Statika

Java Objektově orientované programování Statika

V minulém tutoriálu ze seriálu o Javě jsme si v praxi vyzkoušeli dědičnost a polymorfismus, dnes se budeme věnovat pojmu statika. Až doposud jsme byli zvyklí, že data (stav) nese instance. Atributy, které jsme definovali, tedy patřily instanci a byly pro každou instanci jedinečné. OOP však umožňuje definovat atributy a metody na samotné třídě. Těmto prvkům říkáme statické (někdy třídní) a jsou nezávislé na instanci.

Pozor na statikuPOZOR! Dnešní lekce vám ukáže statiku, tedy postupy, které v podstatě narušují objektový model. OOP je obsahuje jen pro speciální případy a obecně platí, že vše jde napsat bez statiky. Vždy musíme pečlivě zvážit, zda statiku opravdu nutně potřebujeme. Obecně bych doporučoval statiku vůbec nepoužívat, pokud si nejste naprosto jisti, co děláte. Podobně, jako globální proměnné (které Java naštěstí nemá) je statika v objektovém programování něco, co umožňuje psát špatný kód a porušovat dobré praktiky. Dnes si ji tedy spíše vysvětlíme, abyste pochopili určité metody a třídy v Javě, které ji používají. Znalosti použijte s rozvahou, na světe bude méně zla.

Statické (třídní) atributy

Jako statické můžeme označit různé prvky. Začněme u atributů. Jak jsem se již v úvodu zmínil, statické prvky patří třídě, nikoli instanci. Data v nich uložená tedy můžeme číst bez ohledu na to, zda nějaká instance existuje. V podstatě můžeme říci, že statické atributy jsou společné pro všechny instance třídy, ale není to přesné, protože s instancemi doopravdy vůbec nesouvisí. Založme si nový projekt (název např. Statika) a udělejme si jednoduchou třídu Uzivatel:

class Uzivatel
{
        private String jmeno;
        private String heslo;
        private boolean prihlaseny;

        public Uzivatel(String jmeno, String heslo)
        {
                this.jmeno = jmeno;
                this.heslo = heslo;
                prihlaseny = false;
        }

        public boolean prihlasSe(String zadaneHeslo)
        {
                if (zadaneHeslo.equals(heslo))
                {
                        prihlaseny = true;
                        return true;
                }
                else
                        return false; // hesla nesouhlasí
        }

}

Třída je poměrně jednoduchá, reprezentuje uživatele nějakého systému. Každá instance uživatele má své jméno, heslo a také se o ni ví, zda je přihlášená či nikoli. Aby se uživatel přihlásil, zavolá se na něm metoda prihlasSe() a v jejím parametru heslo, které člověk za klávesnicí zadal. Metoda ověří, zda se jedná opravdu o tohoto uživatele a pokusí se ho přihlásit. Vrátí true/false podle toho, zda přihlášení proběhlo úspěšně. V reálu by se heslo ještě tzv. hashovalo, ale to zde opomineme.

Když se uživatel registruje, systém mu napíše, jakou minimální délku musí jeho heslo mít. Toto číslo bychom měli mít někde uložené. Ve chvíli, kdy uživatele registrujeme, tak ještě nemáme k dispozici jeho instanci. Objekt není vytvořený a vytvoří se až po vyplnění formuláře. Nemůžeme tedy v třídě Uzivatel k tomuto účelu použít veřejný atribut minimalniDelka­Hesla. Samozřejmě by bylo velmi přínosné, kdybychom měli údaj o minimální délce hesla uložený ve třídě Uzivatel, protože k němu logicky patří. Údaj uložíme do statického atributu pomocí modifikátoru static:

class Uzivatel
{
        private String jmeno;
        private String heslo;
        private boolean prihlaseny;

        public static int minimalniDelkaHesla = 6;

        ...

}

Nyní se přesuňme do Statika.java a zkusme si atribut vypsat. K atributu nyní přistoupíme přímo přes třídu:

System.out.println(Uzivatel.minimalniDelkaHesla);

Vidíme, že atribut opravdu náleží třídě. Můžeme se na něj ptát v různých místech programu bez toho, aniž bychom měli uživatele vytvořeného. Naopak na instanci uživatele tento atribut nenalezneme:

Uzivatel u = new Uzivatel("Tomáš Marný", "heslojeveslo");
System.out.println(u.minimalniDelkaHesla);

NetBeans zahlásí chybu a kód se nezkompiluje.

Jako další praktické využití statických atributů se nabízí číslování uživatelů. Budeme chtít, aby měl každý uživatel přidělené unikátní identifikační číslo. Bez znalosti statiky bychom si museli hlídat zvenčí každé vytvoření uživatele a počítat je. My si však můžeme vytvořit přímo na třídě Uzivatel privátní statický atribut dalsiId, kde bude vždy připraveno číslo pro dalšího uživatele. První uživatel bude mít id 1, druhý 2 a tak dále. Uživateli tedy přibude nový atribut id, který se v konstruktoru nastaví podle hodnoty dalsiId. Pojďme si to vyzkoušet:

class Uzivatel
{
        private String jmeno;
        private String heslo;
        private boolean prihlaseny;
        private int id;
        private static int minimalniDelkaHesla = 6;
        private static int dalsiId = 1;

        public Uzivatel(String jmeno, String heslo)
        {
                this.jmeno = jmeno;
                this.heslo = heslo;
                prihlaseny = false;
                id = dalsiId;
                dalsiId++;
        }

        ...

}

Třída si sama ukládá, jaké bude id další její instance. Toto id přiřadíme nové instanci v konstruktoru a zvýšíme ho o 1, aby bylo připraveno pro další instanci. Statické však nemusí být jen atributy, možnosti jsou mnohem větší.

Statické metody

Statické metody se volají na třídě. Jedná se zejména o pomocné metody, které potřebujeme často používat a nevyplatí se nám tvořit instanci. Mnoho takovýchto metod již známe, jen jsme si to neuvědomovali. Nikdy jsme např. netvořili instanci třídy System k tomu, abychom do ní mohli zapisovat. Metoda println() na atributu out na třídě System je statická, stejně jako atribut samotný. System je jen jeden a bylo by zbytečné tvořit si z něj instanci, když jej chceme používat. Podobně je tomu např. u metody round() na třídě Math. Když chceme zaokrouhlit číslo, nebudeme si k tomu přeci tvořit objekt. Jedná se tedy většinou o pomocné metody, kde by instanciace zbytečně zdržovala nebo nedávala smysl.

Ukažme si opět reálný příklad. Při registraci uživatele potřebujeme znát minimální délku hesla ještě před jeho vytvořením. Bylo by také dobré, kdybychom mohli před jeho vytvořením i heslo zkontrolovat, zda má správnou délku, neobsahuje diakritiku, je v něm alespoň jedno číslo a podobně. Za tímto účelem si vytvoříme pomocnou statickou metodu zvalidujHeslo():

public static boolean zvalidujHeslo(String heslo)
{
        if (heslo.length() >= minimalniDelkaHesla)
        {
                // podrobnou logiku validace hesla vynecháme
                return true;
        }
        return false;
}

Opět si zkusíme, že metodu můžeme na třídě Uzivatel zavolat:

System.out.println(Uzivatel.zvalidujHeslo("heslojeveslo"));

Pozor! Díky tomu, že metoda zvalidujHeslo() náleží třídě, nemůžeme v ní přistupovat k žádným instančním atributům. Tyto atributy totiž neexistují v kontextu třídy, ale instance. Ptát se na jmeno by v naší metodě nemělo smysl! Můžete si zkusit, že to opravdu nejde.

Stejné funkčnosti při validaci hesla samozřejmě můžeme dosáhnout i bez znalosti statiky. Vytvořili bychom si nějakou třídu, např. ValidatorUzivatelu a do ní napsali tyto metody. Museli bychom poté vytvořit její instanci, abychom metody mohli volat. Bylo by to trochu matoucí, protože logika uživatele by byla zbytečně rozdělena do dvou tříd, když může být za pomoci statiky pohromadě.

U příkladu se statickým atributem minimalniDelka­Hesla jsme porušili zapouzdření, neměli bychom dovolovat atribut nekontrolovaně měnit. Můžeme ho samozřejmě nastavit jako privátní a k jeho čtení vytvořit statickou metodu. To ostatně dobře známe z minulých dílů. Takovou metodu doplníme i pro navrácení id:

public static int vratMinimalniDelkuHesla()
{
        return minimalniDelkaHesla;
}

public int vratId()
{
        return id;
}

A vyzkoušíme si ještě nakonec naše metody. Metoda main() bude vypadat takto:

Uzivatel u = new Uzivatel("Tomáš Marný", "heslojeveslo");
System.out.printf("ID prvního uživatele: %s\n", u.vratId());
Uzivatel v = new Uzivatel("Olí Znusinudle", "csfd1fg");
System.out.printf("ID druhého uživatele: %s\n", v.vratId());
System.out.printf("Minimální délka hesla uživatele je: %s\n", Uzivatel.vratMinimalniDelkuHesla());
System.out.printf("Validnost hesla \"heslo\" je: %s", Uzivatel.zvalidujHeslo("heslo"));

A výstup bude:

Statické metody a atributy v Javě

Všimněte si, že i metoda main() je statická, program totiž máme jen jeden. Z main() můžeme volat také jen statické metody v hlavní třídě našeho programu. Umíte tedy přidávat metody přímo do výchozí třídy s metodou main(), což však úplně nemá smysl, neboť by se veškerá logika měla odehrávat v zapouzdřených objektech.

Statický konstruktor

Třída může mít i statický konstruktor. Ten se vykoná někdy ve chvíli, kdy se aplikace spustí a třída se zaregistruje k použití. Můžeme ho použít pro přípravné práce, výpočty a podobně. Můžeme v něm podobně jako v instančním konstruktoru vytvořit instance nějakých tříd a uložit si je do statických atributů.

Privátní konstruktor

Pokud se nám vyskytne třída, která obsahuje jen pomocné metody nebo nemá smysl od ni tvořit instance (např. nikdy nebudeme mít 2 konzole), hovoříme o ni někdy jako o statické třídě. Java přímo neumožňuje přímo označit třídu jako statickou, ale tvoření její instance zakážeme pomocí implementace privátního konstruktoru. Takovouto třídu poté nelze instanciovat (vytvořit její instanci). Statická třídá, se kterou jsme se setkali, je např. již zmíněná Math. Zkusme si vytvořit instanci statické třídy Math:

Math m = new Math();

Dostaneme vyhubováno, jelikož má instanciaci zakázanou. Statická třída má všechny prvky statické a tedy nedává smysl od ni tvořit instanci, ta by nic neobsahovala.

Statický registr

Pojďme si takovou jednoduchou statickou třídu vytvořit. Mohlo by se jednat o třídu, která obsahuje jen pomocné metody a atributy (jako Math). Já jsem se však rozhodl vytvořit tzv. statický registr. Ukážeme si, jak je možné předávat důležitá data mezi třídami, aniž bychom museli mít instanci.

Mějme aplikaci, řekněme nějakou větší a rozsáhlejší, např. diář. Aplikace bude obsahovat přepínání jazyka jejího rozhraní, zvolení používaných záložek, složky k ukládání souborů, barevného schématu a ještě třeba zda ji chceme spouštět při spuštění operačního systému. Bude mít tedy nějaká nastavení, ke kterým se bude přistupovat z různých míst programu. Bez znalosti statiky bychom museli všem objektům (kalendáři, úlohám, poznámkám...) předat v konstruktoru v jakém jazyce pracují, případně jim dodat tímto způsobem další nastavení, jako první den v týdnu (neděle/pondělí) a podobně.

Jednou z možností, jak toto řešit, je použít k uložení těchto nastavení statickou třídu. Bude tedy přístupná ve všech místech programu a to i bez vytvoření instance. Obsahovat bude všechna potřebná nastavení, která si z ní budou objekty libovolně brát. Mohla by vypadat např. nějak takto:

class Nastaveni
{
        private static String jazyk = "CZ";
        private static String barevneSchema = "cervene";
        private static boolean spustitPoStartu = true;

        private Nastaveni()
        {
        }

        public static string jazyk()
        {
                return jazyk;
        }

        public static String barevneSchema()
        {
                return barevneSchema;
        }

        public static boolean spustitPoStartu()
        {
                return spustitPoStartu;
        }

}

Všechny atributy i metody musí obsahovat modifikátor static, všimněte si privátního konstruktoru. Záměrně jsem do třídy nedával veřejné atributy, ale vytvořil metody, aby se hodnoty nedaly měnit.

Zkusme si třídu nyní použít, i když program diář nemáme. Vytvoříme si jen na ukázku třídu Kalendar a zkusíme si, že v ní máme opravdu bez problému přístup k nastavení. Vložíme do ni metodu, která vrátí všechna nastavení:

class Kalendar
{

        public String vratNastaveni()
        {
                String s = "";
                s += String.format("Jazyk: %s\n", Nastaveni.jazyk());
                s += String.format("Barevné schéma: %s\n", Nastaveni.barevneSchema());
                s += String.format("Spustit po startu: %s\n", Nastaveni.spustitPoStartu());
                return s;
        }

}

Následně vše vypíšeme do konzole:

Kalendar kalendar = new Kalendar();
System.out.println(kalendar.vratNastaveni());
Statická třída jako statický registr v Javě

Vidíme, že instance kalendáře má opravdu bez problému přístup ke všem nastavením programu.

Opět pozor, tento kód lze nesprávně použít pro předávání nezapouzdřených dat a používá se jen ve specifických situacích. Většina předávání dat do instance probíhá pomocí parametru v konstruktoru, nikoli přes statiku.

Statika se velmi často vyskytuje v návrhových vzorech, o kterých jsme se zde již bavili. Jsou to postupy, které dovádí objektově orientované programování k dokonalosti a o kterých se tu jistě ještě zmíníme. Pro dnešek je toho však již dost :)


 

Stáhnout

Staženo 335x (39.62 kB)
Aplikace je včetně zdrojových kódů v jazyce java

 

  Aktivity (3)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (10 hlasů) :
4.84.84.84.84.8


 


Miniatura
Následující článek
Cvičení k 9. lekci OOP v Javě

 

 

Komentáře

Avatar
Iwitrag
Člen
Avatar
Iwitrag:

Ahoj, u toho výpisu s Tomášem Marným (mimochodem dost dobré :D ) je menší chybička - místo System.out.println má být System.out.format - a za tím %s ještě %n pro odřádkování, ale je to jen drobnost, která snad nikoho nevyvede z míry (teda pokud to poctivě čtou a zkoušejí :D ).

Odpovědět 15.1.2014 19:16
Učím se ostře vidět.
Avatar
Kit
Redaktor
Avatar
Kit:

Proč vlastně v třídě Kalendar vytváříš 7 stringových objektů místo jednoho? Není to plýtvání?

Odpovědět 15.1.2014 19:36
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Iwitrag
David Čápka:

Díky, pak na to mrknu.

Odpovědět  +1 15.1.2014 20:15
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Kit
David Čápka:

Jo, byl by tam lepší jeden, při revizi to upravím.

Odpovědět 15.1.2014 20:16
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
madrlukas
Člen
Avatar
madrlukas:

Statické proměnné a metody lze volat i na instanci, ne pouze na třídě. Sice mě NetBeans předem upozorní(žárovkou vedle řádku), že přistupuju ke statické proměnné, nicméně program se provede bez chyby a vypíše proměnnou/vrátí hodnotu metody. Jelikož je v článku několikrát výslovně uvedeno, že statické metody lze volat pouze na třídě, tak jsem dost zmaten. Měl byste někdo k tomu nějaké vysvětlení? Popřípadě mohl byste mě vyvést z iluze..?

 
Odpovědět  +1 9.4.2015 16:41
Avatar
roman64
Redaktor
Avatar
roman64:

Máš pravdu. Já jsem byl z počátku také zmaten. Na druhou stranu, pokud používáš seznam možností Netbeans (ctrl+space nebo tečka za názvěm proměnné -instance), tak jako možnost ti tu static proměnnou NENABÍDNE!

Řešit by se to dalo přes get:
public static int getMinimalniDel­kaHesla() {return minimalniDelka­Hesla;}

a z main pak volat:
System.out.prin­tf("Minimalni dalka hesla je %s znaku",Uzivatel­.getMinimalni­DelkaHesla()); // metodu volat přes název třídy! (nikoliv instance)

Mělo by to být "in compliance" neboť tuto metodu ti Netbeans nabídne.

Odpovědět 23.6.2015 16:04
osvícený člověk se učí celý život, hlupákovi stačí hodina
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:

No, ale teď jsi jasně i na svém příkladu dokázal, že statická metoda nelze být volána z již vytvořené instance, ale z třídy:)
toto prostě nejde:

...
Uzivatel u = new Uzivatel(..);
u.STATICKA_METODA();
...

a jen tak pár drobností, a hned ta první je na přesdržku ode mě, ale jsem puntičkář:D

Uzivatel u = new Uzivatel("Tomáš Marný", "heslojeveslo");
System.out.printf("ID prvního uživatele: %s\n", u.vratId());
Uzivatel v = new Uzivatel("Olí Znusinudle", "csfd1fg");
System.out.printf("ID druhého uživatele: %s\n", v.vratId());
System.out.printf("Minimální délka hesla uživatele je: %s\n", Uzivatel.vratMinimalniDelkuHesla());
System.out.printf("Validnost hesla \"heslo\" je: %s", Uzivatel.zvalidujHeslo("heslo"));

tady ti chybí v posledním řádku za %s i odřádkování:) pak se výstup neshoduje se vzorem:)

No a potom, když se na to upozrňuje všude, tak proč za celou dobu nikdo nepoznamenal, že ve třídě Nastavení máš malé s u Stringu:

class Nastaveni
{
        private static String jazyk = "CZ";
        private static String barevneSchema = "cervene";
        private static boolean spustitPoStartu = true;

        private Nastaveni()
        {
        }

        public static string jazyk()            <=============== tady tu
        {
                return jazyk;
        }

        public static String barevneSchema()
        {
                return barevneSchema;
        }

        public static boolean spustitPoStartu()
        {
                return spustitPoStartu;
        }

}

jinak pěkné až na ten úvod, říct: "Obecně bych doporučoval statiku vůbec nepoužívat, pokud si nejste naprosto jisti, co děláte".... to platí obecně pro všechno:D a třeba jak jsi i ve svém návodu výstižně podotkl, vytvářet si pomocné třídy (třeba i privátní) proto, abych před třídou něco nastavil nebo aby se mi kalkulovaly instance, tak to by byla zase chyba.
To samé bys mohl říct, že je lepší používat if místo switche, nevím, prostě mi to tak příjde:) možná se pletu, ale to už je pak na každém programátorovi. Pochopitelně druhá věc je, kdyby někdo rval static všude, kde mu to zahlásí chybu, že potřebuje statický atribut/referenci, to pak ano:) ale tyto tutoriály jsou pěkně udělané + názorné příklady, takže počítej spíš s tím, že díky tomuto "vychováš" dobré programátory a ne prasata:D

Odpovědět 15. ledna 14:29
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 7 zpráv z 7.