Kdysi jsme si řekli, že každý soubor může obsahovat právě jednu veřejnou třídu/rozhraní a libovolné množství neveřejných tříd a rozhraní. Dnes pronikneme hlouběji a ukážeme si, jakým způsobem můžeme definovat třídy uvnitř jiných tříd a rozhraní, případně dokonce uvnitř jednotlivých metod.
Vnořené třídy/rozhraní
Nejjednodušší variantou zanořených typů jsou vnořené třídy/rozhraní (nested/embedded class/interface). Jsou to typy, které jsou definovány v rámci jiné třídy mezi jejími metodami a jsou uvozeny klíčovým slovem static. Vnořené třídy definované v rámci rozhraní jsou statické implicitně.
V případě vnořených tříd a rozhraní se jedná pouze o pojmenovávací konvenci, kterou dáváme najevo, že k sobě obalovaný a obalující typ nějakým způsobem patří, ale zároveň mohou existovat nezávisle na sobě.
Pro vnořené třídy (a veškeré zanořené typy obecně) platí, že mají přístup k soukromým proměnným obalujícího typu, stejně jako má obalující typ přístup k jejich proměnným.
Příklad
Příkladem může být mobilní telefon a SIM karta. Telefon může existovat bez SIM karty, stejně tak SIM karta může existovat bez mobilního telefonu. Zároveň ale cítíme, že mezi těmito objekty existuje určitá pevnější vazba.
01.
/**
02.
* Trida mobilniho telefonu
03.
* @author Pavel Micka
04.
*/
05.
public
class
CellPhone {
06.
07.
/**
08.
* International Mobile Equipment Identity
09.
*
10.
* Field je final - imai nelze zmenit
11.
*/
12.
private
final
String imei;
13.
private
SIMCard card;
14.
15.
/**
16.
* Konstruktor, kazdy telefon ma imai
17.
* @param imei
18.
*/
19.
public
CellPhone(String imei) {
20.
this
.imei = imei;
21.
}
22.
23.
/**
24.
* @return the imai
25.
*/
26.
public
String getImei() {
27.
return
imei;
28.
}
29.
30.
/**
31.
* @param card the card to set
32.
*/
33.
public
void
setCard(SIMCard card) {
34.
this
.card = card;
35.
}
36.
37.
@Override
38.
public
String toString() {
39.
String phoneNumber =
null
;
40.
if
(
this
.card !=
null
) {
41.
phoneNumber =
"Obsahuji SIM kartu s telefonnim cislem: "
+ card.phoneNumber +
"."
;
//primy pristup k fieldum vnitrni tridy
42.
}
else
{
43.
phoneNumber =
"Neobsahuji zadnou SIM kartu."
;
44.
}
45.
return
"Jsem mobilni telefon a mam imei: "
+ imei +
". "
+ phoneNumber;
46.
}
47.
48.
/**
49.
* Vnorena trida SIM karty
50.
*/
51.
public
static
class
SIMCard {
52.
53.
private
String phoneNumber;
54.
55.
public
SIMCard(String phoneNumber) {
56.
this
.phoneNumber = phoneNumber;
57.
}
58.
59.
/**
60.
* Setter na telefonni cislo (coz snad v realnem svete jde :-])
61.
* @param phoneNumber
62.
*/
63.
public
void
setPhoneNumber(String phoneNumber) {
64.
this
.phoneNumber = phoneNumber;
65.
}
66.
67.
public
String getPhoneNumber() {
68.
return
phoneNumber;
69.
}
70.
}
71.
}
Třídu CellPhone.SIMCard můžeme importovat úplně stejným způsobem, jako to děláme s třídami různých balíčků. Záleží pouze na vkusu programátora, zda-li si přeje tímto způsobem zdůraznit vazbu, nebo jestli bude volat třídu přímo.
Volání
01.
/**
02.
* @param args the command line arguments
03.
*/
04.
public
static
void
main(String[] args) {
05.
/**
06.
* Vytvorime telefon, jak jsme zvykli
07.
*/
08.
CellPhone phone =
new
CellPhone(
"050277-22-222751-3"
);
09.
10.
/**
11.
* Vytvorime sim kartu, je zvykem pouzit teckovaci notaci z obalujici tridy
12.
* Jinak zde zadny rozdil neni. Vnorenou tridou pouze rikame, ze se jedna
13.
* o SIMCard k mobilnimu telefonu (ne k cemukoliv jinemu, kdyby ji to nahodou
14.
* take melo).
15.
*/
16.
CellPhone.SIMCard card =
new
CellPhone.SIMCard(
"77623456"
);
17.
phone.setCard(card);
18.
19.
/**
20.
* Nechame vypsat objekt telefonu, pripominam, ze se zavola metoda toString
21.
*/
22.
System.out.println(phone);
23.
}
1.
Jsem mobilni telefon a mam imei: 050277-22-222751-3. Obsahuji SIM kartu s telefonnim cislem: 77623456.
Vnitřní třídy
Vnitřní třídy (inner class, member class) definujeme stejně jako třídy vnořené, jediný rozdíl je v tom, že neuvádíme modifikátor static. Takto definovaná třída má silnější vztah k obalující třídě než vnořená třída, protože obsahuje implicitní ukazatel na obalující instanci. Právě z tohoto důvodu nelze vytvořit vnitřní rozhraní (a kompilátor vždy doplní implicitní modifikátor static, pokud jej neuvedeme my).
Příklad
Králíci z klobouku jsou dobrým příkladem vnitřní třídy. Bob i Bobek se v klobouku vzbudí, pak celý den někde pracují a večer se do klobouku opět vrátí. Můžeme s klidem v duši říct, že Bob ani Bobek nemohou bez kouzelného klobouku existovat.
01.
/**
02.
* Klobouk - v tomto pripade se vyhybam jinak velmi vhodne konvenci psat
03.
* nazvy trid anglicky, jelikoz se jedna o ciste ceskou
04.
* pohadku a tento priklad by byl v anglictine zmateny pro cechy a cizinec
05.
* by ho stejne nepobral :-]
06.
* @author Pavel Micka
07.
*/
08.
public
class
Klobouk {
09.
10.
/**
11.
* Nazev klobouku
12.
*/
13.
private
String hatName;
14.
private
Kralik[] inhabitants;
15.
16.
public
Klobouk(String name) {
17.
18.
this
.hatName = name;
19.
this
.inhabitants =
new
Kralik[
2
];
//pole dvou kraliku
20.
inhabitants[
0
] =
new
Kralik(
"Bob"
);
21.
inhabitants[
1
] =
new
Kralik(
"Bobek"
);
22.
}
23.
24.
/**
25.
* @return the name
26.
*/
27.
public
String getName() {
28.
return
hatName;
29.
}
30.
31.
/**
32.
* @param name the name to set
33.
*/
34.
public
void
setName(String name) {
35.
this
.hatName = name;
36.
}
37.
38.
public
Kralik[] getInhabitants() {
39.
return
inhabitants;
40.
}
41.
42.
public
class
Kralik {
43.
44.
private
String name;
45.
46.
public
Kralik(String name) {
47.
this
.name = name;
48.
}
49.
50.
/**
51.
* @return the name
52.
*/
53.
public
String getName() {
54.
return
name;
55.
}
56.
57.
/**
58.
* @param name the name to set
59.
*/
60.
public
void
setName(String name) {
61.
this
.name = name;
62.
}
63.
64.
@Override
65.
public
String toString() {
66.
String result =
"Ahoj, ja jsem "
+
this
.name +
" a bydlim v klobouku, ktery se jmenuje "
+ Klobouk.
this
.hatName +
"."
;
//Klobouk.this == reference na obalujici instanci
67.
return
result;
68.
}
69.
}
70.
}
Volání
01.
/**
02.
* @param args the command line arguments
03.
*/
04.
public
static
void
main(String[] args) {
05.
Klobouk hat =
new
Klobouk(
"Pokustonuv klobouk"
);
06.
//Klobouk.Kralik bob = new Klobouk.Kralik(); //tohle by nefungovalo, musime byt v kontextu obalujici tridy
07.
08.
Klobouk.Kralik[] rabbits = hat.getInhabitants();
09.
10.
for
(Klobouk.Kralik k : rabbits){
11.
System.out.println(k);
12.
}
13.
}
1.
Ahoj, ja jsem Bob a bydlim v klobouku, ktery se jmenuje Pokustonuv klobouk.
2.
Ahoj, ja jsem Bobek a bydlim v klobouku, ktery se jmenuje Pokustonuv klobouk.
Lokální třídy
O vnitřní třídě, kterou nadefinujeme v rámci metody řekneme, že je lokální (local inner class). Lokální interface definovat nelze (jelikož nemůže být ani vnitřní).
U lokálních tříd nesmíme používat modifikátory public, protected, private a static. Stejně tak tyto třídy nesmějí obsahovat statické proměnné a metody.
Příklad
01.
/**
02.
* @param args the command line arguments
03.
*/
04.
public
static
void
main(String[] args) {
05.
String[] array = {
"žluťoučký kůň"
,
"dědek"
,
"Máj"
,
"májka"
,
"chalupa"
,
"cihla"
,
"řízek"
,
"Řezno"
};
06.
07.
/**
08.
* Vnitřní třída pro porovnávání českých slov
09.
*/
10.
class
CzechComparator
implements
Comparator{
11.
public
int
compare(Object o1, Object o2) {
12.
Collator c = Collator.getInstance(
new
Locale(
"cs"
,
"CZ"
));
13.
return
c.compare(o1, o2);
14.
}
15.
}
16.
17.
Arrays.sort(array,
new
CzechComparator());
18.
19.
for
(String s : array){
20.
System.out.println(s);
21.
}
22.
}
1.
cihla
2.
dědek
3.
chalupa
4.
Máj
5.
májka
6.
Řezno
7.
řízek
8.
žluťoučký kůň
Tento příklad seřadí zadaná česká slova do abecedního pořadí. Rozeberme si nyní jednotlivá použitá volání.
Jádrem programu je samotný řadicí algoritmus, jenž voláme pomocí Arrays.sort(Object[], Comparator), a který seřadí vstupní pole pomocí upraveného Merge sortu do vzestupného pořadí.
Aby Merge sort mohl rozhodnout o uspořádání jednotlivých řetězců, tak mu musíme předat třídu implementující rozhraní Comparator (dokumentace), která definuje metodu compare. Tato metoda příjímá dva argumenty a vrací -1, pokud je první argument menší než druhý argument, 0 pokud si jsou rovny a 1, je-li první argument vyšší než druhý.
Dále používáme také třídu Collator (dokumentace), která za nás provede veškeré řazení. Pro získání Collatoru musíme zavolat tovární metodu getInstance() s parametrem Locale (dokumentace). Locale je javovský objekt, který specifikuje daný geografický, kulturní nebo politický region. V našem případě vytváříme Locale pro češtinu v České republice (kód jazyka specifikuje norma ISO-639, kód státu ISO-3166).
Anonymní třídy
Anonymní třída (anonymous class) je lokální třída na jedno použití (jelikož je na daném místě rovnou použita, tak ji není zapotřebí pojmenovávat – proto anonymní).
Možnost použít danou třídu pouze jednou se může na první pohled jevit jako nešikovná, ale v reálném světě nepotkáte téměř žádnou pravověrnou lokální třídu, pouze třídy anonymní. Je to dáno především tím, že lokální třídy jsou z principu téměř vždy pouze na jedno použití – kdyby nebyly, tak bychom je buď vytvořili jako běžnou třídu, nebo alespoň jako třídu vnitřní.
Konstrukce
Samotná syntaxe konstrukce anonymní třídy je poněkud zvláštní. Nejprve uvádíme klíčové slovo new, jelikož rovnou vytváříme instanci této třídy. Následuje název abstraktní třídy, nebo rozhraní, které anonymní třída implementuje a kulaté závorky, do kterých můžeme vepsat argumenty volání případného konstruktoru (je-li anonymní třída odvozena od abstraktní třídy). Poslední položkou jsou složené závorky, jež obsahují metody abstraktní třídy nebo rozhraní, které musíme doimplementovat.
Technicky vzato lze vytvářet anonymní třídy také od neabstrakních tříd a v definici překrýt některé metody. Tuto konstrukci jsem však zatím v žádném reálném projektu nepotkal.
Příklad
Při použití anonymní třídy by předchozí příklad vypadal následovně (takto byste jej nejpravděpodobněji potkali v reálném kódu):
01.
/**
02.
* @param args the command line arguments
03.
*/
04.
public
static
void
main(String[] args) {
05.
String[] array = {
"žluťoučký kůň"
,
"dědek"
,
"Máj"
,
"májka"
,
"chalupa"
,
"cihla"
,
"řízek"
,
"Řezno"
};
06.
07.
Arrays.sort(array,
new
Comparator(){
08.
public
int
compare(Object o1, Object o2) {
09.
Collator c = Collator.getInstance(
new
Locale(
"cs"
,
"CZ"
));
10.
return
c.compare(o1, o2);
11.
}
12.
});
//strednik ukoncuje prikaz Arrays.sort()
13.
14.
for
(String s : array){
15.
System.out.println(s);
16.
}
17.
}
Literatura
- PECINOVSKÝ, Rudolf. Návrhové vzory. 1. vyd. [s.l.] : Computer press, 2007. 527 s. ISBN 978-80-2511582-4.
- HORTON, Ivor. Java 5. Praha : Neocortex spol s.r.o., 2005. 1443 s. ISBN 80-86330-12-5.