Dnes budu psát o jedné prkotině, kterou jsme včera řešili s jedním kolegou. Zajímal by mě váš názor na to. Je to opravdu jednoduché. Představte si, že máte následující metodu:
public List<Data> processData(List<Data> data) { List<Data> result = new ArrayList<Data>(); for (Data d:data) { //do something } return result; }
Jak vidíte dostane na vstupu nějaký seznam, ten proiteruje a na základě vstupních dat vrátí nějaký výsledek. Otázka je, jaký typ zvolit pro parametr a potažmo i návratovou hodnotu. Pokud je tato metoda použita na jednom místě není moc co řešit. V našem případě ji ale chceme mít ve veřejném rozhraní. Ba co víc, my počítáme s tím, že programátoři budou vytvářet její různé implementace. Pak už je na místě se pořádně zamyslet. Máme několik možností. Můžeme použít List jako v předchozím případě. Mě by se ale mnohem více líbilo použít Iterable. Samozřejmě můžeme jít zlatou střední cestou a použít Collection.
public Collection<Data> processData(Iterable<Data> data) { List<Data> result = new ArrayList<Data>(); for (Data d:data) { //do something } return result; }
Jaká je ale ta správná volba? Všechno má své pro i proti. Když použiji Iterable, dávám tím jasně najevo, že se argument nechystám nijak měnit, že přes něj chci jenom iterovat. Do metody mohu nacpat jakoukoliv kolekci. Navíc mi to otvírá cestu k tomu, abych použil návrhový vzor Iterator a třeba za pomocí commons-collections nebo Google collections si vstup filtroval. Na druhou stranu mi to může zkomplikovat budoucí implementaci metody. Třeba někdo, kdo bude metodu implementovat, bude chtít zavolat metodu size(). Nebo bude chtít zjistit jestli kolekce obsahuje nějaký prvek. Tím, že v signatuře metody použiji Iterable ho o tuto možnost připravím. Práce s iterátorem je navíc dost nepohodlná.
Když použiji List, tak nebude na první pohled jasné, jestli metoda nebude například do seznamu něco přidávat. Nebude to jasné ani volajícímu, ani implementátorovi. Navíc když budu chtít metodu volat například se Setem, budu ho muset překopírovat do Listu. Já vím, je to na jeden řádek, ale znepřehlední to kód.
Zlatá střední cesta je pravděpodobně nejlepší. Když použiji Collection, zajistím dostatečný komfort jak pro volajícího tak pro implementátora. Sice pořád nebude jasné jestli může být kolekce metodou změněna, ale s tím se asi budeme muset smířit. Navíc nebudeme moci použít vzor Iterator, ale vzhledem k tomu, že ho nikdo beztak nezná, to nebude tak velká škoda.
Zase se dostáváme ke kompromisu, jako všude jinde v programování. Musíme hledat kompromis mezi komfortem a bezpečností, mezi čistotou návrhu a výkonem a nebo jako já, mezi čistotou návrhu a komfortem. V tomto případě je to opravdu jenom prkotina, ale kdo ví, třeba se z ní časem může vyklubat ošklivý problém. A to bohužel ať už zvolíme jakoukoliv variantu.
No jo, treba v Cocoa pro Objective-C je tohle snadno vyresene — tam jsou totiz ke kazdemu typu kontejneru dve dostupne verze, jedna nemenitelna a druha (od ni dedici) menitelna. Tim padem primo z deklarace metody je znamo, zda je mozno ji uvnitr metody menit ci nikoliv.
Na druhou stranu Cocoa ani zdaleka nenabizi takovy komfort co do mnozstvi typu kontejneru a interfacu jako Java.
A co se tyka C#, ten ma tusim i nejakou abstraktni tridu (kolekci), ktera umoznuje pouze read-only pristup.
Asi je lepsi Collection – rika, co chci a kontrakt je popsan v javadoc. Tam by melo stejne byt napsano, jestli muze metoda kolekci menit.
Myslim, ze API je rozhrani s dokumentaci.
Je tu ještě jeden aspekt, a divím se, že nebyl zmíněn (nebo jsem si toho nevšiml): při použití List nebo Collection je potřeba seznam zpracovat celý najednou, ihned.
Použití Iterable nebo Iterator má tu výhodu (která v jistých situacích může být nakrásně zanedbatelná, samozřejmě), že zpracování lze provádět až když je potřeba, např. po jednotlivých prvcích nebo podobně.
Implementace něčeho takového je samozřejmě trochu komplikovanější.
A ještě:
> Když použiji Iterable, dávám tím jasně najevo, že se argument nechystám nijak měnit, že přes něj chci jenom iterovat.
S tím bych si dovolil nesouhlasit, Iterable je prostě proud objektů. Jednoduchý příklad: na vstupu je posloupnost čísel, na výstupu posloupnost částečných součtů (uff, ještě že tuhle část matematiky už mám dávno za sebou 🙂 ).
Tenhle příklad má ještě jednu zajímavou vlastnost: s použitím Iterable/Iterator můžu metodě předat i nekonečnou posloupnost, to si s Collection/Listem dovolit nemůžu. Což může být v jistých situacích taky účelné.
Zlaté C++ a jeho klíčové slovo const. Mám objekt a když kamkoli (do parametrů metody třeba) napíšu const je jasné, že objekt se nezmění a je jenom ke čtení.
to agent: V jave muzu pouzit preci final.
Ale k tomu pripadu, je jasne, ze by se melo co nejvice programovat proti rozhranni a ne proti implementaci, ale kdyz zhodnotim Collection a List, radeji vidim List. Uz jen z duvodu, ze se s nim proste lepe pracuje. Prevody na jiny zpusob je vcelku trivialni. Navic List je podle meho stejne bran temer vsude v potaz.
Jak uz tady nekdo psal, to co metoda skutecne dela, tak k tomu je prece javadoc.
Iterable ma jednu obrovskou nevyhodu a to, ze nevraci velikost. Pokud by to mohla implementace potrebovat, pak by to byl problem. Osobne bych volil Collection.
Zjistit velikost Iterable není problém, prostě ho celý proiterovat 🙂 Pomocná metoda může jako triviální optimalizaci provést test, zda objekt implementuje Collection, a případně zavolat jeho metodu size.
> Zjistit velikost Iterable není problém, prostě ho celý proiterovat.
aneb:
> Tenhle příklad má ještě jednu zajímavou vlastnost: s použitím > Iterable/Iterator můžu metodě předat i nekonečnou posloupnost
A pockat az do smrti – serveru nebo vasi 😉
Rozhodne collection. List jen v pripadech kdy to skutecne musi byt list; jinak diskvalifikuje mnoziny (HashSet, TreeSet, Map.keySet) ci dokonce obecne kolekce (Map.values).
Iterable jen pokud jsou hodnoty generovany postupne (napriklad wrapper okolo ResultSet, nebo prod dat ze site) a neni tedy predem znama velikost; jinak ma jen same nevyhody oproti Collection.
Neco jineho by bylo, pokud by pole automaticky implementovala Iterable (kdyz uz na ne lze pouzit for cyklus). Bez teto feature je Iterable jen takovy “druhorady obcan”.
koalix: no jasně, to jsem samozřejmě v háji — když nevím, co dělám 🙂
a nebo pouzit nejvyssiho predka Object 🙂
xxx: +10 ;-)))
finc: to neni to same. C++ rozlisuje const vector *foo a vector const *foo. To druhe zpusobi, ze neni mozne menit __obsah__ vektoru (tj. volat ne-const metody). Final v Jave odpovida te prvni variante.
Ahoj, muzu mit skoro offtopic dotaz? Nedavno jsem si tu precetl, ze hledas praci v anglii a pochopitelne jsem se podival na CV :o) Nemuzes kdyztak na blog pls napsat neco o javacertifikacich? kde se to da v CR sehnat a nejake zdroje, ze kterych se jde na to pripravit, dekuji.
aha, omlouvám se – nedávno jsem nasel neco o SCJP na tomhle blogu snad i. :o)
Ad C++ const: Tohle je opravdu silná fičura jazyka, když jsem o tom tak přemýšlel po přečtení toho článku a komentářů, myslim, že zavedení klíčového slova const by mohl být dobrý zlepšovák. final by si ponechalo původní funkčnost – finální třídy, metody a reference. const by naopak fungovalo skutečně jako ukazatel toho, že daná metoda nesmí měnit instanci a volat non-const metody, const identifikátor by pak řikal, že přes něj nelze volat non-const metody na referencované instanci.
A co kolekce? Jak říct, že chci mít konstantní kolekci referencí, ale stav samotných objektů uvnitř měnit chci – pak by to šlo napsat jako ‘const Collection c’. Pokud bych chtěl konstantní objekty uvnitř, použil bych naopak ‘Collection c’. Pokud bych to chtěl zkombinovat na plně konstantní kolekci, napsal bych ‘const Collection c’. No, možná že je to pře-const-ováno, ale jistě by se to takto dalo použít. Když generiky, tak něco na způsob ‘const List list’. Jedinou nejednoznačnost vidím u primitivních typů. Tam by klíčová slova final i const měla prakticky identický význam, protože zpětná kompatibilita. Používání const v kolekcích generiky vlastně vyžaduje, jen když nenapíšu konkrétní typ, ale jen const, bude to to samé jako const Object.
V každém případě by nám odpadl problém, jestli použít Collection nebo Iterable/Iterator.
Na druhou stranu – Pokud mi opravdu záleží na tom, jak se pracuje s Listem a Setem, proč neudělat dvě přetížené verze té metody?