Lekce 9 - Statika
V předešlém cvičení, Řešené úlohy k 5.-8. lekci OOP v Javě, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Dnes se v tutoriálu 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! 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)) { // kontrola hesla prihlaseny = true; return true; } 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í hodnotu true
nebo
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 minimalniDelkaHesla
.
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:
{JAVA_OOP} {JAVA_MAIN_BLOCK} System.out.println(Uzivatel.minimalniDelkaHesla); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} class Uzivatel { private String jmeno; private String heslo; private boolean prihlaseny; public static int minimalniDelkaHesla = 6; public Uzivatel(String jmeno, String heslo) { this.jmeno = jmeno; this.heslo = heslo; prihlaseny = false; } public boolean prihlasSe(String zadaneHeslo) { if (zadaneHeslo.equals(heslo)) { // kontrola hesla prihlaseny = true; return true; } return false; // hesla nesouhlasí } } {/JAVA_OOP}
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.
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
na hodnotě 1,
druhý id
na hodnotě 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ý. Třída
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) { // podrobnou logiku validace hesla vynecháme return heslo.length() >= minimalniDelkaHesla; }
Opět si zkusíme, že metodu můžeme na třídě Uzivatel
zavolat:
{JAVA_OOP} {JAVA_MAIN_BLOCK} System.out.println(Uzivatel.zvalidujHeslo("heslojeveslo")); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} 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++; } public boolean prihlasSe(String zadaneHeslo) { if (zadaneHeslo.equals(heslo)) { // kontrola hesla prihlaseny = true; return true; } return false; // hesla nesouhlasí } public static boolean zvalidujHeslo(String heslo) { // podrobnou logiku validace hesla vynecháme return heslo.length() >= minimalniDelkaHesla; } } {/JAVA_OOP}
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 atribut 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 minimalniDelkaHesla
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í atributu 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:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Uzivatel tomas = new Uzivatel("Tomáš Marný", "heslojeveslo"); System.out.printf("ID prvního uživatele: %s%n", tomas.vratId()); Uzivatel oli = new Uzivatel("Olí Znusinudle", "csfd1fg"); System.out.printf("ID druhého uživatele: %s%n", oli.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")); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} 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++; } public boolean prihlasSe(String zadaneHeslo) { if (zadaneHeslo.equals(heslo)) { // kontrola hesla prihlaseny = true; return true; } return false; // hesla nesouhlasí } public static boolean zvalidujHeslo(String heslo) { // podrobnou logiku validace hesla vynecháme return heslo.length() >= minimalniDelkaHesla; } public static int vratMinimalniDelkuHesla() { return minimalniDelkaHesla; } public int vratId() { return id; } } {/JAVA_OOP}
A výstup bude:
Konzolová aplikace
ID prvního uživatele: 1
ID druhého uživatele: 2
Minimální délka hesla uživatele je: 6
Validnost hesla "heslo" je: false
Všimněte si, že i metoda main()
je statická, program totiž
máme jen jeden. Z metody 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.
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řída, se kterou
jsme se setkali, je např. již zmíněná třída Math
. Zkusme si
vytvořit instanci statické třídy Math
:
Math matika = 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.
Některé jazyky podporují i statický konstruktor, který se potom zavolá ve chvíli, kdy je třída zaregistrována. Java statické konstruktory nepodporuje. Pokud některé statické atributy na třídě potřebují inicializaci, můžeme pro ně vytvořit statickou inicializační metodu, kterou na třídě poté někdy za začátku programu zavoláme.
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 třída
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í, barevného schématu a 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í), jaké je barevné schéma 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 vratJazyk() { return jazyk; } public static String vratBarevneSchema() { return barevneSchema; } public static boolean vratSpustitPoStartu() { 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 nastaveni = ""; nastaveni += String.format("Jazyk: %s%n", Nastaveni.vratJazyk()); nastaveni += String.format("Barevné schéma: %s%n", Nastaveni.vratBarevneSchema()); nastaveni += String.format("Spustit po startu: %s%n", Nastaveni.vratSpustitPoStartu()); return nastaveni; } }
Následně vše vypíšeme do konzole:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Kalendar kalendar = new Kalendar(); System.out.println(kalendar.vratNastaveni()); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} class Nastaveni { private static String jazyk = "CZ"; private static String barevneSchema = "cervene"; private static boolean spustitPoStartu = true; private Nastaveni() { } public static String vratJazyk() { return jazyk; } public static String vratBarevneSchema() { return barevneSchema; } public static boolean vratSpustitPoStartu() { return spustitPoStartu; } } {/JAVA_OOP}
{JAVA_OOP} class Kalendar { public String vratNastaveni() { String nastaveni = ""; nastaveni += String.format("Jazyk: %s%n", Nastaveni.vratJazyk()); nastaveni += String.format("Barevné schéma: %s%n", Nastaveni.vratBarevneSchema()); nastaveni += String.format("Spustit po startu: %s%n", Nastaveni.vratSpustitPoStartu()); return nastaveni; } } {/JAVA_OOP}
Konzolová aplikace
Jazyk: CZ
Barevné schéma: cervene
Spustit po startu: true
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 .
V následujícím cvičení, Řešené úlohy k 9. lekci OOP v Javě, si procvičíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 631x (20.11 kB)
Aplikace je včetně zdrojových kódů v jazyce Java