10. diel - Gettery a settery v Jave
V predchádzajúcom cvičení, Riešené úlohy k 9. lekcii OOP v Jave, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.
Dnes sa v tutoriále pozrieme na tzv. gettery a settery.
Gettery a settery
Veľmi často sa nám stáva, že chceme mať kontrolu nad zmenami nejakého
atribútu objektu zvonku. Budeme chcieť atribút nastaviť ako
read-only (len na čítanie) alebo reagovať na jeho zmeny. Založme si
nový projekt s názvom napr. GetSet
a vytvorme nasledujúcu triedu
Student
, ktorá bude reprezentovať študenta v nejakom
informačnom systéme:
class Student { public String name; public boolean male; public int age; public boolean fullAged; public Student(String name, boolean male, int age) { this.name = name; this.male = male; this.age = age; fullAged = true; if (age < 18) { fullAged = false; } } @Override public String toString() { String iAmFullAged = "I'm"; if (!fullAged) { iAmFullAged = "I'm not"; } String gender = "male"; if (!male) { gender = "female"; } return String.format("I'm %s, %s. I'm %d years old and %s of age.", name, gender, age, iAmFullAged); } }
Trieda je veľmi jednoduchá, študent sa nejako volá, je nejakého pohlavia
a má určitý vek. Podľa tohto veku sa nastavuje atribút
fullAged
pre pohodlnejšie vyhodnocovanie plnoletosti na rôznych
miestach systému. Na uloženie pohlavia používame hodnotu typu
boolean
, či je študent muž. Konštruktor podľa veku určí, či
je študent plnoletý. Metóda toString()
je pre potreby tutoriálu
navrhnutá tak, aby nám vypísala všetky informácie. V reáli by vrátila
pravdepodobne len meno študenta. Pomocou konštruktoru si nejakého študenta
vytvorme:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Student student = new Student("Peter Brown", true, 20); System.out.println(student); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} class Student { public String name; public boolean male; public int age; public boolean fullAged; public Student(String name, boolean male, int age) { this.name = name; this.male = male; this.age = age; fullAged = true; if (age < 18) { fullAged = false; } } @Override public String toString() { String iAmFullAged = "I'm"; if (!fullAged) { iAmFullAged = "I'm not"; } String gender = "male"; if (!male) { gender = "female"; } return String.format("I'm %s, %s. I'm %d years old and %s of age.", name, gender, age, iAmFullAged); } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
I'm Peter Brown, male. I'm 20 years old and I'm of age.
Všetko vyzerá pekne, ale atribúty sú prístupné ako na čítanie, tak na zápis. Objekt teda môžeme rozbiť napríklad takto (hovoríme o nekonzistentnom vnútornom stave):
{JAVA_OOP} {JAVA_MAIN_BLOCK} Student student = new Student("Peter Brown", true, 20); student.age = 15; student.male = true; System.out.println(student); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} class Student { public String name; public boolean male; public int age; public boolean fullAged; public Student(String name, boolean male, int age) { this.name = name; this.male = male; this.age = age; fullAged = true; if (age < 18) { fullAged = false; } } @Override public String toString() { String iAmFullAged = "I'm"; if (!fullAged) { iAmFullAged = "I'm not"; } String gender = "male"; if (!male) { gender = "female"; } return String.format("I'm %s, %s. I'm %d years old and %s of age.", name, gender, age, iAmFullAged); } } {/JAVA_OOP}
Výstup:
Konzolová aplikácia
I'm Peter Brown, male. I'm 15 years old and I'm of age.
Určite musíme ošetriť, aby sa plnoletosť obnovila pri zmene veku. Keď
sa zamyslíme nad ostatnými atribútmi, nie je dôvod, aby sme taktiež
umožňovali modifikovať pohlavie. Bolo by však zároveň vhodné ich
vystaviť na čítanie, nemôžeme ich teda iba nastaviť ako
private
. V skorších dieloch seriálu sme na tento účel
používali metódy, ktoré slúžili na čítanie privátnych atribútov. Ich
názov sme volili ako getAge()
a pre nastavenie atribútu
napríklad setAge()
. V Jave sa všetky atribúty, s ktorými sa má
pracovať zvonku, označujú ako privátne a pre prístup k nim sa definujú
práve podobné metódy. Na ich pomenovanie sa ustálilo
getFieldName()
na čítanie a setFieldName()
na
zápis. Ak je atribút typu boolean
, volá sa getter
isFieldName()
. Trieda by teraz vyzerala napr. takto:
class Student { private String name; private boolean male; private int age; private boolean fullAged; public Student(String name, boolean male, int age) { this.name = name; this.male = male; setAge(age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isMale() { return male; } public int getAge() { return age; } public void setAge(int age) { this.age = age; // updating whether student is of age fullAged = age >= 18; } public boolean isFullAged() { return fullAged; } @Override public String toString() { String iAmFullAged = "I'm"; if (!fullAged) { iAmFullAged = "I'm not"; } String gender = "male"; if (!male) { gender = "female"; } return String.format("I'm %s, %s. I'm %d years old and %s of age.", name, gender, age, iAmFullAged); } }
Metódy, čo hodnoty iba vracajú, sú veľmi jednoduché. Nastavenie veku
má už nejakú vnútornú logiku, pri jeho zmene musíme totiž prehodnotiť
atribút fullAged
. Zaistili sme, že sa do premenných nedá
zapisovať inak, než chceme my. Máme teda pod kontrolou všetky zmeny
atribútov a dokážeme na ne reagovať. Nemôže sa stať, že by nám niekto
vnútorný stav nekontrolovane zmenil a rozbil. Všimnite si aj zmeny v
konštruktore, kde sa nastavuje vek metódou setAge()
.
Metódam na vrátenie hodnoty sa hovorí gettery a metódam
pre zápis settery. Pre editáciu ostatných atribútov by sme
urobili jednu metódu editStudent()
, ktorá by bola podobná
konštruktoru.
Otázkou je, aká je teraz výhoda toho, že je atribút name
privátny, keď do neho ide zapisovať a možno z neho aj čítať. Veď máme v
kóde zbytočne 2 metódy, ktoré tam zaberajú miesto a ešte je to aj
pomalé?
Naozaj sme to napísali správne, alebo aspoň tak, ako sa to bežne robí. Java pred kompiláciou vykonáva početné optimalizácie a ak sú metódy tak jednoduché, že iba vracajú hodnotu alebo ju nastavujú, metóda sa skompiluje ako priamy prístup do pamäte. Sú teda rovnako rýchle, ako keby sme pracovali s verejným atribútom (za predpokladu, že setter alebo getter nemá nejakú ďalšiu logiku).
IDE dokáže gettery a settery automaticky generovať, nemusíme ich teda otrocky prepisovať. Stačí na privátnu premennú kliknúť pravým tlačidlom a zvoliť položku Refactor -> Encapsulate fields:
V ďalšom dialógu si zaškrtneme, ku ktorým atribútom chceme vygenerovať
gettery a settery. My nebudeme chcieť sprístupniť pre zápis atribúty
fullAged
a male
. Atribút fullAged
pôjde
zmeniť iba zmenou veku študenta. Pohlavie nedáva zmysel meniť vôbec (ak by
to bolo naozaj niekedy potrebné, bola by na to nejaká špeciálna metóda, aby
sa vylúčila zmena chybou v kóde). Dialóg potvrdíme:
Settery pre atribúty fullAged
a
male
z triedy teda odstránime.
Optimalizácia pred kompiláciou odstránila rýchlostný problém, ktorý by inak zbytočné volanie metód spôsobovalo. Aj zbytočné písanie metód sme vykompenzovali ich automatickým generovaním. Otázkou zostáva, v čom je písanie metód oproti atribútom výhodnejšie.
Hlavným dôvodom je určitá štandardizácia. Nemusíme premýšľať nad
tým, či je daná vlastnosť objektu riešená cez getter alebo atribút, na
inštanciu jednoducho vždy voláme metódu začínajúcu slovom
get
(prípadne is
), ak chceme vlastnosť inštancie
čítať, prípadne metódu začínajúcu set
, ak ju chceme
zmeniť.
Ďalšou výhodou je, že keď sa v budúcnosti rozhodneme, že nejaký atribút chceme urobiť read-only (len na čítanie), jednoducho zmažeme setter. Nemusíme vytvárať getter a meniť viditeľnosť atribútu, čo by zmenilo rozhranie triedy a rozbilo existujúci kód, ktorý by ju používal.
Gettery a settery teda budeme odteraz používať pri všetkých
atribútoch, ktoré majú byť zvonku prístupné. V našich triedach
sa takmer nebudú vyskytovať atribúty s viditeľnosťou
public
.
Skúsme si teraz ešte spustiť kód, ktorý predtým rozbil interný stav objektu:
{JAVA_OOP} {JAVA_MAIN_BLOCK} Student student = new Student("Peter Brown", true, 20); student.setAge(15); // s.setMale(false); // This line has to be commented out since it's no longer possible to change the gender System.out.println(student); {/JAVA_MAIN_BLOCK} {/JAVA_OOP}
{JAVA_OOP} class Student { private String name; private boolean male; private int age; private boolean fullAged; public Student(String name, boolean male, int age) { this.name = name; this.male = male; setAge(age); } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isMale() { return male; } public int getAge() { return age; } public void setAge(int age) { this.age = age; // updating whether student is of age fullAged = age >= 18; } public boolean isFullAged() { return fullAged; } @Override public String toString() { String iAmFullAged = "I'm"; if (!fullAged) { iAmFullAged = "I'm not"; } String gender = "male"; if (!male) { gender = "female"; } return String.format("I'm %s, %s. I'm %d years old and %s of age.", name, gender, age, iAmFullAged); } } {/JAVA_OOP}
Výstup je už v poriadku:
Konzolová aplikácia
I'm Peter Brown, male. I'm 15 years old and I'm not of age.
V nasledujúcom kvíze, Kvíz - Dedičnosť, statika, gettery a settery v Jave OOP, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 15x (2.8 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Java