Java 4
Tvorenie objektov, referenčné premenné a objekty
Cieľom kapitoly je oboznámiť sa s procesom
vytvárania objektu, s procesom inicializácie statických premenných a premenných
inštancie. Krátke varovanie o práci s referenčnými premennými. Implicitný balík.
Na to, aby bol objekt nejakej triedy vytvorený a aby všetky jeho členské premenné boli inicializované, je potrebná špeciálna metóda. V Jave je touto metódou konštruktor. Táto metóda musí mať rovnaký názov, ako je názov príslušnej triedy. Ak žiaden konštruktor nevytvoríme sami, Java vytvorí implicitný konštruktor ( tvaru MojaTrieda(), t. j. – logicky – je to metóda bez argumentov ). Samozrejme, ako chceme, aby boli členské premenné novovytvoreného objektu inicializované podľa našich predstáv, musíme vhodný konštruktor, alebo viacero konštruktorov, definovať sami.
Prezrite si nasledujúci kód a sledujte najprv definíciu konštruktorov. Potom si prezrite prvé štyri riadky z metódy main a výpis, zodpovedajúci týmto riadkom. Z príkladu vyplýva, že aj pri konštruktoroch môžeme použiť takzvané preťaženie metódy. T. j. definujeme konštruktory s tým istým názvom, ale rôznymi parametrami. Konštruktor sa od ostatných metód líši okrem mena ešte jednou vecou – nedeklarujeme pri ňom návratový typ.
Kód
P4.1
// Objekty
public class Slovo {
// ako sa pise?
private String text;
// ake je dlhe?
private int dlzka;
// prislusne gety, pripripade sety....
public String getText(){
return text;
}
public void setText(String text){
this.text = text;
dlzka = text.length();
}
public int getLength(){
return dlzka;
}
// konstruktor
public Slovo() {
System.out.println("Slovo, konstruktor bez
argumentov.");
}
public Slovo(String obsah){
text = obsah;
dlzka = text.length();
System.out.println("Konstruktor Slovo
vytvoril slovo s textom \" " + text +
"\" s dlzkou " +dlzka);
}
public String napisOdzadu(){
StringBuffer otoc = new StringBuffer(text);
otoc = otoc.reverse();
return otoc.toString();
}
public String toString(){
return text;
}
public static void main(String[] args){
Slovo pes
= new Slovo("bernardin");
Slovo dravec = new Slovo("tiger");
System.out.println(dravec);
System.out.println(pes);
System.out.println("dlzka slova " + pes
+ " je " + pes.getLength());
// referencna premenna pes zacne ukazovat na iny objekt
pes = dravec;
System.out.println("dlzka slova " + pes
+ " je
" + pes.getLength());
// pozor, ked sa zmeni "dravec", zmeni sa aj
"pes"
// ide o jeden a ten isty objekt
dravec.setText("lev");
System.out.println("dlzka slova " + pes
+ "
je " + pes.getLength());
// dalsia zmena aky
bude vytlacenz text?
pes = new Slovo();
System.out.println("dlzka slova " + pes
+ " je " + pes.getLength());
}//EOMain
}//EOClass
Výpis z programu:
Konstruktor Slovo vytvoril
slovo s textom " bernardin" s dlzkou 9
Konstruktor Slovo vytvoril
slovo s textom " tiger" s dlzkou 5
tiger
bernardin
dlzka slova bernardin je 9
dlzka slova tiger je 5
dlzka slova lev je 3
Slovo, konstruktor bez
argumentov.
dlzka slova null je 0
Počas písania programu, počas práce
s referenčnou premennou treba mať stále na pamäti, na aký objekt
premenná ukazuje. Na jeden a ten istý objekt môže ukazovať viacero
premenných. Ak sa objekt akýmkoľvek zásahom zmení, zmenu zbadáme, nech ho
voláme ktoroukoľvek referenčnou premennou, ktorá naň ukazuje.
Na
ilustráciu tohto faktu si pozrite si záver metódy main v predchádzajúcom
kóde a takisto výpis z programu. Interpretujte jednotlivé výpisy.
Výhody práce s objektami
Práca s objektami umožňuje vačšiu pružnosť,
než len práca s “lineárnym kódom“
ktorý sa opiera o dopredu definované funkcie, podprogramy. V kóde P3.5 sa písal krátky príbeh pomocou metód.
Pozrime sa na podobné písanie príbehu pomocou objektov. Tentoraz budeme pracovať až s dvoma
triedami. Kód P4.1 a Kód P4.2 umiestnite do príslušných súborov
Slovo.java a .java a umiestnite
ich do osobitného, spoločného adresára. Tam ich oba odlaďte a spustite
program NapisRozpravku.
(V nasledujúcom kóde by bolo asi pre
programátora pohodlnejšie pracovať s objektami typu String a nie
Slovo, ale neskôr triedu slovo ešte rozvinieme a tým aj možnosti písania
príbehov.)
Kód P4.2
public
class NapisRozpravku{
// povodne nastavenie
Slovo[] podstatneMena = new Slovo[]
{new Slovo("Princ"),new Slovo("Laktibrada"),new
Slovo("Tarzan"),new Slovo("Popolvar" )};
Slovo[] prislovky = new
Slovo[]{new Slovo("pomaly"), new Slovo("udatne"),new
Slovo("ospalo"),new Slovo("vynachadzavo"),new Slovo("dovtipne")};
Slovo[] slovesa = new Slovo[]{new Slovo("zaspal"),new
Slovo("zvolal"),new Slovo("zaspieval"),new
Slovo("bojoval"),new Slovo("obedoval")};
Slovo[] zaverecnyZnak = new Slovo[] {new Slovo("."),new
Slovo("?"),new Slovo("!"),new Slovo("...")};
// prislusne gety
/*
public Slovo[] getPodstatneMena(){
return podstatneMena;
}
public Slovo[] getPrislovky(){
return prislovky;
}
public Slovo[] getSlovesa(){
return slovesa;
}
public Slovo[] getZaverecnyZnak(){
return zaverecnyZnak;
}*/
public NapisRozpravku(){
}
// konstruktor, ktory nastavenie
upravi
public NapisRozpravku(Slovo[] podst,
Slovo[] prisl,Slovo[] sloves, Slovo[] znak){
System.out.println("Hlasi sa
konstruktor triedy \" Napis rozpravku\"");
podstatneMena = podst;
prislovky = prisl;
slovesa = sloves;
zaverecnyZnak = znak;
}
// metoda, ktora vytlaci nahodne 1
prvok z pola typu Slovo[]
public void tlacNahodne(Slovo[]
slova){
int dlzka = slova.length;
int nahodnyIndex = (int)(Math.random()*dlzka);
System.out.print(slova[nahodnyIndex]);
}
// metoda, ktora vytlaci rozpravku
public void pis(){
System.out.println();
tlacNahodne(podstatneMena);
System.out.print(" ");
tlacNahodne(prislovky);
System.out.print(" ");
tlacNahodne(slovesa);
tlacNahodne(zaverecnyZnak);
System.out.println( "\n");
}
public static void main(String[]
args){
NapisRozpravku bard = new
NapisRozpravku();
bard.pis();
bard = new
NapisRozpravku(new Slovo[]{new Slovo("Snehulienka"),new
Slovo("Popoluska")}, new Slovo[]{new Slovo("ladne"),new
Slovo("spanilo")}, new Slovo[]{new Slovo("priadla"),new
Slovo("zametala")}, new Slovo[]{new Slovo(".")});
bard.pis();
}//EOMain
}//EOClas
Jeden
z možných výstupov programu je:
Konstruktor
Slovo vytvoril slovo s textom " Princ" s dlzkou 5
Konstruktor
Slovo vytvoril slovo s textom " Laktibrada" s dlzkou 10
Konstruktor
Slovo vytvoril slovo s textom " Tarzan" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " Popolvar" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " pomaly" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " udatne" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " ospalo" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " vynachadzavo" s dlzkou 12
Konstruktor
Slovo vytvoril slovo s textom " dovtipne" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " zaspal" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " zvolal" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " zaspieval" s dlzkou 9
Konstruktor
Slovo vytvoril slovo s textom " bojoval" s dlzkou 7
Konstruktor
Slovo vytvoril slovo s textom " obedoval" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " ." s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " ?" s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " !" s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " ..." s dlzkou 3
Tarzan
udatne zvolal...
Konstruktor
Slovo vytvoril slovo s textom " Snehulienka" s dlzkou 11
Konstruktor
Slovo vytvoril slovo s textom " Popoluska" s dlzkou 9
Konstruktor
Slovo vytvoril slovo s textom " ladne" s dlzkou 5
Konstruktor
Slovo vytvoril slovo s textom " spanilo" s dlzkou 7
Konstruktor
Slovo vytvoril slovo s textom " priadla" s dlzkou 7
Konstruktor
Slovo vytvoril slovo s textom " zametala" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " ." s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " Princ" s dlzkou 5
Konstruktor
Slovo vytvoril slovo s textom " Laktibrada" s dlzkou 10
Konstruktor
Slovo vytvoril slovo s textom " Tarzan" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " Popolvar" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " pomaly" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " udatne" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " ospalo" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " vynachadzavo" s dlzkou 12
Konstruktor
Slovo vytvoril slovo s textom " dovtipne" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " zaspal" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " zvolal" s dlzkou 6
Konstruktor
Slovo vytvoril slovo s textom " zaspieval" s dlzkou 9
Konstruktor
Slovo vytvoril slovo s textom " bojoval" s dlzkou 7
Konstruktor
Slovo vytvoril slovo s textom " obedoval" s dlzkou 8
Konstruktor
Slovo vytvoril slovo s textom " ." s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " ?" s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " !" s dlzkou 1
Konstruktor
Slovo vytvoril slovo s textom " ..." s dlzkou 3
Hlasi
sa konstruktor triedy " Napis rozpravku"
Snehulienka
ladne priadla.
Process
Exit...
Implicitný balík
Metóda main() v triede NapisRozpravku
„pozná“ triedu Slovo preto, lebo sú v spoločnom adresári. Tvoria takzvaný implicitný
balík.
Pre
túto metódu main() už nie sú zaujímavé výpisy o tvorení jednotlivých slov.
Zakomentujte preto pomocné tlačenia, obsiahnuté v konštruktore triedy
Slovo a tento kód dajte preložiť. Znovu
spustite program main z triedy NapisRozpravku. Potom urobte ďalší pokus:
premiestnite preložený súbor Slovo.class do iného adresára. Spustite program
NapisRozpravku. Čo sa stane?
(java.lang.NoClassDefFoundError:
Slovo
at
NapisRozpravku.<init>(NapisRozpravku.java:4)
at
NapisRozpravku.main(NapisRozpravku.java:59)
Exception
in thread "main" Process Exit...)
Teraz
najprv preložte NapisRozpravku, potom tento program spustite. Čo sa stane?
Nakoniec
odstráňte z adresára aj súbor Slovo.java aj súbor Slovo.class. Preložte
NapisRozpravku. Aká je reakcia prekladača?
Ďalší
pokus: konštruktor v triede Slovo
public
Slovo(String obsah) premenujte na
private
Slovo(String obsah)
Preložte
Slovo. java a spustite Slovo. Výstup je taký istý ako predtým. Preložte
NapisRozpravku:
Ako
zareagoval prekladač?
(
NapisRozpravku.java:4:
Slovo(java.lang.String) has private access in Slovo
Slovo[] podstatneMena = new Slovo[]
{new Slovo("Princ"),new Slovo("Laktibrada"),new
Slovo("Tarzan"),new Slovo("Popolvar" )};
^
Prepíšte
konštruktor v triede Slovo na
protected
Slovo(String obsah) a znovu preložte a spustite najprv Slovo, potom
NapisRozpravku. Oba programy zbehnú, lebo NapisRozpravku je v spoločnom
(implicitnom) balíku s triedou Slovo.
(Viac
Eckel str. 216-217 o kľúčovom
slove protected a dedičnosti, my sa ku slovu protected vrátime pri preberaní dedičnosti ).
Proces inicializácie
V nasledovnom
kóde sa pozrieme na to, ako prebieha proces inicializácie statických
premenných, premenných inštancie a v akom poradí sa vytvára objekt, keď je zavolaný konštruktor.
Kód P4.3
//nahodny cinziak
lezi v radovej zastavbe, na nahodnom sidlisku
//
vsetky cinziaky tam maju nahodne vybrany, ale jednotny podorys
//
lisia sa vyskou a tym, ci maju vytah, alebo nie
public
class NahodnyCinziak{
// konstruktor
public NahodnyCinziak(){
System.out.println("Tu
konstruktor: vytvaram nahodny cinziak s " +
"minimalnou sirkou."
+ minimalnaSirka);
}//EndOFKonstruktor
//staticke premenne - premenne triedy:
public static final double
minimalnaSirka = 8.0;
public static double sirka;
public static double dlzka;
// premenne instancie:
public int pocetPoschodi;
public double vyska;
public boolean maVytah = false;
// inicializacny blok
{
System.out.println("\n Zacina pracovat inicializacny blok");
System.out.println("pocetPoschodi
pred inicializaciou: " + pocetPoschodi);
pocetPoschodi = 1 +
(int)(Math.random()*30);
System.out.println("Pocet
poschodi: " +pocetPoschodi);
vyska = 0.5 + pocetPoschodi*2.3;
System.out.println("vyska: "
+ vyska);
if(pocetPoschodi > 3) {
maVytah
= true;
}
System.out.println("Ma vytah:
" + maVytah);
}
//staticky inicializacny blok:
static {
System.out.println("\n Zacina
pracovat staticky inicializacny blok); System.out.println("Sirka
vsetkych cinziakov pred inicializaciou: " + sirka);
sirka = minimalnaSirka
+ Math.random()*10;
System.out.println("Sirka
vsetkych cinziakov: " + sirka);
dlzka = sirka +
Math.random()*20;
System.out.println("Dlzka vsetkych cinziakov: " + dlzka);
}
public static void main(String[] args){
System.out.println("\n
Spustam metodu main v triede Nahodny cinziak");
System.out.println("\n Idem
vytvorit prvy cinziak:");
NahodnyCinziak n = new
NahodnyCinziak();
System.out.println("\n Idem
vytvorit druhy cinziak:");
NahodnyCinziak m =
new NahodnyCinziak();
}//EOMain
}//EOClass
Jeden z výstupov programu:
Zacina pracovat staticky inicializacny blok
Sirka
vsetkych cinziakov pred inicializaciou: 0.0
Sirka
vsetkych cinziakov: 9.180804686437227
Dlzka
vsetkych cinziakov: 23.577058346934585
Spustam metodu main v triede Nahodny cinziak
Idem vytvorit prvy cinziak:
Zacina pracovat inicializacny blok
pocetPoschodi
pred inicializaciou: 0
Pocet
poschodi: 3
vyska:
7.3999999999999995
Ma
vytah: false
Tu
konstruktor: vytvaram nahodny cinziak s minimalnou sirkou.8.0
Idem vytvorit druhy cinziak:
Zacina pracovat inicializacny blok
pocetPoschodi
pred inicializaciou: 0
Pocet
poschodi: 25
vyska:
57.99999999999999
Ma
vytah: true
Tu
konstruktor: vytvaram nahodny cinziak s minimalnou sirkou.8.0
Process
Exit...
Stručne o poradí inicializácie
Z predchádzajúceho príkladu vyplýva, stručne
zhrnuté, takéto poradie inicializácie:
Ešte
predtým, ako je spustený príslušný inicializačný blok, dané premenné majú
priradené implicitné hodnoty: Pre číselné premenné sú to nuly. Potom, sa nastavia statické premenné, prípadne
prebehne statický inicializačný blok (iba raz, pri pri prvom volaní triedy). Pri zavolaní konštruktora sa najprv
nastavia hodnoty referenčných premenných, prebehne inicializačný blok – ak je
nejaký definovaný, potom sa vykoná zvyšok práce konštruktora.
Presne a podrobne o tomto procese
informuje Bruce Eckel. Citujme ho:
It’s helpful to summarize the process of
creating an object. Consider a class called Dog:
The
first time an object of type Dog is created, or the first time a static method
or static field of class Dog is accessed, the Java interpreter must locate
Dog.class, which it does by searching through the classpath.
As
Dog.class is loaded (creating a Class object, which you’ll learn about later),
all of its static initializers are run. Thus, static initialization takes place
only once, as the Class object is loaded for the first time.
When
you create a new Dog( ), the construction process for a Dog object first
allocates enough storage for a Dog object on the heap.
This
storage is wiped to zero, automatically setting all the primitives in that Dog
object to their default values (zero for numbers and the equivalent for boolean
and char) and the references to null.
Any
initializations that occur at the point of field definition are executed.
Constructors
are executed. As you shall see in Chapter 6, this might actually involve a fair
amount of activity, especially when inheritance is involved.
Koniec
citátu.
Trieda String
Preskúmajte
jej dokumentáciu. Podobne u triedy Integer a Boolean.
Cvičenia 4
Cv4.1
Konštruktor
triedy môže vo svojom vnútri volať ľubovoľnú metódu triedy, teda aj iný
konštruktor. Presvedčte sa o tom vytvorením a spustením vhodného
kódu.
Cv4.2 dva malé hlavolamy
Už sme sa oboznámili s varovaním, týkajúcim sa
práce s referenčnými premennými a s objektami. Overte si, či ste
toto varovanie správne pochopili:
I.
V Kóde
P4.1 zameňte v metóde main riadok:
pes = new Slovo();
za
riadok:
dravec = new Slovo();
Inak nechajte kód bez zmeny.
Čo sa stane s výpisom programu? Zmení sa? Odôvodnite svoju odpoveď, potom
pozmenený program spustite a presvedčte sa, či ste uhádli správne.
II.
V Kóde
P4.1 zakomentujte nasledujúci text:
public Slovo() {
System.out.println("Slovo, konstruktor bez
argumentov.");
}
Spustite prekladač. Čo sa
stalo? Viete vysvetliť reakciu prekladača? Nie je v rozpore s tým, čo
sme si hovorili o vytvorení implicitného konštruktora?
Cv4.3
a)
V triede NahodnyCinziak
zakomentujte všetky príkazy v metóde main(). Kód preložte a spustite.
Aký
bude podľa Vás výstup z programu?
b)Vytvorte
triedu NahodnyCinziak2, okopírujte do nej všetok kód z triedy
NahodnyCinziak.
V kóde
o činžiakoch pridajte pred triedu main definíciu novej metódy,
S názvom „ public static double
nahodneCislo(double
odkial,double pokial) “. Táto metóda má ako
výstup náhodné číslo v intervale <odkial, pokial).
Upravte teraz statický inicializačný blok a inicializačný blok tak, že
budú pri generovaní náhodných čísel v maximálne možnej miere používať
služby
Metódy
nahodneCislo. Kód spustite
a presvedčte sa, že aj inicializačný blok môže používať služby metódy,
definovanej v triede. Koniec koncov, nie je to v tomto príklade až také
prekvapivé, vedeli by ste povedať prečo?
Premenujte
teraz metódu na
public double nahodneCislo(double odkial,double pokial)
a dajte kód preložiť.
Všimnite si reakciu prekladača.
c) Urobte zmeny v kóde tak,
aby ste zistili, aké sú hodnoty, priradené premenným typu boolean a String pred
inicializáciou.
Cv4.4
Vytvorte krátky program, v ktorom využijete objekty
a metódy triedy String, Integer, Boolean.
Preveďte
pritom Stringové reprezentácie do objektov Integer, Boolean a opačne.
Riešenia
Cv.4.2
Implicitný konštruktor bez argumentu Java vytvorí, AK sme my nevytvorili
žiaden konštruktor. V opačnom prípade implicitný konštruktor nevytvára.
Cv.4.3
a)
Statický inicializačný blok sa spustí.
b) Používanie metód zvonka nie je čudné preto, lebo pôvodne inicializačný blok používal služby metódy random(), definovanej dokonca v inej triede – v triede Math. A tiež služby metódy println(), definovanej pre objekt „out“ triedy „System“ .
c)
Do inicializačného bloku dajte vypísať hodnotu premennej maVytah ešte pred jej inicializáciou. Zaveďte v triede statickú premennú typu
String „nahodneMenoSidliska“ a pred jej inicializáciou ju dajte vypísať.
d)
Urobte podobný pokus s premennou typu char.
Cv4.4
Využite
okrem iného metódy valueOf(). Pozor, sú
statické.
© Ivan Kupka
Vaše komentáre:
kupka@fmph.uniba.sk