V minulých dílech jsme se věnovali objektům. V tomto dílu si ukážeme, jak vypadají konstrukce ovlivňující větvení programu – podmínky. Představíme si operátory porovnání, jejichž výstupem jsou pravdivostní hodnoty a řekneme si, jaká úskalí plynou z jejich použití. Pravdivostní hodnoty poté využijeme při rozhodování v podmínkách, ternárních operátorech a konstrukci switch.
Od tohoto dílu dále silně doporučuji, abyste si kód nejen přečetli, ale také abyste ho přepsali a zkoušeli poupravovat. Jednou věcí je kód chápat a druhou jej umět reprodukovat. A bohužel jinak, než neustálým zkoušením a chybováním, se programovat ještě nikdo nenaučil.
Operátory porovnání
Ve čvrtém dílu jsme si ukázali některé základní operátory, nyní tento výběr doplníme o operátory porovnání, které mají jako návratou hodnotu pravdivostní typ boolean.
Operátory == a !=
Operátory rovná se (==) a nerovná se (!=) slouží k porovnání hodnot na své levé a pravé straně. Operátor rovnosti vrací true, pokud jsou tyto hodnoty totožné, anologicky operátor nerovnosti vrací v tomto případě false. U primitivních datových typů je porovnání zřejmé – závisí čistě na uložené hodnotě.
Nebezpečné porovnávání objektů
Nebezpečí těchto operátorů nastává u porovnávání ukazatelů na objekty. Pokud si vzpomenete, tak jsme si říkali, že operátor new vrací referenci, která ukazuje do paměti. Čili v proměnné objektového typu není objekt, ale nějaká adresa. Z tohoto důvodu porovnání dvou různých ukazatelů ukazujících na objekty s totožným obsahem skončí jako false, protože obě reference ukazují jinam. Pro porovnání objektů se proto používá metoda equals (dokumentace), již má každý objekt, a kterou můžeme ve svých třídách redefinovat (znovu deklarovat a implementovat).
Velmi nebezpečnou záležitostí se stává neopatrné použití operátorů == a != u řetězců, protože u nich může kompilátor nebo virtuální stroj přijít na to, že řetězec již v paměti existuje a nevytvořit jej znovu (ukázat na místo již existujícího exempláře), což je samozřejmě možné, protože řetězce jsou neměnné a totožné objekty se proto mohou libovolně zastupovat.
Autoboxing
Další a možná ještě nebezpečnější situací je porovnávání objektových wrapperů primitivních datových typů (Java obsahuje pro každý primitivní typ jeho objektový ekvivalent). S těmito typy se váže tzv. autoboxing, což je konstrukce, při které se přiřazením primitivní hodnoty do reference na odpovídající objektový wrapper vytvoří automaticky obalující objekt s patřičnou hodnotou.
Toto nebezpečí spočívá v tom, že si právě při autoboxingu Java ukládá některé často používané objekty bokem a při jejich opětovném autoboxovacím využití vrátí tyto původní instance. U typu Integer (obalující třída int) se jedná o proměnné v rozsahu -128 → 127.
01.
System.out.println(
5
==
5
);
//true
02.
System.out.println(
5
!=
5
);
//false
03.
System.out.println(
'a'
!=
'b'
);
//true
04.
System.out.println(
'a'
==
'b'
);
//false
05.
06.
String s =
"pes"
;
//String je objekt
07.
System.out.println(s ==
null
);
//false, s neukazuje na null
08.
09.
System.out.println(
null
==
null
);
//true
10.
11.
12.
/*****************************************************
13.
* Ukazka nebezpecnosti porovnavani objektovych typu *
14.
*****************************************************/
15.
Integer i =
5
;
//Integer je objektovy wrapper na int
16.
Integer i2 =
5
;
17.
System.out.println(i == i2);
//true, cacheovane hodnoty
18.
19.
Integer i3 =
128
;
//tohle Java necacheuje
20.
Integer i4 =
128
;
21.
System.out.println(i3 == i4);
//false
22.
23.
Integer i5 =
new
Integer(
5
);
//puvodni priklad bez autoboxingu
24.
Integer i6 =
new
Integer(
5
);
25.
System.out.println(i5 == i6);
//false, cache se vztahuje jenom k autoboxingu
26.
27.
/******************************
28.
* Jak by to melo byt spravne *
29.
******************************/
30.
System.out.println(i.equals(i2));
//true
31.
System.out.println(i3.equals(i4));
//true
32.
System.out.println(i5.equals(i6));
//true
Z tohoto kódu plyne jediné poučení. Operátory rovno a nerovno používejte pouze pro primitivní datové typy. Pokud u objektů vysloveně nechcete porovnávat reference, tak vždy používejte metodu equals, jinak se Váš program bude chovat maximálně nevyzpytatelně.
Operátory >, <, >=, <=
Pro porovnávání číselných typů máme k dispozici operátory porovnání větší, menší, větší nebo rovno, menší nebo rovno (>, <, >=, <=).
1.
System.out.println(
5.5
>
5
);
//true
2.
System.out.println(
5.5
<
5
);
//false
3.
System.out.println(
5
<=
5
);
//true
4.
System.out.println(
5.5
>=
6
);
//false
Operátor instanceof
Pro úplnost si ještě uvedeme nástin toho, co umí operátor instanceof, ke kterému se v jednom z následujících dílů vrátíme a vysvětlíme si, proč je jeho časté využívání ukazatelem špatného návrhu aplikace.
Operátor instanceof vrací true, pokud je daný objekt instancí uvedené třídy, případně instancí libovolného podtypu této třídy (odvozování podtypů nás v rámci tohoto tutoriálu teprve čeká).
1.
System.out.println(
""
instanceof
String);
//true
Logické operátory
Jednotlivé podmínky můžeme vrstvit pomocí logických operátorů konjunkce (a), disjunkce (nebo) a negace.
Negace
Nejjednodušším operátorem je operátor negace (!). Tento umisťujeme vždy před výraz, jehož logickou hodnotu chceme negovat (otočit).
1.
boolean
b =
true
;
2.
System.out.println(b);
//true
3.
b = !b;
4.
System.out.println(b);
//false
5.
System.out.println(!(
5
<
6
));
//false
Konjunkce a disjunkce
Pro konjunci a disjunkci existuje dokonce po dvou operátorech. Rozdíl mezi nimi je v jejich vyhodnocování. Při použití logických operátorů konjukce a disjunkce (& a |) budou vyhodnoceny vždy obě strany operátoru. V případě podmíněných oeprátorů (&& a ||) bude vyhodnocování výrazu ukončeno v okamžik, kdy se již výsledek nemůže změnit.
To znamená, že pokud je levá strana operátoru && false, tak operátor vrátí okamžitě false (libovolná hodnota na pravé straně již výsledek nemůže zvrátit). Obdobně pokud je pravdivá levá strana operátoru ||, tak ten vrátí bez dalšího vyhodnocování pravé strany true.
01.
/**
02.
* Demonstrace vyhodnocovani vyrazu, pouzivame metody, abychom si
03.
* nase zavery mohli overit, v praxi bychom samozrejme pouzivali pouze @true a @false
04.
* @param args the command line arguments
05.
*/
06.
public
static
void
main(String[] args) {
07.
/*******************************
08.
* Vyhodnocovani &, &&, | a || *
09.
*******************************/
10.
System.out.println(returnFalse() & returnTrue());
//vyhodnoti se oboji
11.
System.out.println(returnFalse() && returnTrue());
//pouze prvni cast
12.
13.
System.out.println(returnTrue() | returnFalse());
//vyhodnoti se oboji
14.
System.out.println(returnTrue() || returnFalse());
//pouze prvni cast
15.
//slozeny vyraz, vyhodnoti se vse, vypise true
16.
System.out.println((returnTrue() && returnFalse()) | !returnFalse());
17.
}
18.
19.
/**
20.
* Vypise retezec "returnFalse" na konzoli a vrati false
21.
* Metoda je staticka, protoze nevytvarime zadnou instanci, volame tuto
22.
* metodu ze statickeho kontextu metody main (vstupni bod aplikace)
23.
* Pokud bychom tuto metudu chteli mit instancni, pak bychom odstranili slovo
24.
* static a v metode main vytvorili instanci tohoto objektu (zkuste)
25.
* @return vzdy @false
26.
*/
27.
public
static
boolean
returnFalse(){
28.
System.out.println(
"returnFalse"
);
29.
return
false
;
30.
}
31.
/**
32.
* Vypise retezec "returnTrue" na konzoli a vrati true
33.
* @return vzdy @true
34.
*/
35.
public
static
boolean
returnTrue(){
36.
System.out.println(
"returnTrue"
);
37.
return
true
;
38.
}
Při programování se setkáme v drtivé většině případů s podmíněnými příkazy (&&, ||), jelikož jednak budou lépe popisovat to, co budeme chtít udělat a druhak jsou šetrnější ke zdrojům aplikace (nemusí se v daných případech ověřovat ona druhá část výrazu).
Podmínky
Základním konstruktem je samotná podmínka – příkaz if – za kterým následuje booleovský výraz v kulatých závorkách a blok (nebo jeden příkaz), který se vykoná, je-li podmínka splněna.
1.
if
(i %
3
==
0
){
//pokud je cislo delitelne tremi
2.
System.out.println(
"Cislo je delitelne tremi!"
);
3.
}
Druhým konstruktem je else, za kterým následuje buď jednotlivý příkaz nebo blok. Větví else se vydá zpracování programu tehdy, není-li splněna podmínka u nejbližšího if.
1.
if
(i %
3
==
0
){
//pokud je cislo delitelne tremi
2.
System.out.println(
"Cislo je delitelne tremi!"
);
3.
}
else
{
4.
System.out.println(
"Cislo neni delitelne tremi"
);
5.
}
Pokud chceme postupně ověřit více podmínek stejné úrovně, tak tyto konstrukty složíme za sebe a vznikne složenina else if(podmínka). Všimněme si, že if je zde v roli onoho jednoho příkazu, který můžeme vepsat přímo za else.
01.
/**
02.
* Ukazkova metoda pro podminky (staticka, abychom nemuseli vytvaret instanci obalujiciho objektu)
03.
*/
04.
public
static
void
testCondition(){
05.
int
i = (
int
) (Math.random()*
10
);
//nahodne cislo 0 az 9
06.
if
(i %
3
==
0
){
//pokud je cislo delitelne tremi
07.
System.out.println(
"Cislo je delitelne tremi!"
);
08.
}
else
if
(i %
2
==
0
) {
09.
System.out.println(
"Cislo je sude"
);
10.
}
else
{
11.
System.out.println(
"Cislo neni delitelne tremi, ani neni sude"
);
12.
}
13.
14.
//za if a else muzeme napsat jeden prikaz bez vyuziti bloku.
15.
//Obcas to udela kod prehlednejsim, ale obecne bych to (krome pripadu else if) nedoporucil,
16.
//protoze to pri nevhodnem formatovani kodu muze vest k chybam.
17.
if
(i ==
7
) System.out.println(
"Cislo je 7"
);
18.
else
if
(i ==
6
) System.out.println(
"Cislo je 6"
);
19.
else
System.out.println(
"Cislo neni ani 7 ani 6"
);
20.
}
Ternární operátor
Ternární operátor, který zapisujeme jako
1.
podmínka ? odpověď
1
: odpověď
2
;
slouží ke zjednodušení (zkrácení) přiřazovací konstrukce if-then-else. Jeho interpretace je následující. Pokud platí podmínka před otazníkem, pak operátor vrátí první z odpovědí (oddělené dvojtečkou). Pokud podmínka není uspokojena, pak vrátí druhou odpověď.
Technicky je sice možné skládat řadu ternárních operátorů do sebe a zajistit tak komplexnější rozhodování, ale jedná se o praktiku, jež velmi znepřehledňuje kód, a kterou je z tohoto důvodu zdraví nebezpečné používat (případný čtenář tohoto kódu by Vás mohl insultovat).
01.
/**
02.
* Metoda demonstrujici ternarni operator, ktera vraci osloveni osoby v anglictine
03.
* na zaklade toho, zda-li oslovujeme zenu nebo muze a jejich rodinneho stavu
04.
* (pan, slecna, pani)
05.
* @param male priznak toho, zda-li se jedna o muze (@true), nebo zenu (@false)
06.
* @param married male priznak rodinneho stavu - zenaty/vdana (@true), svobodny/svobodna (@false)
07.
* @return osloveni
08.
*/
09.
public
static
String createGreeting(
boolean
male,
boolean
married){
10.
String base =
"Dear "
;
11.
if
(male){
// je to muz
12.
return
base +
"Mr."
;
13.
}
else
{
//je to zena
14.
return
base + (married ?
"Mrs."
:
"Ms."
);
15.
}
16.
/*
17.
* takto by to take slo zapsat, ale uz je to ponekud neprehledne
18.
* pripade pozuti slozitejsich logickych podminek a vice slozenych
19.
* ternarnich operatoru by to byl konec sveta
20.
*/
21.
//return base + (male ? "Mr." : married ? "Mrs." : "Ms.");
22.
}
Switch
Méně častou možností, jak řídit tok, je příkaz switch (přepínač). Switch, jak již název napovídá, umožňuje na základě hodnoty proměnné přepínat mezi několika větvemi programu. Proměnná, která složí jako přepínač může být následujících typů: char, byte, short, int. Tento seznam je platný pro Javu 1.6, ve verzi 1.7 by se měl rozšířit o řetězec.
Samotný zápis switche se od podmínek v některých aspektech liší. S podmínkami analogicky definujeme blok nejprve příkazem switch, za kterým následuje v kulatých závorkách proměnná, podle které přepínáme, a samotný blok příkazu.
V tomto bloku ovšem nalezneme vždy nejprve návěští case, za ním hodnotu proměnné, která odpovídá danému stavu přepínače a dvojtečku. Za dvojtečkou následují příkazy odpovídající tomuto stavu a příkaz break, který slouží k opuštění bloku switch. Pokud bychom jej z jakéhokoliv důvodu neuvedli nebo na něj zapomněli, tak se provede i následující case blok.
01.
int
day = (
int
) (Math.random() *
7
);
//cisla 0-6
02.
switch
(day) {
03.
case
0
:
04.
System.out.println(
"Je pondeli"
);
05.
break
;
06.
case
1
:
07.
System.out.println(
"Je utery"
);
08.
break
;
09.
case
2
:
10.
System.out.println(
"Je streda"
);
11.
break
;
12.
case
3
:
13.
System.out.println(
"Je ctvrtek"
);
14.
break
;
15.
case
4
:
16.
System.out.println(
"Je patek"
);
17.
break
;
18.
case
5
:
19.
System.out.println(
"Je sobota"
);
20.
break
;
21.
case
6
:
22.
System.out.println(
"Je nedele"
);
23.
break
;
24.
}
25.
26.
//pokazeny switch
27.
int
switcher =
0
;
28.
switch
(switcher){
29.
case
0
:
30.
System.out.println(
"Toto bude vypsano, ale zapomeneme na break"
);
31.
case
1
:
32.
System.out.println(
"A toto se vypise taky"
);
33.
}
Metody equals a hashCode
Aby nebyl dnešní díl pouze přehlídkou aha-příkladů, tak si ukážeme, jakým způsobem můžeme předefinovat (překrýt) metodu equals u třídy zvířátka z minulých dílů.
S redefinicí metody equals se bohužel váže i redefinice metody hashCode (dokumentace), která slouží při ukládání objektů do rozptýlených tabulek (hash table). Na vysvětlení této datové struktury a všech souvislostí bohužel zatím nemáme dostatek znalostí. Proto se spokojíme s metodou hashCode, kterou nám na základě naší úpravy equals vygeneruje vývojové prostředí.
01.
/**
02.
* Zviratko, ktere uz ma i nejake operace
03.
* @author Pavel Micka
04.
*/
05.
public
class
Animal {
06.
/**
07.
* Druh zviratka
08.
*/
09.
private
String kind;
10.
/**
11.
* Zvuk, ktery vydava
12.
*/
13.
private
String sound;
14.
.
15.
.
16.
.
17.
@Override
//kontrola toho, ze skutecne prekryvame (slouzi k prevenci chyb z nepozornosti)
18.
public
boolean
equals(Object obj) {
19.
if
(obj
instanceof
Animal){
//je stejneho typu
20.
Animal animal = (Animal) obj;
//muzeme tedy pretypovat
21.
if
(
this
.kind.equals(animal.kind) &&
this
.sound.equals(animal.sound)){
22.
return
true
;
//je stejneho druhu a vydava stejny zvuk
23.
}
24.
}
25.
return
false
;
26.
}
27.
/*
28.
* Tuto metodu nam nageneruje vyvojove prostredi na zaklade nasi upravy
29.
* metody equals
30.
*/
31.
@Override
32.
public
int
hashCode() {
33.
int
hash =
5
;
34.
hash =
79
* hash + (
this
.kind !=
null
?
this
.kind.hashCode() :
0
);
35.
hash =
79
* hash + (
this
.sound !=
null
?
this
.sound.hashCode() :
0
);
36.
return
hash;
37.
}
38.
}
Poznámka: kód není kompletní, ony tři tečky značí, že je tam reálně zbylý kód z minulých dílů, který ovšem nyní není podstatný a pouze by překážel.